From c61d5c7cbc3f8f7d506b28b1334ccf5eac828b7a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 29 Jun 2018 22:55:37 +1000 Subject: [PATCH 0001/1235] Bump version to 2.2.0-rc.1 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index c2ba8fc18..370260429 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -9,7 +9,7 @@ import logging -__version__ = "2.2.0-dev" +__version__ = "2.2.0-rc.1" log = logging.getLogger('can') From 49eb7301366c6ce79ca3a6c16b3d077110c4f0fe Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 30 Jun 2018 15:03:29 +1000 Subject: [PATCH 0002/1235] Add changelog highlights and set version to 2.2.0 --- CHANGELOG.txt | 13 +++++++++++++ can/__init__.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 06b6ef4ee..781feef24 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,16 @@ + +Version 2.2.0 (2018-06-30) +===== + +* Fallback message filtering implemented in Python for interfaces that don't offer better accelerated mechanism. +* SocketCAN interfaces have been merged (Now use `socketcan` instead of either `socketcan_native` and `socketcan_ctypes`), + this is now completely transparent for the library user. +* automatic detection of available configs/channels in supported interfaces. +* Added synchronized (thread-safe) Bus variant. +* context manager support for the Bus class. +* Dropped support for Python 3.3 (officially reached end-of-life in Sept. 2017) +* Deprecated the old `CAN` module, please use the newer `can` entry point (will be removed in version 2.4) + Version 2.1.0 (2018-02-17) ===== diff --git a/can/__init__.py b/can/__init__.py index 370260429..e10968cd7 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -9,7 +9,7 @@ import logging -__version__ = "2.2.0-rc.1" +__version__ = "2.2.0" log = logging.getLogger('can') From ab30b36bc215ded4cc8dbdcc51ed9c8e0ea49a65 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Mon, 9 Jul 2018 21:59:01 +0200 Subject: [PATCH 0003/1235] Only expose SocketCAN classes directly on Linux --- can/interface.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/can/interface.py b/can/interface.py index 8156f7d08..b0e670942 100644 --- a/can/interface.py +++ b/can/interface.py @@ -20,7 +20,10 @@ from .util import load_config from .interfaces import BACKENDS -from can.interfaces.socketcan.socketcan import CyclicSendTask, MultiRateCyclicSendTask +if 'linux' in sys.platform: + # Deprecated and undocumented access to SocketCAN cyclic tasks + # Will be removed in version 3.0 + from can.interfaces.socketcan import CyclicSendTask, MultiRateCyclicSendTask # Required by "detect_available_configs" for argument interpretation if sys.version_info.major > 2: From ad7d2c09e39dc793a57cc2a17f711ebddba4e5f5 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Wed, 11 Jul 2018 20:06:58 +0200 Subject: [PATCH 0004/1235] Raise meaningful exception for Vector when a channel is not available (#355) --- can/interfaces/vector/canlib.py | 13 ++++++++++--- can/interfaces/vector/vxlapi.py | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 3d7f6c119..33551deb9 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -29,7 +29,7 @@ # Import Modules # ============== -from can import BusABC, Message +from can import BusABC, Message, CanError from can.util import len2dlc, dlc2len from .exceptions import VectorError @@ -101,6 +101,14 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, LOG.debug('Channel index %d found', channel) idx = vxlapi.xlGetChannelIndex(hw_type.value, hw_index.value, hw_channel.value) + if idx < 0: + # Undocumented behavior! See issue #353. + # If hardware is unavailable, this function returns -1. + # Raise an exception as if the driver + # would have signalled XL_ERR_HW_NOT_PRESENT. + raise VectorError(vxlapi.XL_ERR_HW_NOT_PRESENT, + "XL_ERR_HW_NOT_PRESENT", + "xlGetChannelIndex") mask = 1 << idx LOG.debug('Channel %d, Type: %d, Mask: 0x%X', hw_channel.value, hw_type.value, mask) @@ -177,8 +185,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self._is_filtered = False super(VectorBus, self).__init__(channel=channel, can_filters=can_filters, - poll_interval=0.01, receive_own_messages=False, bitrate=None, - rx_queue_size=256, app_name="CANalyzer", **config) + **config) def _apply_filters(self, filters): if filters: diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index fbdf442e9..a5f26f80f 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -27,6 +27,7 @@ XL_BUS_TYPE_CAN = 0x00000001 XL_ERR_QUEUE_IS_EMPTY = 10 +XL_ERR_HW_NOT_PRESENT = 129 XL_RECEIVE_MSG = 1 XL_CAN_EV_TAG_RX_OK = 1024 From cb74ee469149e3269441f8b6ce069f8135dfec63 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Wed, 11 Jul 2018 20:19:34 +0200 Subject: [PATCH 0005/1235] Add changelog highlights and set version to 2.2.1 --- CHANGELOG.txt | 5 +++++ can/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 781feef24..d56a151de 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,8 @@ +Version 2.2.1 (2018-07-12) +===== + +* Fix errors and warnings when importing library on Windows +* Fix Vector backend raising ValueError when hardware is not connected Version 2.2.0 (2018-06-30) ===== diff --git a/can/__init__.py b/can/__init__.py index e10968cd7..42c20d2b8 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -9,7 +9,7 @@ import logging -__version__ = "2.2.0" +__version__ = "2.2.1" log = logging.getLogger('can') From cedb85395b136b414ea250f01f4b6e7b97cc3fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Fri, 28 Sep 2018 10:39:41 -0400 Subject: [PATCH 0006/1235] Adding neoVI support for receive_own_messages --- can/interfaces/ics_neovi/neovi_bus.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 6b0c95e69..d5f00d407 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -75,6 +75,8 @@ def __init__(self, channel, can_filters=None, **config): :type channel: int or str or list(int) or list(str) :param list can_filters: See :meth:`can.BusABC.set_filters` for details. + :param bool receive_own_messages: + If transmitted messages should also be received by this bus. :param bool use_system_timestamp: Use system timestamp for can messages instead of the hardware time stamp @@ -125,6 +127,7 @@ def __init__(self, channel, can_filters=None, **config): self._use_system_timestamp = bool( config.get('use_system_timestamp', False) ) + self._receive_own_messages = config.get('receive_own_messages', True) self.channel_info = '%s %s CH:%s' % ( self.dev.Name, @@ -220,6 +223,9 @@ def _process_msg_queue(self, timeout=0.1): for ics_msg in messages: if ics_msg.NetworkID not in self.channels: continue + is_tx = bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG) + if not self._receive_own_messages and is_tx: + continue self.rx_buffer.append(ics_msg) if errors: logger.warning("%d error(s) found" % errors) From 6a6c6411c02421baea1923a6fd6ee76520a0c4b0 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 30 Sep 2018 18:49:37 +1000 Subject: [PATCH 0007/1235] Feature update deprecation notes (#437) * Bump version to 3.0.0-dev and update deprecation notes * Update changelog with message changes * Split installation requirements onto own line --- CHANGELOG.txt | 11 +++++++++-- can/CAN.py | 16 +++++++--------- can/__init__.py | 2 +- can/broadcastmanager.py | 6 +++--- can/interface.py | 2 +- can/interfaces/__init__.py | 10 ++++------ can/util.py | 10 ++++++---- doc/bcm.rst | 2 +- doc/interfaces/socketcan.rst | 2 +- setup.py | 4 +++- 10 files changed, 36 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4bc1be66c..ef0d76c17 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,4 @@ -Version 2.3.0 +Version 3.0.0 ==== Major features @@ -18,6 +18,8 @@ Major features Breaking changes ---------------- +- Interfaces should no longer override `send_periodic` and instead implement + `_send_periodic_internal` #426 - writing to closed writers is not supported any more (it was supported only for some) - the method `Listener.on_message_received()` is now abstract (using `@abc.abstractmethod`) - the file in the reader/writer is now always stored in the attribute uniformly called `file`, and not in @@ -28,6 +30,11 @@ Breaking changes Other notable changes --------------------- +* can.Message class updated #413 + - Addition of a Message.equals method. + - Deprecate id_type in favor of is_extended_id + - documentation, testing and example updates + - Addition of support for various builtins: __repr__, __slots__, __copy__ * IO module updates to bring consistency to the different CAN message writers and readers. #348 - context manager support for all readers and writers - they share a common super class called `BaseIOHandler` @@ -112,7 +119,7 @@ Version 2.2.0 (2018-06-30) * Added synchronized (thread-safe) Bus variant. * context manager support for the Bus class. * Dropped support for Python 3.3 (officially reached end-of-life in Sept. 2017) -* Deprecated the old `CAN` module, please use the newer `can` entry point (will be removed in version 2.4) +* Deprecated the old `CAN` module, please use the newer `can` entry point (will be removed in an upcoming major version) Version 2.1.0 (2018-02-17) ===== diff --git a/can/CAN.py b/can/CAN.py index 127963c95..0ed96dfb1 100644 --- a/can/CAN.py +++ b/can/CAN.py @@ -4,10 +4,10 @@ This module was once the core of python-can, containing implementations of all the major classes in the library, now however all functionality has been refactored out. This API -is left intact for version 2.0 to 2.3 to aide with migration. +is left intact for version 2.x to aide with migration. WARNING: -This module is deprecated an will get removed in version 2.4. +This module is deprecated an will get removed in version 3.x. Please use ``import can`` instead. """ @@ -18,14 +18,12 @@ from can.util import set_logging_level from can.io import * -import logging - -log = logging.getLogger('can') +import warnings # See #267. # Version 2.0 - 2.1: Log a Debug message # Version 2.2: Log a Warning -# Version 2.3: Log an Error -# Version 2.4: Remove the module -log.error('Loading python-can via the old "CAN" API is deprecated since v2.0 an will get removed in v2.4. ' - 'Please use `import can` instead.') +# Version 3.x: DeprecationWarning +# Version 4.0: Remove the module +warnings.warn('Loading python-can via the old "CAN" API is deprecated since v3.0 an will get removed in v4.0 ' + 'Please use `import can` instead.', DeprecationWarning) diff --git a/can/__init__.py b/can/__init__.py index 714de2234..a63646741 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "2.3.0-dev" +__version__ = "3.0.0-dev" log = logging.getLogger('can') diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 87f19edce..6837f0e67 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -11,7 +11,7 @@ import logging import threading import time - +import warnings log = logging.getLogger('can.bcm') @@ -153,6 +153,6 @@ def send_periodic(bus, message, period, *args, **kwargs): :param float period: The minimum time between sending messages. :return: A started task instance """ - log.warning("The function `can.send_periodic` is deprecated and will " + - "be removed in version 3.0. Please use `can.Bus.send_periodic` instead.") + warnings.warn("The function `can.send_periodic` is deprecated and will " + + "be removed in an upcoming version. Please use `can.Bus.send_periodic` instead.", DeprecationWarning) return bus.send_periodic(message, period, *args, **kwargs) diff --git a/can/interface.py b/can/interface.py index 3565deb6c..680566821 100644 --- a/can/interface.py +++ b/can/interface.py @@ -21,7 +21,7 @@ if 'linux' in sys.platform: # Deprecated and undocumented access to SocketCAN cyclic tasks - # Will be removed in version 3.0 + # Will be removed in version 4.0 from can.interfaces.socketcan import CyclicSendTask, MultiRateCyclicSendTask # Required by "detect_available_configs" for argument interpretation diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 77c1d8c4f..e02dd2fb4 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -4,11 +4,9 @@ Interfaces contain low level implementations that interact with CAN hardware. """ -import logging - +import warnings from pkg_resources import iter_entry_points -logger = logging.getLogger(__name__) # interface_name => (module, classname) BACKENDS = { @@ -31,10 +29,10 @@ for interface in iter_entry_points('can.interface') }) -# Old entry point name. May be removed in 3.0. +# Old entry point name. May be removed >3.0. for interface in iter_entry_points('python_can.interface'): BACKENDS[interface.name] = (interface.module_name, interface.attrs[0]) - logger.warning('%s is using the deprecated python_can.interface entry point. ' - 'Please change to can.interface instead.', interface.name) + warnings.warn('{} is using the deprecated python_can.interface entry point. '.format(interface.name) + + 'Please change to can.interface instead.', DeprecationWarning) VALID_INTERFACES = frozenset(list(BACKENDS.keys()) + ['socketcan_native', 'socketcan_ctypes']) diff --git a/can/util.py b/can/util.py index 4d93f6ba6..af421651f 100644 --- a/can/util.py +++ b/can/util.py @@ -12,6 +12,8 @@ import platform import re import logging +import warnings + try: from configparser import ConfigParser except ImportError: @@ -184,11 +186,11 @@ def load_config(path=None, config=None, context=None): if key not in config: config[key] = None - # deprecated socketcan types + # Handle deprecated socketcan types if config['interface'] in ('socketcan_native', 'socketcan_ctypes'): - # Change this to a DeprecationWarning in future 2.x releases - # Remove completely in 3.0 - log.warning('%s is deprecated, use socketcan instead', config['interface']) + # DeprecationWarning in 3.x releases + # TODO: Remove completely in 4.0 + warnings.warn('{} is deprecated, use socketcan instead'.format(config['interface']), DeprecationWarning) config['interface'] = 'socketcan' if config['interface'] not in VALID_INTERFACES: diff --git a/doc/bcm.rst b/doc/bcm.rst index 6f57192fa..96e73d52d 100644 --- a/doc/bcm.rst +++ b/doc/bcm.rst @@ -49,7 +49,7 @@ Functional API .. warning:: The functional API in :func:`can.broadcastmanager.send_periodic` is now deprecated - and will be removed in version 3.0. + and will be removed in version 4.0. Use the object oriented API via :meth:`can.BusABC.send_periodic` instead. .. autofunction:: can.broadcastmanager.send_periodic diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index f9c674174..7c4f89876 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -10,7 +10,7 @@ The full documentation for socketcan can be found in the kernel docs at Versions before 2.2 had two different implementations named ``socketcan_ctypes`` and ``socketcan_native``. These are now deprecated and the aliases to ``socketcan`` will be removed in - version 3.0. Future 2.x release may raise a DeprecationWarning. + version 4.0. 3.x releases raise a DeprecationWarning. Socketcan Quickstart diff --git a/setup.py b/setup.py index cb6d4facc..a40b01268 100644 --- a/setup.py +++ b/setup.py @@ -99,7 +99,9 @@ # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", install_requires=[ - 'wrapt~=1.10', 'typing', 'windows-curses;platform_system=="Windows"', + 'wrapt~=1.10', + 'typing', + 'windows-curses;platform_system=="Windows"', ], extras_require=extras_require, From 44d27dfcaad23e6984f0accbff0d3f53cbbfa604 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 1 Oct 2018 07:49:57 +1000 Subject: [PATCH 0008/1235] Start migration of Message init to use is_extended_id (#440) * Start migration of Message init to use is_extended_id * Update deprecation versions in message.py #424 --- can/message.py | 23 +++++++++++++++-------- doc/message.rst | 7 +++++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/can/message.py b/can/message.py index 57b61e34f..673bc3ca2 100644 --- a/can/message.py +++ b/can/message.py @@ -43,14 +43,14 @@ class Message(object): "is_fd", "bitrate_switch", "error_state_indicator", - "__weakref__", # support weak references to messages - "_dict" # see __getattr__ + "__weakref__", # support weak references to messages + "_dict" # see __getattr__ ) def __getattr__(self, key): # TODO keep this for a version, in order to not break old code # this entire method (as well as the _dict attribute in __slots__ and the __setattr__ method) - # can be removed in 3.0 + # can be removed in 4.0 # this method is only called if the attribute was not found elsewhere, like in __slots__ try: warnings.warn("Custom attributes of messages are deprecated and will be removed in the next major version", DeprecationWarning) @@ -68,20 +68,21 @@ def __setattr__(self, key, value): @property def id_type(self): - # TODO remove in 3.0 + # TODO remove in 4.0 warnings.warn("Message.id_type is deprecated, use is_extended_id", DeprecationWarning) return self.is_extended_id @id_type.setter def id_type(self, value): - # TODO remove in 3.0 + # TODO remove in 4.0 warnings.warn("Message.id_type is deprecated, use is_extended_id", DeprecationWarning) self.is_extended_id = value - def __init__(self, timestamp=0.0, arbitration_id=0, extended_id=True, + def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, is_remote_frame=False, is_error_frame=False, channel=None, dlc=None, data=None, is_fd=False, bitrate_switch=False, error_state_indicator=False, + extended_id=True, check=False): """ To create a message object, simply provide any of the below attributes @@ -99,7 +100,13 @@ def __init__(self, timestamp=0.0, arbitration_id=0, extended_id=True, self.timestamp = timestamp self.arbitration_id = arbitration_id - self.is_extended_id = extended_id + if is_extended_id is not None: + self.is_extended_id = is_extended_id + else: + if not extended_id: + # Passed extended_id=False (default argument is True) so we warn to update + warnings.warn("extended_id is a deprecated parameter, use is_extended_id", DeprecationWarning) + self.is_extended_id = extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame @@ -152,7 +159,7 @@ def __str__(self): if self.data is not None: for index in range(0, min(self.dlc, len(self.data))): data_strings.append("{0:02x}".format(self.data[index])) - if data_strings: # if not empty + if data_strings: # if not empty field_strings.append(" ".join(data_strings).ljust(24, " ")) else: field_strings.append(" " * 24) diff --git a/doc/message.rst b/doc/message.rst index e7e9fb353..fcb5e5e1a 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -103,6 +103,7 @@ Message :type: bool This flag controls the size of the :attr:`~can.Message.arbitration_id` field. + Previously this was exposed as `id_type`. >>> print(Message(extended_id=False)) Timestamp: 0.000000 ID: 0000 S DLC: 0 @@ -110,8 +111,10 @@ Message Timestamp: 0.000000 ID: 00000000 X DLC: 0 - Previously this was exposed as `id_type`. - Please use `is_extended_id` from now on. + .. note:: + + The :meth:`Message.__init__` argument ``extended_id`` has been deprecated in favor of + ``is_extended_id``, but will continue to work for the ``3.x`` release series. .. attribute:: is_error_frame From 940d3324c2ebe043e2462d582134c4d12bfb7228 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 1 Oct 2018 07:56:45 +1000 Subject: [PATCH 0009/1235] Set version to v3.0.0 final --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 2c973c764..b5cc37924 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -9,7 +9,7 @@ import logging -__version__ = "3.0.0-dev" +__version__ = "3.0.0" log = logging.getLogger('can') From 951568cc67517c4696db05131f026fbf0da70bb7 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 1 Oct 2018 08:13:25 +1000 Subject: [PATCH 0010/1235] Minor updates to changelog for 3.0.0 --- CHANGELOG.txt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ef0d76c17..4402a0a53 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -7,7 +7,7 @@ Major features * Adds support for developing `asyncio` applications with `python-can` more easily. This can be useful when implementing protocols that handles simultaneous connections to many nodes since you can write synchronous looking code without handling multiple threads and locking mechanisms. #388 -* New can viewer terminal application. #390 +* New can viewer terminal application. (`python -m can.viewer`) #390 * More formally adds task management responsibility to the `Bus`. By default tasks created with `bus.send_periodic` will have a reference held by the bus - this means in many cases the user doesn't need to keep the task in scope for their periodic messages to continue being sent. If @@ -18,11 +18,10 @@ Major features Breaking changes ---------------- -- Interfaces should no longer override `send_periodic` and instead implement - `_send_periodic_internal` #426 -- writing to closed writers is not supported any more (it was supported only for some) -- the method `Listener.on_message_received()` is now abstract (using `@abc.abstractmethod`) -- the file in the reader/writer is now always stored in the attribute uniformly called `file`, and not in +* Interfaces should no longer override `send_periodic` and instead implement + `_send_periodic_internal` to allow the Bus base class to manage tasks. #426 +* writing to closed writers is not supported any more (it was supported only for some) +* the file in the reader/writer is now always stored in the attribute uniformly called `file`, and not in something like `fp`, `log_file` or `output_file`. Changed the name of the first parameter of the read/writer constructors from `filename` to `file`. @@ -31,8 +30,9 @@ Other notable changes --------------------- * can.Message class updated #413 - - Addition of a Message.equals method. + - Addition of a `Message.equals` method. - Deprecate id_type in favor of is_extended_id + - Initializer parameter extended_id deprecated in favor of is_extended_id - documentation, testing and example updates - Addition of support for various builtins: __repr__, __slots__, __copy__ * IO module updates to bring consistency to the different CAN message writers and readers. #348 @@ -50,13 +50,14 @@ Other notable changes and only buffers messages up to a certain limit before writing/committing to the database. - the unused `header_line` attribute from `CSVReader` has been removed - privatized some attributes that are only to be used internally in the classes + - the method `Listener.on_message_received()` is now abstract (using `@abc.abstractmethod`) * Start testing against Python 3.7 #380 * All scripts have been moved into `can/scripts`. #370, #406 * Added support for additional sections to the config #338 * Code coverage reports added. #346, #374 * Bug fix to thread safe bus. #397 -General fixes, cleanup and docs changes: (#347, #348, #367, #368, #370, #371, #373, #420, #417, #419) +General fixes, cleanup and docs changes: (#347, #348, #367, #368, #370, #371, #373, #420, #417, #419, #432) Backend Specific Changes ------------------------ @@ -94,7 +95,7 @@ serial socketcan ~~~~~~~~~ -* socketcan tasks now reuse a bcm socket +* socketcan tasks now reuse a bcm socket #404, #425, #426, * socketcan bugfix to receive error frames #384 vector From a9ae867dbbc95b9e2c31d8610f0256f8b84abf8f Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Mon, 1 Oct 2018 16:43:05 -0700 Subject: [PATCH 0011/1235] Remove shebangs from files in the module (#443) Follow-up to 56a4cb26da37c854346e32ef866c33bbfd96e362 --- can/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 2c973c764..a63646741 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # coding: utf-8 """ From 34f05dec1e2f8a095f59d0ec40a6eda2015bd94b Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Mon, 1 Oct 2018 17:07:16 -0700 Subject: [PATCH 0012/1235] Fix path image path in scripts documentation (#442) --- doc/scripts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scripts.rst b/doc/scripts.rst index 2b2290ad1..a63f1b108 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -25,7 +25,7 @@ can.viewer A screenshot of the application can be seen below: -.. image:: ../images/viewer.png +.. image:: images/viewer.png :width: 100% The first column is the number of times a frame with the particular ID that has been received, next is the timestamp of the frame relative to the first received message. The third column is the time between the current frame relative to the previous one. Next is the length of the frame, the data and then the decoded data converted according to the ``-d`` argument. The top red row indicates an error frame. From 97339ebdeba59606870a8d58818c79970a8cdc2f Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 3 Oct 2018 20:14:55 +1000 Subject: [PATCH 0013/1235] Remove warning about extended_id parameter. May be added in a future version (#448) --- can/message.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/can/message.py b/can/message.py index 673bc3ca2..97346b366 100644 --- a/can/message.py +++ b/can/message.py @@ -103,9 +103,6 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, if is_extended_id is not None: self.is_extended_id = is_extended_id else: - if not extended_id: - # Passed extended_id=False (default argument is True) so we warn to update - warnings.warn("extended_id is a deprecated parameter, use is_extended_id", DeprecationWarning) self.is_extended_id = extended_id self.is_remote_frame = is_remote_frame From c59c9596f3dbe0edd386c2106b9c1b72bde5647d Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Wed, 3 Oct 2018 06:37:05 -0400 Subject: [PATCH 0014/1235] Fix bug in setting bitrate introduce by the multiple channel support (#446) --- can/interfaces/ics_neovi/neovi_bus.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 6b0c95e69..e138631fb 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -114,13 +114,15 @@ def __init__(self, channel, can_filters=None, **config): ics.open_device(self.dev) if 'bitrate' in config: - ics.set_bit_rate(self.dev, config.get('bitrate'), channel) + for channel in self.channels: + ics.set_bit_rate(self.dev, config.get('bitrate'), channel) fd = config.get('fd', False) if fd: if 'data_bitrate' in config: - ics.set_fd_bit_rate( - self.dev, config.get('data_bitrate'), channel) + for channel in self.channels: + ics.set_fd_bit_rate( + self.dev, config.get('data_bitrate'), channel) self._use_system_timestamp = bool( config.get('use_system_timestamp', False) From bf56c58198d4acdcae21f121f7a189a40091d9a2 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Sat, 6 Oct 2018 12:55:47 +0200 Subject: [PATCH 0015/1235] Fix some slcan issues (#447) Allow partial messages to be received Fix DLC for remote frames Add unit test --- can/interfaces/slcan.py | 48 +++++++++++------ test/test_slcan.py | 112 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 test/test_slcan.py diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index f115c239a..009d45814 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -47,7 +47,10 @@ class slcanBus(BusABC): _SLEEP_AFTER_SERIAL_OPEN = 2 # in seconds + LINE_TERMINATOR = b'\r' + def __init__(self, channel, ttyBaudrate=115200, bitrate=None, + sleep_after_open=_SLEEP_AFTER_SERIAL_OPEN, rtscts=False, **kwargs): """ :param str channel: @@ -59,6 +62,8 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, Bitrate in bit/s :param float poll_interval: Poll interval in seconds when reading messages + :param float sleep_after_open: + Time to wait in seconds after opening serial connection :param bool rtscts: turn hardware handshake (RTS/CTS) on and off """ @@ -72,7 +77,9 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, self.serialPortOrig = serial.serial_for_url( channel, baudrate=ttyBaudrate, rtscts=rtscts) - time.sleep(self._SLEEP_AFTER_SERIAL_OPEN) + self._buffer = bytearray() + + time.sleep(sleep_after_open) if bitrate is not None: self.close() @@ -87,9 +94,7 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, bitrate=None, rtscts=False, **kwargs) def write(self, string): - if not string.endswith('\r'): - string += '\r' - self.serialPortOrig.write(string.encode()) + self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() def open(self): @@ -107,12 +112,20 @@ def _recv_internal(self, timeout): extended = False frame = [] - readStr = self.serialPortOrig.read_until(b'\r') + # Read everything that is already available + waiting = self.serialPortOrig.read(self.serialPortOrig.in_waiting) + self._buffer += waiting - if not readStr: - return None, False - else: - readStr = readStr.decode() + # Check if a complete message has been received + pos = self._buffer.find(self.LINE_TERMINATOR) + if pos == -1: + # Keep reading... + self._buffer += self.serialPortOrig.read_until(self.LINE_TERMINATOR) + pos = self._buffer.find(self.LINE_TERMINATOR) + + if pos != -1: + readStr = self._buffer[0:pos].decode() + del self._buffer[0:pos+1] if readStr[0] == 'T': # extended frame canId = int(readStr[1:9], 16) @@ -129,45 +142,46 @@ def _recv_internal(self, timeout): elif readStr[0] == 'r': # remote frame canId = int(readStr[1:4], 16) + dlc = int(readStr[4]) remote = True elif readStr[0] == 'R': # remote extended frame canId = int(readStr[1:9], 16) + dlc = int(readStr[9]) extended = True remote = True if canId is not None: msg = Message(arbitration_id=canId, - extended_id=extended, + is_extended_id=extended, timestamp=time.time(), # Better than nothing... is_remote_frame=remote, dlc=dlc, data=frame) return msg, False - else: - return None, False + return None, False - def send(self, msg, timeout=0): + def send(self, msg, timeout=None): if timeout != self.serialPortOrig.write_timeout: self.serialPortOrig.write_timeout = timeout if msg.is_remote_frame: if msg.is_extended_id: - sendStr = "R%08X0" % (msg.arbitration_id) + sendStr = "R%08X%d" % (msg.arbitration_id, msg.dlc) else: - sendStr = "r%03X0" % (msg.arbitration_id) + sendStr = "r%03X%d" % (msg.arbitration_id, msg.dlc) else: if msg.is_extended_id: sendStr = "T%08X%d" % (msg.arbitration_id, msg.dlc) else: sendStr = "t%03X%d" % (msg.arbitration_id, msg.dlc) - for i in range(0, msg.dlc): - sendStr += "%02X" % msg.data[i] + sendStr += "".join(["%02X" % b for b in msg.data]) self.write(sendStr) def shutdown(self): self.close() + self.serialPortOrig.close() def fileno(self): if hasattr(self.serialPortOrig, 'fileno'): diff --git a/test/test_slcan.py b/test/test_slcan.py new file mode 100644 index 000000000..29869cb1c --- /dev/null +++ b/test/test_slcan.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# coding: utf-8 +import unittest +import can + + +class slcanTestCase(unittest.TestCase): + + def setUp(self): + self.bus = can.Bus('loop://', bustype='slcan', sleep_after_open=0) + self.serial = self.bus.serialPortOrig + self.serial.read(self.serial.in_waiting) + + def tearDown(self): + self.bus.shutdown() + + def test_recv_extended(self): + self.serial.write(b'T12ABCDEF2AA55\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 2) + self.assertSequenceEqual(msg.data, [0xAA, 0x55]) + + def test_send_extended(self): + msg = can.Message(arbitration_id=0x12ABCDEF, + is_extended_id=True, + data=[0xAA, 0x55]) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual(data, b'T12ABCDEF2AA55\r') + + def test_recv_standard(self): + self.serial.write(b't4563112233\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x456) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 3) + self.assertSequenceEqual(msg.data, [0x11, 0x22, 0x33]) + + def test_send_standard(self): + msg = can.Message(arbitration_id=0x456, + is_extended_id=False, + data=[0x11, 0x22, 0x33]) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual(data, b't4563112233\r') + + def test_recv_standard_remote(self): + self.serial.write(b'r1238\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, True) + self.assertEqual(msg.dlc, 8) + + def test_send_standard_remote(self): + msg = can.Message(arbitration_id=0x123, + is_extended_id=False, + is_remote_frame=True, + dlc=8) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual(data, b'r1238\r') + + def test_recv_extended_remote(self): + self.serial.write(b'R12ABCDEF6\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, True) + self.assertEqual(msg.dlc, 6) + + def test_send_extended_remote(self): + msg = can.Message(arbitration_id=0x12ABCDEF, + is_extended_id=True, + is_remote_frame=True, + dlc=6) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual(data, b'R12ABCDEF6\r') + + def test_partial_recv(self): + self.serial.write(b'T12ABCDEF') + msg = self.bus.recv(0) + self.assertIsNone(msg) + + self.serial.write(b'2AA55\rT12') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 2) + self.assertSequenceEqual(msg.data, [0xAA, 0x55]) + + msg = self.bus.recv(0) + self.assertIsNone(msg) + + self.serial.write(b'ABCDEF2AA55\r') + msg = self.bus.recv(0) + self.assertIsNotNone(msg) + + +if __name__ == '__main__': + unittest.main() From eb8ee51d42399aca913401bc7d073b6e63aa5aa9 Mon Sep 17 00:00:00 2001 From: maxchill Date: Sat, 6 Oct 2018 23:58:57 -0400 Subject: [PATCH 0016/1235] Make typing dependency conditional < python 3.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a40b01268..d4431fe67 100644 --- a/setup.py +++ b/setup.py @@ -100,7 +100,7 @@ python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", install_requires=[ 'wrapt~=1.10', - 'typing', + 'typing;python_version<"3.5"', 'windows-curses;platform_system=="Windows"', ], extras_require=extras_require, From 5f2c5cec8303be8e3b985ee98b01fca4b057aa38 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 7 Oct 2018 16:24:44 +0200 Subject: [PATCH 0017/1235] Doc update This should work like that, right? --- doc/interfaces/socketcan.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 7c4f89876..32c21b4c3 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -142,7 +142,7 @@ To spam a bus: for i in range(10): msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], extended_id=False) bus.send(msg) - # Issue #3: Need to keep running to ensure the writing threads stay alive. ? + time.sleep(1) producer(10) From 63325b27bd4a90f16eacb2f198d83a2efd87ee80 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 7 Oct 2018 16:29:03 +0200 Subject: [PATCH 0018/1235] use is_expedned_id instead of extended_id in Message constructor --- README.rst | 2 +- can/interfaces/ics_neovi/neovi_bus.py | 4 ++-- can/interfaces/iscan.py | 2 +- can/interfaces/ixxat/canlib.py | 2 +- can/interfaces/kvaser/canlib.py | 2 +- can/interfaces/nican.py | 2 +- can/interfaces/pcan/pcan.py | 2 +- can/interfaces/serial/serial_can.py | 2 +- can/interfaces/socketcan/socketcan.py | 2 +- can/interfaces/usb2can/usb2canInterface.py | 2 +- can/interfaces/vector/canlib.py | 4 ++-- can/io/asc.py | 4 ++-- can/io/blf.py | 6 +++--- can/io/canutils.py | 2 +- can/io/csv.py | 2 +- can/io/sqlite.py | 2 +- can/message.py | 6 +++--- doc/interfaces/socketcan.rst | 2 +- doc/message.rst | 6 +++--- examples/cyclic.py | 6 +++--- examples/send_one.py | 2 +- examples/vcan_filtered.py | 6 +++--- test/back2back_test.py | 14 ++++++------- test/contextmanager_test.py | 2 +- test/data/example_data.py | 24 +++++++++++----------- test/logformats_test.py | 4 ++-- test/network_test.py | 2 +- test/simplecyclic_test.py | 8 ++++---- test/test_kvaser.py | 8 ++++---- test/test_message_filtering.py | 4 ++-- test/test_viewer.py | 12 +++++------ test/zero_dlc_test.py | 4 ++-- 32 files changed, 76 insertions(+), 76 deletions(-) diff --git a/README.rst b/README.rst index cf6b45f83..1277403dc 100644 --- a/README.rst +++ b/README.rst @@ -67,7 +67,7 @@ Example usage receive_own_messages=True) # send a message - message = can.Message(arbitration_id=123, extended_id=True, + message = can.Message(arbitration_id=123, is_extended_id=True, data=[0x11, 0x22, 0x33]) bus.send(message, timeout=0.2) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 541ff524a..3f8585b51 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -268,7 +268,7 @@ def _ics_msg_to_message(self, ics_msg): arbitration_id=ics_msg.ArbIDOrHeader, data=data, dlc=ics_msg.NumberBytesData, - extended_id=bool( + is_extended_id=bool( ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME ), is_fd=is_fd, @@ -289,7 +289,7 @@ def _ics_msg_to_message(self, ics_msg): arbitration_id=ics_msg.ArbIDOrHeader, data=ics_msg.Data[:ics_msg.NumberBytesData], dlc=ics_msg.NumberBytesData, - extended_id=bool( + is_extended_id=bool( ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME ), is_fd=is_fd, diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index da8dbffa4..1f1d0c485 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -111,7 +111,7 @@ def _recv_internal(self, timeout): break msg = Message(arbitration_id=raw_msg.message_id, - extended_id=bool(raw_msg.is_extended), + is_extended_id=bool(raw_msg.is_extended), timestamp=time.time(), # Better than nothing... is_remote_frame=bool(raw_msg.remote_req), dlc=raw_msg.data_len, diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 51812b147..91f846554 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -470,7 +470,7 @@ def _recv_internal(self, timeout): rx_msg = Message( timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, - extended_id=True if self._message.uMsgInfo.Bits.ext else False, + is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, arbitration_id=self._message.dwMsgId, dlc=self._message.uMsgInfo.Bits.dlc, data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 2d8305239..1e5c2a49f 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -508,7 +508,7 @@ def _recv_internal(self, timeout=None): rx_msg = Message(arbitration_id=arb_id.value, data=data_array[:dlc.value], dlc=dlc.value, - extended_id=is_extended, + is_extended_id=is_extended, is_error_frame=is_error_frame, is_remote_frame=is_remote_frame, is_fd=is_fd, diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 38cca7504..0e962cd2f 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -245,7 +245,7 @@ def _recv_internal(self, timeout): channel=self.channel, is_remote_frame=is_remote_frame, is_error_frame=is_error_frame, - extended_id=is_extended, + is_extended_id=is_extended, arbitration_id=arb_id, dlc=dlc, data=raw_msg.data[:dlc]) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 0c1881d29..63b3a2a3b 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -218,7 +218,7 @@ def _recv_internal(self, timeout): rx_msg = Message(timestamp=timestamp, arbitration_id=theMsg.ID, - extended_id=bIsExt, + is_extended_id=bIsExt, is_remote_frame=bIsRTR, dlc=dlc, data=theMsg.DATA[:dlc]) diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index d90107414..afa545734 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -119,7 +119,7 @@ def _recv_internal(self, timeout): Received message and False (because not filtering as taken place). .. warning:: - Flags like extended_id, is_remote_frame and is_error_frame + Flags like is_extended_id, is_remote_frame and is_error_frame will not be set over this function, the flags in the return message are the default values. diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 24a174867..4b7850f2f 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -422,7 +422,7 @@ def capture_message(sock, get_channel=False): msg = Message(timestamp=timestamp, channel=channel, arbitration_id=arbitration_id, - extended_id=is_extended_frame_format, + is_extended_id=is_extended_frame_format, is_remote_frame=is_remote_transmission_request, is_error_frame=is_error_frame, is_fd=is_fd, diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index fee9e14ab..b4eca1d10 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -65,7 +65,7 @@ def message_convert_rx(messagerx): msgrx = Message(timestamp=messagerx.timestamp, is_remote_frame=REMOTE_FRAME, - extended_id=ID_TYPE, + is_extended_id=ID_TYPE, is_error_frame=ERROR_FRAME, arbitration_id=messagerx.id, dlc=messagerx.sizeData, diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 7154d5d38..60e423276 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -265,7 +265,7 @@ def _recv_internal(self, timeout): msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, - extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), + 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), @@ -292,7 +292,7 @@ def _recv_internal(self, timeout): msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, - extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), + is_extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), is_remote_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME), is_error_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_ERROR_FRAME), is_fd=False, diff --git a/can/io/asc.py b/can/io/asc.py index 3feb6755c..1323130fb 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -85,7 +85,7 @@ def __iter__(self): can_id_num, is_extended_id = self._extract_can_id(can_id_str) msg = Message(timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, - extended_id=is_extended_id, + is_extended_id=is_extended_id, is_remote_frame=True, channel=channel) yield msg @@ -111,7 +111,7 @@ def __iter__(self): yield Message( timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, - extended_id=is_extended_id, + is_extended_id=is_extended_id, is_remote_frame=False, dlc=dlc, data=frame, diff --git a/can/io/blf.py b/can/io/blf.py index df8f611b0..059da5ec9 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -215,7 +215,7 @@ def __iter__(self): can_data) = CAN_MSG_STRUCT.unpack_from(data, pos) msg = Message(timestamp=timestamp, arbitration_id=can_id & 0x1FFFFFFF, - extended_id=bool(can_id & CAN_MSG_EXT), + is_extended_id=bool(can_id & CAN_MSG_EXT), is_remote_frame=bool(flags & REMOTE_FLAG), dlc=dlc, data=can_data[:dlc], @@ -227,7 +227,7 @@ def __iter__(self): length = dlc2len(dlc) msg = Message(timestamp=timestamp, arbitration_id=can_id & 0x1FFFFFFF, - extended_id=bool(can_id & CAN_MSG_EXT), + is_extended_id=bool(can_id & CAN_MSG_EXT), is_remote_frame=bool(flags & REMOTE_FLAG), is_fd=bool(fd_flags & EDL), bitrate_switch=bool(fd_flags & BRS), @@ -241,7 +241,7 @@ def __iter__(self): can_data) = CAN_ERROR_EXT_STRUCT.unpack_from(data, pos) msg = Message(timestamp=timestamp, is_error_frame=True, - extended_id=bool(can_id & CAN_MSG_EXT), + is_extended_id=bool(can_id & CAN_MSG_EXT), arbitration_id=can_id & 0x1FFFFFFF, dlc=dlc, data=can_data[:dlc], diff --git a/can/io/canutils.py b/can/io/canutils.py index 40b9ec2b6..69c0227a4 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -81,7 +81,7 @@ def __iter__(self): msg = Message(timestamp=timestamp, is_error_frame=True) else: msg = Message(timestamp=timestamp, arbitration_id=canId & 0x1FFFFFFF, - extended_id=isExtended, is_remote_frame=isRemoteFrame, + is_extended_id=isExtended, is_remote_frame=isRemoteFrame, dlc=dlc, data=dataBin, channel=channel) yield msg diff --git a/can/io/csv.py b/can/io/csv.py index 92b6fb921..6648593b3 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -99,7 +99,7 @@ def __iter__(self): yield Message( timestamp=float(timestamp), is_remote_frame=(remote == '1'), - extended_id=(extended == '1'), + is_extended_id=(extended == '1'), is_error_frame=(error == '1'), arbitration_id=int(arbitration_id, base=16), dlc=int(dlc), diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 3da3cefe5..ce4967045 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -64,7 +64,7 @@ def _assemble_message(frame_data): return Message( timestamp=timestamp, is_remote_frame=bool(is_remote), - extended_id=bool(is_extended), + is_extended_id=bool(is_extended), is_error_frame=bool(is_error), arbitration_id=can_id, dlc=dlc, diff --git a/can/message.py b/can/message.py index 97346b366..fd2c35947 100644 --- a/can/message.py +++ b/can/message.py @@ -82,7 +82,7 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, is_remote_frame=False, is_error_frame=False, channel=None, dlc=None, data=None, is_fd=False, bitrate_switch=False, error_state_indicator=False, - extended_id=True, + extended_id=True, # deprecated in 3.x, removed in 4.x check=False): """ To create a message object, simply provide any of the below attributes @@ -221,7 +221,7 @@ def __copy__(self): new = Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, - extended_id=self.is_extended_id, + is_extended_id=self.is_extended_id, is_remote_frame=self.is_remote_frame, is_error_frame=self.is_error_frame, channel=self.channel, @@ -238,7 +238,7 @@ def __deepcopy__(self, memo): new = Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, - extended_id=self.is_extended_id, + is_extended_id=self.is_extended_id, is_remote_frame=self.is_remote_frame, is_error_frame=self.is_error_frame, channel=deepcopy(self.channel, memo), diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 7c4f89876..064356fef 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -140,7 +140,7 @@ To spam a bus: """:param id: Spam the bus with messages including the data id.""" bus = can.interface.Bus(channel=channel, bustype=bustype) for i in range(10): - msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], extended_id=False) + msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], is_extended_id=False) bus.send(msg) # Issue #3: Need to keep running to ensure the writing threads stay alive. ? time.sleep(1) diff --git a/doc/message.rst b/doc/message.rst index fcb5e5e1a..9014e52e3 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -43,7 +43,7 @@ Message (either 2\ :sup:`11` - 1 for 11-bit IDs, or 2\ :sup:`29` - 1 for 29-bit identifiers). - >>> print(Message(extended_id=False, arbitration_id=100)) + >>> print(Message(is_extended_id=False, arbitration_id=100)) Timestamp: 0.000000 ID: 0064 S DLC: 0 @@ -105,9 +105,9 @@ Message This flag controls the size of the :attr:`~can.Message.arbitration_id` field. Previously this was exposed as `id_type`. - >>> print(Message(extended_id=False)) + >>> print(Message(is_extended_id=False)) Timestamp: 0.000000 ID: 0000 S DLC: 0 - >>> print(Message(extended_id=True)) + >>> print(Message(is_extended_id=True)) Timestamp: 0.000000 ID: 00000000 X DLC: 0 diff --git a/examples/cyclic.py b/examples/cyclic.py index 508440041..021af14bf 100755 --- a/examples/cyclic.py +++ b/examples/cyclic.py @@ -26,7 +26,7 @@ def simple_periodic_send(bus): Sleeps for 2 seconds then stops the task. """ print("Starting to send a message every 200ms for 2s") - msg = can.Message(arbitration_id=0x123, data=[1, 2, 3, 4, 5, 6], extended_id=False) + msg = can.Message(arbitration_id=0x123, data=[1, 2, 3, 4, 5, 6], is_extended_id=False) task = bus.send_periodic(msg, 0.20) assert isinstance(task, can.CyclicSendTaskABC) time.sleep(2) @@ -36,7 +36,7 @@ def simple_periodic_send(bus): def limited_periodic_send(bus): print("Starting to send a message every 200ms for 1s") - msg = can.Message(arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], extended_id=True) + msg = can.Message(arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], is_extended_id=True) task = bus.send_periodic(msg, 0.20, 1, store_task=False) if not isinstance(task, can.LimitedDurationCyclicSendTaskABC): print("This interface doesn't seem to support a ") @@ -107,7 +107,7 @@ def test_periodic_send_with_modifying_data(bus): if __name__ == "__main__": - reset_msg = can.Message(arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], extended_id=False) + reset_msg = can.Message(arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False) for interface, channel in [ ('socketcan', 'vcan0'), diff --git a/examples/send_one.py b/examples/send_one.py index ebf0d1790..67fddf437 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -24,7 +24,7 @@ def send_one(): msg = can.Message(arbitration_id=0xc0ffee, data=[0, 25, 0, 1, 3, 1, 4, 1], - extended_id=True) + is_extended_id=True) try: bus.send(msg) diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index 86ee7f5ed..cf7e1f8e3 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -17,8 +17,8 @@ can_filters = [{"can_id": 1, "can_mask": 0xf, "extended": True}] bus.set_filters(can_filters) notifier = can.Notifier(bus, [can.Printer()]) - bus.send(can.Message(arbitration_id=1, extended_id=True)) - bus.send(can.Message(arbitration_id=2, extended_id=True)) - bus.send(can.Message(arbitration_id=1, extended_id=False)) + bus.send(can.Message(arbitration_id=1, is_extended_id=True)) + bus.send(can.Message(arbitration_id=2, is_extended_id=True)) + bus.send(can.Message(arbitration_id=1, is_extended_id=False)) time.sleep(10) diff --git a/test/back2back_test.py b/test/back2back_test.py index 25629bc0e..522225d11 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -93,26 +93,26 @@ def test_timestamp(self): 'But measured {}'.format(delta_time)) def test_standard_message(self): - msg = can.Message(extended_id=False, + msg = can.Message(is_extended_id=False, arbitration_id=0x100, data=[1, 2, 3, 4, 5, 6, 7, 8]) self._send_and_receive(msg) def test_extended_message(self): - msg = can.Message(extended_id=True, + msg = can.Message(is_extended_id=True, arbitration_id=0x123456, data=[10, 11, 12, 13, 14, 15, 16, 17]) self._send_and_receive(msg) def test_remote_message(self): - msg = can.Message(extended_id=False, + msg = can.Message(is_extended_id=False, arbitration_id=0x200, is_remote_frame=True, dlc=4) self._send_and_receive(msg) def test_dlc_less_than_eight(self): - msg = can.Message(extended_id=False, + msg = can.Message(is_extended_id=False, arbitration_id=0x300, data=[4, 5, 6]) self._send_and_receive(msg) @@ -120,7 +120,7 @@ def test_dlc_less_than_eight(self): @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message(self): msg = can.Message(is_fd=True, - extended_id=True, + is_extended_id=True, arbitration_id=0x56789, data=[0xff] * 64) self._send_and_receive(msg) @@ -129,7 +129,7 @@ def test_fd_message(self): def test_fd_message_with_brs(self): msg = can.Message(is_fd=True, bitrate_switch=True, - extended_id=True, + is_extended_id=True, arbitration_id=0x98765, data=[0xff] * 48) self._send_and_receive(msg) @@ -190,7 +190,7 @@ def test_concurrent_writes(self): message = can.Message( arbitration_id=0x123, - extended_id=True, + is_extended_id=True, timestamp=121334.365, data=[254, 255, 1, 2] ) diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py index ea9321502..35bc045da 100644 --- a/test/contextmanager_test.py +++ b/test/contextmanager_test.py @@ -13,7 +13,7 @@ class ContextManagerTest(unittest.TestCase): def setUp(self): data = [0, 1, 2, 3, 4, 5, 6, 7] - self.msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=data) + self.msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data) def test_open_buses(self): with can.Bus(interface='virtual') as bus_send, can.Bus(interface='virtual') as bus_recv: diff --git a/test/data/example_data.py b/test/data/example_data.py index d4b1b877c..1ed5466b6 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -40,11 +40,11 @@ def sort_messages(messages): ), Message( # no data - arbitration_id=0xAB, extended_id=False + arbitration_id=0xAB, is_extended_id=False ), Message( # no data - arbitration_id=0x42, extended_id=True + arbitration_id=0x42, is_extended_id=True ), Message( # no data @@ -75,27 +75,27 @@ def sort_messages(messages): channel="awesome_channel", ), Message( - arbitration_id=0xABCDEF, extended_id=True, + arbitration_id=0xABCDEF, is_extended_id=True, timestamp=TEST_TIME, data=[1, 2, 3, 4, 5, 6, 7, 8] ), Message( - arbitration_id=0x123, extended_id=False, + arbitration_id=0x123, is_extended_id=False, timestamp=TEST_TIME + 42.42, data=[0xff, 0xff] ), Message( - arbitration_id=0xDADADA, extended_id=True, + arbitration_id=0xDADADA, is_extended_id=True, timestamp=TEST_TIME + .165, data=[1, 2, 3, 4, 5, 6, 7, 8] ), Message( - arbitration_id=0x123, extended_id=False, + arbitration_id=0x123, is_extended_id=False, timestamp=TEST_TIME + .365, data=[254, 255] ), Message( - arbitration_id=0x768, extended_id=False, + arbitration_id=0x768, is_extended_id=False, timestamp=TEST_TIME + 3.165 ), ] @@ -104,21 +104,21 @@ def sort_messages(messages): TEST_MESSAGES_REMOTE_FRAMES = [ Message( - arbitration_id=0xDADADA, extended_id=True, is_remote_frame=True, + arbitration_id=0xDADADA, is_extended_id=True, is_remote_frame=True, timestamp=TEST_TIME + .165, data=[1, 2, 3, 4, 5, 6, 7, 8] ), Message( - arbitration_id=0x123, extended_id=False, is_remote_frame=True, + arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, timestamp=TEST_TIME + .365, data=[254, 255] ), Message( - arbitration_id=0x768, extended_id=False, is_remote_frame=True, + arbitration_id=0x768, is_extended_id=False, is_remote_frame=True, timestamp=TEST_TIME + 3.165 ), Message( - arbitration_id=0xABCDEF, extended_id=True, is_remote_frame=True, + arbitration_id=0xABCDEF, is_extended_id=True, is_remote_frame=True, timestamp=TEST_TIME + 7858.67 ), ] @@ -164,4 +164,4 @@ def generate_message(arbitration_id): and a non-extended ID. """ data = bytearray([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) - return Message(arbitration_id=arbitration_id, data=data, extended_id=False, timestamp=TEST_TIME) + return Message(arbitration_id=arbitration_id, data=data, is_extended_id=False, timestamp=TEST_TIME) diff --git a/test/logformats_test.py b/test/logformats_test.py index 039b3f768..76c9cc426 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -352,12 +352,12 @@ def test_read_known_file(self): expected = [ can.Message( timestamp=1.0, - extended_id=False, + is_extended_id=False, arbitration_id=0x64, data=[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]), can.Message( timestamp=73.0, - extended_id=True, + is_extended_id=True, arbitration_id=0x1FFFFFFF, is_error_frame=True,) ] diff --git a/test/network_test.py b/test/network_test.py index cf5acca76..f4163329d 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -55,7 +55,7 @@ def producer(self, ready_event, msg_read): arbitration_id=self.ids[i], is_remote_frame=self.remote_flags[i], is_error_frame=self.error_flags[i], - extended_id=self.extended_flags[i], + is_extended_id=self.extended_flags[i], data=self.data[i] ) #logging.debug("writing message: {}".format(m)) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 70a017937..40a2e8685 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -24,7 +24,7 @@ def __init__(self, *args, **kwargs): @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_cycle_time(self): - msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0,1,2,3,4,5,6,7]) + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0,1,2,3,4,5,6,7]) bus1 = can.interface.Bus(bustype='virtual') bus2 = can.interface.Bus(bustype='virtual') task = bus1.send_periodic(msg, 0.01, 1) @@ -46,7 +46,7 @@ def test_removing_bus_tasks(self): bus = can.interface.Bus(bustype='virtual') tasks = [] for task_i in range(10): - msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 1) tasks.append(task) @@ -65,7 +65,7 @@ def test_managed_tasks(self): bus = can.interface.Bus(bustype='virtual', receive_own_messages=True) tasks = [] for task_i in range(3): - msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 10, store_task=False) tasks.append(task) @@ -91,7 +91,7 @@ def test_stopping_perodic_tasks(self): bus = can.interface.Bus(bustype='virtual') tasks = [] for task_i in range(10): - msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 1) tasks.append(task) diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 173b80d48..4aa69607d 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -100,7 +100,7 @@ def test_send_extended(self): msg = can.Message( arbitration_id=0xc0ffee, data=[0, 25, 0, 1, 3, 1, 4], - extended_id=True) + is_extended_id=True) self.bus.send(msg) @@ -113,7 +113,7 @@ def test_send_standard(self): msg = can.Message( arbitration_id=0x321, data=[50, 51], - extended_id=False) + is_extended_id=False) self.bus.send(msg) @@ -130,7 +130,7 @@ def test_recv_extended(self): self.msg_in_cue = can.Message( arbitration_id=0xc0ffef, data=[1, 2, 3, 4, 5, 6, 7, 8], - extended_id=True) + is_extended_id=True) now = time.time() msg = self.bus.recv() @@ -144,7 +144,7 @@ def test_recv_standard(self): self.msg_in_cue = can.Message( arbitration_id=0x123, data=[100, 101], - extended_id=False) + is_extended_id=False) msg = self.bus.recv() self.assertEqual(msg.arbitration_id, 0x123) diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index e08e6acfd..1419a4439 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -14,8 +14,8 @@ from .data.example_data import TEST_ALL_MESSAGES -EXAMPLE_MSG = Message(arbitration_id=0x123, extended_id=True) -HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, extended_id=True) +EXAMPLE_MSG = Message(arbitration_id=0x123, is_extended_id=True) +HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, is_extended_id=True) MATCH_EXAMPLE = [{ "can_id": 0x123, diff --git a/test/test_viewer.py b/test/test_viewer.py index 47e5ef7e7..3665f157b 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -140,31 +140,31 @@ def tearDown(self): def test_send(self): # CANopen EMCY data = [1, 2, 3, 4, 5, 6, 7] # Wrong length - msg = can.Message(arbitration_id=0x080 + 1, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x080 + 1, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) data = [1, 2, 3, 4, 5, 6, 7, 8] - msg = can.Message(arbitration_id=0x080 + 1, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x080 + 1, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) # CANopen HEARTBEAT data = [0x05] # Operational - msg = can.Message(arbitration_id=0x700 + 0x7F, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x700 + 0x7F, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) # Send non-CANopen message data = [1, 2, 3, 4, 5, 6, 7, 8] - msg = can.Message(arbitration_id=0x101, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x101, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) # Send the same command, but with another data length data = [1, 2, 3, 4, 5, 6] - msg = can.Message(arbitration_id=0x101, data=data, extended_id=False) + msg = can.Message(arbitration_id=0x101, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) # Message with extended id data = [1, 2, 3, 4, 5, 6, 7, 8] - msg = can.Message(arbitration_id=0x123456, data=data, extended_id=True) + msg = can.Message(arbitration_id=0x123456, data=data, is_extended_id=True) self.can_viewer.bus.send(msg) # self.assertTupleEqual(self.can_viewer.parse_canopen_message(msg), (None, None)) diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py index 83a073dc4..77d55d678 100644 --- a/test/zero_dlc_test.py +++ b/test/zero_dlc_test.py @@ -19,7 +19,7 @@ def test_recv_non_zero_dlc(self): bus_send = can.interface.Bus(bustype='virtual') bus_recv = can.interface.Bus(bustype='virtual') data = [0, 1, 2, 3, 4, 5, 6, 7] - msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=data) + msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data) bus_send.send(msg_send) msg_recv = bus_recv.recv() @@ -38,7 +38,7 @@ def test_recv_none(self): def test_recv_zero_dlc(self): bus_send = can.interface.Bus(bustype='virtual') bus_recv = can.interface.Bus(bustype='virtual') - msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=[]) + msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=[]) bus_send.send(msg_send) msg_recv = bus_recv.recv() From fae631bf12c4579237e4a4a9a7436f73a9d9d501 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Mon, 8 Oct 2018 22:22:34 +0200 Subject: [PATCH 0019/1235] Fixed license header Fixes #450 --- can/viewer.py | 16 +++++++++++++++- test/test_viewer.py | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/can/viewer.py b/can/viewer.py index 7b323e559..283433ed8 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -1,6 +1,20 @@ # coding: utf-8 # -# Copyright (C) 2018 Kristian Sloth Lauszus. All rights reserved. +# Copyright (C) 2018 Kristian Sloth Lauszus. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # Contact information # ------------------- diff --git a/test/test_viewer.py b/test/test_viewer.py index 3665f157b..c4b7aa45f 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -1,7 +1,21 @@ #!/usr/bin/python # coding: utf-8 # -# Copyright (C) 2018 Kristian Sloth Lauszus. All rights reserved. +# Copyright (C) 2018 Kristian Sloth Lauszus. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # Contact information # ------------------- From 3e7b5e13ea99dbfe356f706b2bf7d3eeec1993d9 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Mon, 8 Oct 2018 22:36:26 +0200 Subject: [PATCH 0020/1235] Fixed help text for the "--filter" argument, as the filters should be separated by spaces --- can/viewer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/viewer.py b/can/viewer.py index 283433ed8..660dce62c 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -384,11 +384,11 @@ def parse_args(args): metavar='{:,:::...:,file.txt}', nargs=argparse.ONE_OR_MORE, default='') - optional.add_argument('-f', '--filter', help='R|Comma separated CAN filters for the given CAN interface:' + optional.add_argument('-f', '--filter', help='R|Space separated CAN filters for the given CAN interface:' '\n : (matches when & mask == can_id & mask)' '\n ~ (matches when & mask != can_id & mask)' - '\nFx to show only frames with ID 0x100 to 0x103:' - '\n python -m can.viewer -f 100:7FC' + '\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:' + '\n python -m can.viewer -f 100:7FC 200:7F0' '\nNote that the ID and mask are alway interpreted as hex values', metavar='{:,~}', nargs=argparse.ONE_OR_MORE, default='') From 70ac964db8a25aa198264babb03745206fe5b318 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 12 Oct 2018 13:03:14 -0400 Subject: [PATCH 0021/1235] Fix doc to reflect the correct argument `context` --- doc/configuration.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index 9297ab15c..dda2ace2a 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -57,7 +57,7 @@ The configuration file sets the default interface and channel: bitrate = -The configuration can also contain additional sections: +The configuration can also contain additional sections (or context): :: @@ -81,8 +81,8 @@ The configuration can also contain additional sections: from can.interfaces.interface import Bus - hs_bus = Bus(config_section='HS') - ms_bus = Bus(config_section='MS') + hs_bus = Bus(context='HS') + ms_bus = Bus(context='MS') Environment Variables --------------------- From 66ce24e9f7659dc5974f8bfc58c436878c915158 Mon Sep 17 00:00:00 2001 From: BOUAN-DU-CHEF-DU-BOS Ludovic Date: Tue, 30 Oct 2018 22:44:13 +0100 Subject: [PATCH 0022/1235] Changed asc log timestamps to 6 decimal places and uses real message timestamp --- can/io/asc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 3feb6755c..dadef38af 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -132,7 +132,7 @@ class ASCWriter(BaseIOHandler, Listener): FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" FORMAT_DATE = "%a %b %m %I:%M:%S %p %Y" - FORMAT_EVENT = "{timestamp: 9.4f} {message}\n" + FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" def __init__(self, file, channel=1): """ @@ -181,8 +181,8 @@ def log_event(self, message, timestamp=None): self.header_written = True self.log_event("Start of measurement") # caution: this is a recursive call! - # figure out the correct timestamp - if timestamp is None or timestamp < self.last_timestamp: + # Use last known timestamp if unknown + if timestamp is None: timestamp = self.last_timestamp # turn into relative timestamps if necessary From 00273846085485bd642397bc5577b4090aa610d4 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Mon, 29 Oct 2018 14:27:33 +0100 Subject: [PATCH 0023/1235] fix rendering of BusABC docstring Sphinx needs a newline in order to properly render the docstring --- can/bus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/bus.py b/can/bus.py index eb666a6ab..3b7f6c1a1 100644 --- a/can/bus.py +++ b/can/bus.py @@ -368,6 +368,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): def state(self): """ Return the current state of the hardware + :return: ACTIVE, PASSIVE or ERROR :rtype: NamedTuple """ @@ -377,6 +378,7 @@ def state(self): def state(self, new_state): """ Set the new state of the hardware + :param new_state: BusState.ACTIVE, BusState.PASSIVE or BusState.ERROR """ raise NotImplementedError("Property is not implemented.") From 9f6f9188ff4ad78b0162925781d24625ca65ac50 Mon Sep 17 00:00:00 2001 From: Ben Powell Date: Fri, 9 Nov 2018 11:09:33 +1300 Subject: [PATCH 0024/1235] Fix Kvaser CANLIB CAN FD data rate setting --- can/interfaces/kvaser/canlib.py | 2 +- test/test_kvaser.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 1e5c2a49f..8b005602e 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -396,7 +396,7 @@ def __init__(self, channel, can_filters=None, **config): elif not data_bitrate: # Use same bitrate for arbitration and data phase data_bitrate = bitrate - canSetBusParamsFd(self._read_handle, bitrate, tseg1, tseg2, sjw) + canSetBusParamsFd(self._read_handle, data_bitrate, tseg1, tseg2, sjw) else: if 'tseg1' not in config and bitrate in BITRATE_OBJS: bitrate = BITRATE_OBJS[bitrate] diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 4aa69607d..d8a22a0ae 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -28,6 +28,7 @@ def setUp(self): canlib.canIoCtl = Mock(return_value=0) canlib.kvReadTimer = Mock() canlib.canSetBusParams = Mock() + canlib.canSetBusParamsFd = Mock() canlib.canBusOn = Mock() canlib.canBusOff = Mock() canlib.canClose = Mock() @@ -160,6 +161,36 @@ def test_available_configs(self): ] self.assertListEqual(configs, expected) + def test_canfd_default_data_bitrate(self): + canlib.canSetBusParams.reset_mock() + canlib.canSetBusParamsFd.reset_mock() + can.Bus(channel=0, bustype='kvaser', fd=True) + canlib.canSetBusParams.assert_called_once_with( + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) + canlib.canSetBusParamsFd.assert_called_once_with( + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0) + + def test_canfd_nondefault_data_bitrate(self): + canlib.canSetBusParams.reset_mock() + canlib.canSetBusParamsFd.reset_mock() + data_bitrate = 2000000 + can.Bus(channel=0, bustype='kvaser', fd=True, data_bitrate=data_bitrate) + bitrate_constant = canlib.BITRATE_FD[data_bitrate] + canlib.canSetBusParams.assert_called_once_with( + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) + canlib.canSetBusParamsFd.assert_called_once_with( + 0, bitrate_constant, 0, 0, 0) + + def test_canfd_custom_data_bitrate(self): + canlib.canSetBusParams.reset_mock() + canlib.canSetBusParamsFd.reset_mock() + data_bitrate = 123456 + can.Bus(channel=0, bustype='kvaser', fd=True, data_bitrate=data_bitrate) + canlib.canSetBusParams.assert_called_once_with( + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) + canlib.canSetBusParamsFd.assert_called_once_with( + 0, data_bitrate, 0, 0, 0) + @staticmethod def canGetNumberOfChannels(count): count._obj.value = 2 From 43763ed294617612921fb7fe8826889584c5a5a5 Mon Sep 17 00:00:00 2001 From: Matthieu BRIEDA Date: Fri, 14 Dec 2018 16:47:23 +0100 Subject: [PATCH 0025/1235] Import name deepcopy for __deepcopy__ method --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index fd2c35947..db34643ed 100644 --- a/can/message.py +++ b/can/message.py @@ -11,7 +11,7 @@ from __future__ import absolute_import, division import warnings - +from copy import deepcopy class Message(object): """ From 3a092bc94c3f07ec73d355566131356d66ecbdc9 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 18 Dec 2018 05:57:21 +0100 Subject: [PATCH 0026/1235] Fix slcan message handling (#462) Fixes #444 --- can/interfaces/slcan.py | 95 +++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 009d45814..7b276a078 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -112,53 +112,56 @@ def _recv_internal(self, timeout): extended = False frame = [] - # Read everything that is already available - waiting = self.serialPortOrig.read(self.serialPortOrig.in_waiting) - self._buffer += waiting - - # Check if a complete message has been received - pos = self._buffer.find(self.LINE_TERMINATOR) - if pos == -1: - # Keep reading... + # First read what is already in the receive buffer + while (self.serialPortOrig.in_waiting and + self.LINE_TERMINATOR not in self._buffer): + self._buffer += self.serialPortOrig.read(1) + + # If we still don't have a complete message, do a blocking read + if self.LINE_TERMINATOR not in self._buffer: self._buffer += self.serialPortOrig.read_until(self.LINE_TERMINATOR) - pos = self._buffer.find(self.LINE_TERMINATOR) - - if pos != -1: - readStr = self._buffer[0:pos].decode() - del self._buffer[0:pos+1] - if readStr[0] == 'T': - # extended frame - canId = int(readStr[1:9], 16) - dlc = int(readStr[9]) - extended = True - for i in range(0, dlc): - frame.append(int(readStr[10 + i * 2:12 + i * 2], 16)) - elif readStr[0] == 't': - # normal frame - canId = int(readStr[1:4], 16) - dlc = int(readStr[4]) - for i in range(0, dlc): - frame.append(int(readStr[5 + i * 2:7 + i * 2], 16)) - elif readStr[0] == 'r': - # remote frame - canId = int(readStr[1:4], 16) - dlc = int(readStr[4]) - remote = True - elif readStr[0] == 'R': - # remote extended frame - canId = int(readStr[1:9], 16) - dlc = int(readStr[9]) - extended = True - remote = True - - if canId is not None: - msg = Message(arbitration_id=canId, - is_extended_id=extended, - timestamp=time.time(), # Better than nothing... - is_remote_frame=remote, - dlc=dlc, - data=frame) - return msg, False + + if self.LINE_TERMINATOR not in self._buffer: + # Timed out + return None, False + + readStr = self._buffer.decode() + del self._buffer[:] + if not readStr: + pass + elif readStr[0] == 'T': + # extended frame + canId = int(readStr[1:9], 16) + dlc = int(readStr[9]) + extended = True + for i in range(0, dlc): + frame.append(int(readStr[10 + i * 2:12 + i * 2], 16)) + elif readStr[0] == 't': + # normal frame + canId = int(readStr[1:4], 16) + dlc = int(readStr[4]) + for i in range(0, dlc): + frame.append(int(readStr[5 + i * 2:7 + i * 2], 16)) + elif readStr[0] == 'r': + # remote frame + canId = int(readStr[1:4], 16) + dlc = int(readStr[4]) + remote = True + elif readStr[0] == 'R': + # remote extended frame + canId = int(readStr[1:9], 16) + dlc = int(readStr[9]) + extended = True + remote = True + + if canId is not None: + msg = Message(arbitration_id=canId, + is_extended_id=extended, + timestamp=time.time(), # Better than nothing... + is_remote_frame=remote, + dlc=dlc, + data=frame) + return msg, False return None, False def send(self, msg, timeout=None): From 6c5955ee17950c7cdae6333ccdb33d744baa34cb Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 23 Dec 2018 22:08:31 +1100 Subject: [PATCH 0027/1235] Update argument name in thread safe bus set_filters method. Closes #478 --- can/thread_safe_bus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index c7d458366..d82ac6bd6 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -87,9 +87,9 @@ def filters(self, filters): with self._lock_recv: self.__wrapped__.filters = filters - def set_filters(self, can_filters=None, *args, **kwargs): + def set_filters(self, filters=None, *args, **kwargs): with self._lock_recv: - return self.__wrapped__.set_filters(can_filters=can_filters, *args, **kwargs) + return self.__wrapped__.set_filters(filters=filters, *args, **kwargs) def flush_tx_buffer(self, *args, **kwargs): with self._lock_send: From 9453cd0c8fbe3e68a239279d21e0a63d3624fc92 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 23 Dec 2018 23:32:47 +1100 Subject: [PATCH 0028/1235] Extend the thread safe bus tests --- test/back2back_test.py | 57 +++++++++++++++++++++++++++++++++++++----- test/test_scripts.py | 2 +- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 522225d11..800ffce9e 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -13,6 +13,7 @@ from multiprocessing.dummy import Pool as ThreadPool import pytest +import random import can @@ -168,8 +169,6 @@ def test_broadcast_channel(self): class TestThreadSafeBus(Back2BackTestCase): - """Does some testing that is better than nothing. - """ def setUp(self): self.bus1 = can.ThreadSafeBus(channel=self.CHANNEL_1, @@ -190,6 +189,7 @@ def test_concurrent_writes(self): message = can.Message( arbitration_id=0x123, + channel=self.CHANNEL_1, is_extended_id=True, timestamp=121334.365, data=[254, 255, 1, 2] @@ -200,12 +200,57 @@ def sender(msg): self.bus1.send(msg) def receiver(_): - result = self.bus2.recv(timeout=2.0) - self.assertIsNotNone(result) - self.assertEqual(result, message) + return self.bus2.recv(timeout=2.0) sender_pool.map_async(sender, workload) - receiver_pool.map_async(receiver, len(workload) * [None]) + for msg in receiver_pool.map(receiver, len(workload) * [None]): + self.assertIsNotNone(msg) + self.assertEqual(message.arbitration_id, msg.arbitration_id) + self.assertTrue(message.equals(msg, timestamp_delta=None)) + + sender_pool.close() + sender_pool.join() + receiver_pool.close() + receiver_pool.join() + + @pytest.mark.timeout(5.0) + def test_filtered_bus(self): + sender_pool = ThreadPool(100) + receiver_pool = ThreadPool(100) + + included_message = can.Message( + arbitration_id=0x123, + channel=self.CHANNEL_1, + is_extended_id=True, + timestamp=121334.365, + data=[254, 255, 1, 2] + ) + excluded_message = can.Message( + arbitration_id=0x02, + channel=self.CHANNEL_1, + is_extended_id=True, + timestamp=121334.300, + data=[1, 2, 3] + ) + workload = 500 * [included_message] + 500 * [excluded_message] + random.shuffle(workload) + + self.bus2.set_filters([{"can_id": 0x123, "can_mask": 0xff, "extended": True}]) + + def sender(msg): + self.bus1.send(msg) + + def receiver(_): + return self.bus2.recv(timeout=2.0) + + sender_pool.map_async(sender, workload) + received_msgs = receiver_pool.map(receiver, 500 * [None]) + + for msg in received_msgs: + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, included_message.arbitration_id) + self.assertTrue(included_message.equals(msg, timestamp_delta=None)) + self.assertEqual(len(received_msgs), 500) sender_pool.close() sender_pool.join() diff --git a/test/test_scripts.py b/test/test_scripts.py index 90687ccd7..da3ba1d6c 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -29,7 +29,7 @@ def setUpClass(cls): __metaclass__ = ABCMeta def test_do_commands_exist(self): - """This test calls each scripts once and veifies that the help + """This test calls each scripts once and verifies that the help can be read without any other errors, like the script not being found. """ From 093509a8e8601778ae990ed5f8cc69844ae68d2d Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 24 Dec 2018 09:52:33 +1000 Subject: [PATCH 0029/1235] Improved listener docs and docstrings (#484) --- can/io/csv.py | 7 ++++--- can/io/logger.py | 2 ++ can/io/sqlite.py | 2 +- can/listener.py | 7 ++++++- can/notifier.py | 11 +++++++++-- doc/asyncio.rst | 5 +++-- doc/listeners.rst | 2 +- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/can/io/csv.py b/can/io/csv.py index 6648593b3..029a2c373 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -19,6 +19,7 @@ from can.listener import Listener from .generic import BaseIOHandler + class CSVWriter(BaseIOHandler, Listener): """Writes a comma separated text file with a line for each message. Includes a header line. @@ -37,13 +38,13 @@ class CSVWriter(BaseIOHandler, Listener): data base64 encoded WzQyLCA5XQ== ================ ======================= =============== - Each line is terminated with a platform specific line seperator. + Each line is terminated with a platform specific line separator. """ def __init__(self, file, append=False): """ - :param file: a path-like object or as file-like object to write to - If this is a file-like object, is has to opened in text + :param file: a path-like object or a file-like object to write to. + If this is a file-like object, is has to open in text write mode, not binary write mode. :param bool append: if set to `True` messages are appended to the file and no header line is written, else diff --git a/can/io/logger.py b/can/io/logger.py index cc11579c2..52d2e8d83 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -32,6 +32,8 @@ class Logger(BaseIOHandler, Listener): * .log :class:`can.CanutilsLogWriter` * other: :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 arguments are passed on to the returned instance. diff --git a/can/io/sqlite.py b/can/io/sqlite.py index ce4967045..a12023a6f 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -228,7 +228,7 @@ def _db_writer_thread(self): def stop(self): """Stops the reader an writes all remaining messages to the database. Thus, this - might take a while an block. + might take a while and block. """ BufferedReader.stop(self) self._stop_running_event.set() diff --git a/can/listener.py b/can/listener.py index 6ed04b64b..a91b1dac1 100644 --- a/can/listener.py +++ b/can/listener.py @@ -35,6 +35,8 @@ class Listener(object): # or listener.on_message_received(msg) + # Important to ensure all outputs are flushed + listener.stop() """ __metaclass__ = ABCMeta @@ -59,7 +61,10 @@ def on_error(self, exc): def stop(self): """ - Override to cleanup any open resources. + Stop handling new messages, carry out any final tasks to ensure + data is persisted and cleanup any open resources. + + Concrete implementations override. """ diff --git a/can/notifier.py b/can/notifier.py index 45df675dd..7c5f2820d 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -18,8 +18,15 @@ class Notifier(object): def __init__(self, bus, listeners, timeout=1.0, loop=None): - """Manages the distribution of **Messages** from a given bus/buses to a - list of listeners. + """Manages the distribution of :class:`can.Message` instances to listeners. + + Supports multiple busses and listeners. + + .. Note:: + + Remember to call `stop()` after all messages are received as + 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` diff --git a/doc/asyncio.rst b/doc/asyncio.rst index f5bd7771b..cd8d65de5 100644 --- a/doc/asyncio.rst +++ b/doc/asyncio.rst @@ -4,8 +4,9 @@ Asyncio support =============== The :mod:`asyncio` module built into Python 3.4 and later can be used to write -asynchronos code in a single thread. This library supports receiving messages -asynchronosly in an event loop using the :class:`can.Notifier` class. +asynchronous code in a single thread. This library supports receiving messages +asynchronously in an event loop using the :class:`can.Notifier` class. + There will still be one thread per CAN bus but the user application will execute entirely in the event loop, allowing simpler concurrency without worrying about threading issues. Interfaces that have a valid file descriptor will however be diff --git a/doc/listeners.rst b/doc/listeners.rst index 3434f2b3e..975de6fd1 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -24,7 +24,7 @@ and are listed below. Some of them allow messages to be written to files, and the corresponding file readers are also documented here. -.. warning :: +.. note :: Please note that writing and the reading a message might not always yield a completely unchanged message again, since some properties are not (yet) From 5d19dbac6389735a9aab3b6526955afbeae98434 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Tue, 18 Dec 2018 10:32:51 +0100 Subject: [PATCH 0030/1235] Fixed help text for the scaling in the viewer --- can/viewer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/viewer.py b/can/viewer.py index 660dce62c..80201348d 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -366,8 +366,8 @@ def parse_args(args): '\nNote that the IDs are always interpreted as hex values.' '\nAn optional conversion from integers to real units can be given' '\nas additional arguments. In order to convert from raw integer' - '\nvalues the values are multiplied with the corresponding scaling value,' - '\nsimilarly the values are divided by the scaling value in order' + '\nvalues the values are divided with the corresponding scaling value,' + '\nsimilarly the values are multiplied by the scaling value in order' '\nto convert from real units to raw integer values.' '\nFx lets say the uint8_t needs no conversion, but the uint16 and the uint32_t' '\nneeds to be divided by 10 and 100 respectively:' From 6ab3d8ded477221f0f4be29bd649c523faf67ba1 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Tue, 18 Dec 2018 10:55:17 +0100 Subject: [PATCH 0031/1235] Create separate structs for the Mac PCAN driver Fixes #460 --- can/interfaces/pcan/basic.py | 42 +++++++++++++++++++++++++++++++++--- can/interfaces/pcan/pcan.py | 5 ++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 322c6bbd4..053197119 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -288,6 +288,16 @@ class TPCANMsg (Structure): + """ + Represents a PCAN message + """ + _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier + ("MSGTYPE", TPCANMessageType), # Type of the message + ("LEN", c_ubyte), # Data Length Code of the message (0..8) + ("DATA", c_ubyte * 8) ] # Data of the message (DATA[0]..DATA[7]) + + +class TPCANMsgMac (Structure): """ Represents a PCAN message """ @@ -298,6 +308,16 @@ class TPCANMsg (Structure): class TPCANTimestamp (Structure): + """ + Represents a timestamp of a received PCAN message + Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow + """ + _fields_ = [ ("millis", c_uint), # Base-value: milliseconds: 0.. 2^32-1 + ("millis_overflow", c_ushort), # Roll-arounds of millis + ("micros", c_ushort) ] # Microseconds: 0..999 + + +class TPCANTimestampMac (Structure): """ Represents a timestamp of a received PCAN message Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow @@ -308,6 +328,15 @@ class TPCANTimestamp (Structure): class TPCANMsgFD (Structure): + """ + Represents a PCAN message + """ + _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier + ("MSGTYPE", TPCANMessageType), # Type of the message + ("DLC", c_ubyte), # Data Length Code of the message (0..15) + ("DATA", c_ubyte * 64) ] # Data of the message (DATA[0]..DATA[63]) + +class TPCANMsgFDMac (Structure): """ Represents a PCAN message """ @@ -484,8 +513,12 @@ def Read( A touple with three values """ try: - msg = TPCANMsg() - timestamp = TPCANTimestamp() + if platform.system() == 'Darwin': + msg = TPCANMsgMac() + timestamp = TPCANTimestampMac() + else: + msg = TPCANMsg() + timestamp = TPCANTimestamp() res = self.__m_dllBasic.CAN_Read(Channel,byref(msg),byref(timestamp)) return TPCANStatus(res),msg,timestamp except: @@ -514,7 +547,10 @@ def ReadFD( A touple with three values """ try: - msg = TPCANMsgFD() + if platform.system() == 'Darwin': + msg = TPCANMsgFDMac() + else: + msg = TPCANMsgFD() timestamp = TPCANTimestampFD() res = self.__m_dllBasic.CAN_ReadFD(Channel,byref(msg),byref(timestamp)) return TPCANStatus(res),msg,timestamp diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 63b3a2a3b..09e9457b4 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -232,7 +232,10 @@ def send(self, msg, timeout=None): msgType = PCAN_MESSAGE_STANDARD # create a TPCANMsg message structure - CANMsg = TPCANMsg() + if platform.system() == 'Darwin': + CANMsg = TPCANMsgMac() + else: + CANMsg = TPCANMsg() # configure the message. ID, Length of data, message type and data CANMsg.ID = msg.arbitration_id From 90b6021e9752223666d8ca9b9be967942c8b56c5 Mon Sep 17 00:00:00 2001 From: Pontus Englund Date: Sun, 2 Dec 2018 21:35:42 +0100 Subject: [PATCH 0032/1235] Introduced structures for the kvaser driver. --- can/interfaces/kvaser/structures.py | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 can/interfaces/kvaser/structures.py diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py new file mode 100644 index 000000000..145058223 --- /dev/null +++ b/can/interfaces/kvaser/structures.py @@ -0,0 +1,34 @@ +# coding: utf-8 + +""" +Contains Python equivalents of the structures in CANLIB's canstat.h, +with some supporting functionality specific to Python. + +Copyright (C) 2010 Dynamic Controls +""" + +import ctypes + + +class BusStatistics(ctypes.Structure): + _fields_ = [ + ("stdData", ctypes.c_ulong), + ("stdRemote", ctypes.c_ulong), + ("extData", ctypes.c_ulong), + ("extRemote", ctypes.c_ulong), + ("errFrame", ctypes.c_ulong), + ("busLoad", ctypes.c_ulong), + ("overruns", ctypes.c_ulong) + ] + + def __str__(self): + return ("stdData: {}, stdRemote: {}, extData: {}, extRemote: {}, " + "errFrame: {}, busLoad: {}, overruns: {}").format( + self.stdData, + self.stdRemote, + self.extData, + self.extRemote, + self.errFrame, + self.busLoad / 100, + self.overruns, + ) From 04a11cb565443b1a689429f88d3243e629537413 Mon Sep 17 00:00:00 2001 From: Pontus Englund Date: Sun, 2 Dec 2018 21:37:31 +0100 Subject: [PATCH 0033/1235] Added support for bus statistics. --- can/interfaces/kvaser/canlib.py | 22 ++++++++++++++++++++++ can/interfaces/kvaser/structures.py | 4 ++-- test/test_kvaser.py | 8 ++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 8b005602e..4b47b55bb 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -18,6 +18,7 @@ from can import CanError, BusABC from can import Message from . import constants as canstat +from . import structures log = logging.getLogger('can.kvaser') @@ -248,6 +249,18 @@ def __check_bus_handle_validity(handle, function, arguments): restype=canstat.c_canStatus, errcheck=__check_status) + canRequestBusStatistics = __get_canlib_function("canRequestBusStatistics", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status) + + canGetBusStatistics = __get_canlib_function("canGetBusStatistics", + argtypes=[c_canHandle, + ctypes.POINTER(structures.BusStatistics), + ctypes.c_size_t], + restype=canstat.c_canStatus, + errcheck=__check_status) + def init_kvaser_library(): if __canlib is not None: @@ -572,6 +585,15 @@ def shutdown(self): canBusOff(self._write_handle) canClose(self._write_handle) + def getstats(self): + """Returns the bus statistics.""" + canRequestBusStatistics(self._write_handle) + stats = structures.BusStatistics() + canGetBusStatistics(self._write_handle, + ctypes.pointer(stats), + ctypes.sizeof(stats)) + return stats + @staticmethod def _detect_available_configs(): num_channels = ctypes.c_int(0) diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index 145058223..645fa293c 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -23,12 +23,12 @@ class BusStatistics(ctypes.Structure): def __str__(self): return ("stdData: {}, stdRemote: {}, extData: {}, extRemote: {}, " - "errFrame: {}, busLoad: {}, overruns: {}").format( + "errFrame: {}, busLoad: {:.1f}%, overruns: {}").format( self.stdData, self.stdRemote, self.extData, self.extRemote, self.errFrame, - self.busLoad / 100, + self.busLoad / 100.0, self.overruns, ) diff --git a/test/test_kvaser.py b/test/test_kvaser.py index d8a22a0ae..735232d51 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -38,6 +38,8 @@ def setUp(self): canlib.canWriteSync = Mock() canlib.canWrite = self.canWrite canlib.canReadWait = self.canReadWait + canlib.canGetBusStatistics = Mock() + canlib.canRequestBusStatistics = Mock() self.msg = {} self.msg_in_cue = None @@ -191,6 +193,12 @@ def test_canfd_custom_data_bitrate(self): canlib.canSetBusParamsFd.assert_called_once_with( 0, data_bitrate, 0, 0, 0) + def test_bus_getstats(self): + stats = self.bus.getstats() + self.assertTrue(canlib.canRequestBusStatistics.called) + self.assertTrue(canlib.canGetBusStatistics.called) + self.assertIsInstance(stats, canlib.structures.BusStatistics) + @staticmethod def canGetNumberOfChannels(count): count._obj.value = 2 From 560d1e68c457f5add040683ae74204318ade9ed1 Mon Sep 17 00:00:00 2001 From: Pontus Englund Date: Sat, 8 Dec 2018 22:45:53 +0100 Subject: [PATCH 0034/1235] Updated documentation for the new kvaser custom-method get_stats. --- can/interfaces/kvaser/canlib.py | 14 +++++- can/interfaces/kvaser/structures.py | 75 ++++++++++++++++++++++------- test/test_kvaser.py | 4 +- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 4b47b55bb..34f7f6e7c 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -585,8 +585,18 @@ def shutdown(self): canBusOff(self._write_handle) canClose(self._write_handle) - def getstats(self): - """Returns the bus statistics.""" + def get_stats(self): + """Retrieves the bus statistics. + + Use like so: + + >>> stats = bus.get_stats() + >>> print(stats) + std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0 + + :returns: bus statistics. + :rtype: can.interfaces.kvaser.structures.BusStatistics + """ canRequestBusStatistics(self._write_handle) stats = structures.BusStatistics() canGetBusStatistics(self._write_handle, diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index 645fa293c..21eefe86f 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -1,34 +1,73 @@ # coding: utf-8 """ -Contains Python equivalents of the structures in CANLIB's canstat.h, +Contains Python equivalents of the structures in CANLIB's canlib.h, with some supporting functionality specific to Python. - -Copyright (C) 2010 Dynamic Controls """ import ctypes class BusStatistics(ctypes.Structure): + """ + This structure is used with the method :meth:`KvaserBus.get_stats`. + + .. seealso:: :meth:`KvaserBus.get_stats` + + """ _fields_ = [ - ("stdData", ctypes.c_ulong), - ("stdRemote", ctypes.c_ulong), - ("extData", ctypes.c_ulong), - ("extRemote", ctypes.c_ulong), - ("errFrame", ctypes.c_ulong), - ("busLoad", ctypes.c_ulong), - ("overruns", ctypes.c_ulong) + ("m_stdData", ctypes.c_ulong), + ("m_stdRemote", ctypes.c_ulong), + ("m_extData", ctypes.c_ulong), + ("m_extRemote", ctypes.c_ulong), + ("m_errFrame", ctypes.c_ulong), + ("m_busLoad", ctypes.c_ulong), + ("m_overruns", ctypes.c_ulong) ] def __str__(self): - return ("stdData: {}, stdRemote: {}, extData: {}, extRemote: {}, " - "errFrame: {}, busLoad: {:.1f}%, overruns: {}").format( - self.stdData, - self.stdRemote, - self.extData, - self.extRemote, - self.errFrame, - self.busLoad / 100.0, + return ("std_data: {}, std_remote: {}, ext_data: {}, ext_remote: {}, " + "err_frame: {}, bus_load: {:.1f}%, overruns: {}").format( + self.std_data, + self.std_remote, + self.ext_data, + self.ext_remote, + self.err_frame, + self.bus_load / 100.0, self.overruns, ) + + @property + def std_data(self): + """Number of received standard (11-bit identifiers) data frames.""" + return self.m_stdData + + @property + def std_remote(self): + """Number of received standard (11-bit identifiers) remote frames.""" + return self.m_stdRemote + + @property + def ext_data(self): + """Number of received extended (29-bit identifiers) data frames.""" + return self.m_extData + + @property + def ext_remote(self): + """Number of received extended (29-bit identifiers) remote frames.""" + return self.m_extRemote + + @property + def err_frame(self): + """Number of error frames.""" + return self.m_errFrame + + @property + def bus_load(self): + """The bus load, expressed as an integer in the interval 0 - 10000 representing 0.00% - 100.00% bus load.""" + return self.m_busLoad + + @property + def overruns(self): + """Number of overruns.""" + return self.m_overruns diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 735232d51..3e0bcf396 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -193,8 +193,8 @@ def test_canfd_custom_data_bitrate(self): canlib.canSetBusParamsFd.assert_called_once_with( 0, data_bitrate, 0, 0, 0) - def test_bus_getstats(self): - stats = self.bus.getstats() + def test_bus_get_stats(self): + stats = self.bus.get_stats() self.assertTrue(canlib.canRequestBusStatistics.called) self.assertTrue(canlib.canGetBusStatistics.called) self.assertIsInstance(stats, canlib.structures.BusStatistics) From 69db2ec851914118084ecacc49ccc9dc1fd3cc6d Mon Sep 17 00:00:00 2001 From: Pontus Englund Date: Wed, 26 Dec 2018 17:40:45 +0100 Subject: [PATCH 0035/1235] Moved get_stats to a new documentation section called Custom Methods. --- doc/interfaces/kvaser.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/interfaces/kvaser.rst b/doc/interfaces/kvaser.rst index 289300093..a4a51ad09 100644 --- a/doc/interfaces/kvaser.rst +++ b/doc/interfaces/kvaser.rst @@ -10,6 +10,8 @@ Bus --- .. autoclass:: can.interfaces.kvaser.canlib.KvaserBus + :members: + :exclude-members: get_stats Internals @@ -35,3 +37,12 @@ If one filter is requested, this is will be handled by the Kvaser driver. If more than one filter is needed, these will be handled in Python code in the ``recv`` method. If a message does not match any of the filters, ``recv()`` will return None. + + +Custom methods +~~~~~~~~~~~~~~~~~ + +This section contains Kvaser driver specific methods. + + +.. automethod:: can.interfaces.kvaser.canlib.KvaserBus.get_stats From 35c3fbbef06d52f153de07a8b0a2e9cbdbc0d130 Mon Sep 17 00:00:00 2001 From: Shaoyu Date: Fri, 30 Nov 2018 17:10:57 -0600 Subject: [PATCH 0036/1235] Added new interface: CANalystII interface --- can/interfaces/__init__.py | 3 +- can/interfaces/canalystii.py | 110 +++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 can/interfaces/canalystii.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index e02dd2fb4..1542aa2dd 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -21,7 +21,8 @@ 'virtual': ('can.interfaces.virtual', 'VirtualBus'), 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), 'vector': ('can.interfaces.vector', 'VectorBus'), - 'slcan': ('can.interfaces.slcan', 'slcanBus') + 'slcan': ('can.interfaces.slcan', 'slcanBus'), + 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus') } BACKENDS.update({ diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py new file mode 100644 index 000000000..18fe695c0 --- /dev/null +++ b/can/interfaces/canalystii.py @@ -0,0 +1,110 @@ +from ctypes import * +import logging +import platform +from can import BusABC, Message + +logger = logging.getLogger(__name__) + + +class VCI_INIT_CONFIG(Structure): + _fields_ = [("AccCode", c_int32), + ("AccMask", c_int32), + ("Reserved", c_int32), + ("Filter", c_ubyte), + ("Timing0", c_ubyte), + ("Timing1", c_ubyte), + ("Mode", c_ubyte)] + + +class VCI_CAN_OBJ(Structure): + _fields_ = [("ID", c_uint), + ("TimeStamp", c_int), + ("TimeFlag", c_byte), + ("SendType", c_byte), + ("RemoteFlag", c_byte), + ("ExternFlag", c_byte), + ("DataLen", c_byte), + ("Data", c_ubyte * 8), + ("Reserved", c_byte * 3)] + + +VCI_USBCAN2 = 4 + +STATUS_OK = 0x01 +STATUS_ERR = 0x00 + +try: + if platform.system() == "Windows": + CANalystII = WinDLL("./ControlCAN.dll") + else: + CANalystII = CDLL("./libcontrolcan.so") + logger.info("Loaded CANalystII library") +except OSError as e: + CANalystII = None + logger.info("Cannot load CANalystII library") + + +class CANalystIIBus(BusABC): + def __init__(self, channel, can_filters=None): + super(CANalystIIBus, self).__init__(channel, can_filters) + + if isinstance(channel, (list, tuple)): + self.channels = channel + elif isinstance(channel, int): + self.channels = [channel] + else: + # Assume comma separated string of channels + self.channels = [int(ch.strip()) for ch in channel.split(',')] + + self.init_config = VCI_INIT_CONFIG(0, 0xFFFFFFFF, 0, 1, 0x00, 0x1C, 0) + + if CANalystII.VCI_OpenDevice(VCI_USBCAN2, 0, 0) == STATUS_ERR: + logger.error("VCI_OpenDevice Error") + + for channel in self.channels: + if CANalystII.VCI_InitCAN(VCI_USBCAN2, 0, channel, byref(self.init_config)) == STATUS_ERR: + logger.error("VCI_InitCAN Error") + self.shutdown() + return + + if CANalystII.VCI_StartCAN(VCI_USBCAN2, 0, channel) == STATUS_ERR: + logger.error("VCI_StartCAN Error") + self.shutdown() + return + + def send(self, msg, timeout=None): + """ + + :param msg: message to send + :param timeout: timeout is not used here + :return: + """ + raw_message = VCI_CAN_OBJ(msg.arbitration_id, 0, 0, 0, 0, 0, msg.dlc, (c_ubyte * 8)(*msg.data), (c_byte * 3)(*[0, 0, 0])) + + if msg.channel is not None: + channel = msg.channel + elif len(self.channels) == 1: + channel = self.channels[0] + else: + raise ValueError( + "msg.channel must be set when using multiple channels.") + + CANalystII.VCI_Transmit(VCI_USBCAN2, 0, channel, byref(raw_message), 1) + + def _recv_internal(self, timeout=None): + """ + + :param timeout: float in seconds + :return: + """ + raw_message = VCI_CAN_OBJ() + + timeout = -1 if timeout is None else int(timeout * 1000) + + if CANalystII.VCI_Receive(VCI_USBCAN2, 0, self.channels[0], byref(raw_message), 1, timeout) <= STATUS_ERR: + return None, False + else: + return Message(arbitration_id=raw_message.ID, channel=0, data=raw_message.Data), False + + def shutdown(self): + CANalystII.VCI_CloseDevice(VCI_USBCAN2, 0) From 8dbbf65c9f19c34c6d6d7050018188357182c26d Mon Sep 17 00:00:00 2001 From: Shaoyu Date: Mon, 17 Dec 2018 18:06:10 -0600 Subject: [PATCH 0037/1235] add baudrate option --- can/interfaces/canalystii.py | 65 ++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 18fe695c0..019917add 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -45,7 +45,7 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): - def __init__(self, channel, can_filters=None): + def __init__(self, channel, device=0, baud=10000000, Timing0=0x00, Timing1=0x14, can_filters=None): super(CANalystIIBus, self).__init__(channel, can_filters) if isinstance(channel, (list, tuple)): @@ -56,18 +56,51 @@ def __init__(self, channel, can_filters=None): # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(',')] - self.init_config = VCI_INIT_CONFIG(0, 0xFFFFFFFF, 0, 1, 0x00, 0x1C, 0) - - if CANalystII.VCI_OpenDevice(VCI_USBCAN2, 0, 0) == STATUS_ERR: + self.device = device + + if baud == 1000000: + Timing0, Timing1 = (0x00, 0x14) + elif baud == 800000: + Timing0, Timing1 = (0x00, 0x16) + elif baud == 666000: + Timing0, Timing1 = (0x80, 0xB6) + elif baud == 500000: + Timing0, Timing1 = (0x00, 0x1C) + elif baud == 400000: + Timing0, Timing1 = (0x80, 0xFA) + elif baud == 250000: + Timing0, Timing1 = (0x01, 0x1C) + elif baud == 200000: + Timing0, Timing1 = (0x81, 0xFA) + elif baud == 125000: + Timing0, Timing1 = (0x03, 0x1C) + elif baud == 100000: + Timing0, Timing1 = (0x04, 0x1C) + elif baud == 80000: + Timing0, Timing1 = (0x83, 0xFF) + elif baud == 50000: + Timing0, Timing1 = (0x09, 0x1C) + elif baud == 40000: + Timing0, Timing1 = (0x87, 0xFF) + elif baud == 20000: + Timing0, Timing1 = (0x18, 0x1C) + elif baud == 10000: + Timing0, Timing1 = (0x31, 0x1C) + elif baud == 5000: + Timing0, Timing1 = (0xBF, 0xFF) + + self.init_config = VCI_INIT_CONFIG(0, 0xFFFFFFFF, 0, 1, Timing0, Timing1, 0) + + if CANalystII.VCI_OpenDevice(VCI_USBCAN2, self.device, 0) == STATUS_ERR: logger.error("VCI_OpenDevice Error") for channel in self.channels: - if CANalystII.VCI_InitCAN(VCI_USBCAN2, 0, channel, byref(self.init_config)) == STATUS_ERR: + if CANalystII.VCI_InitCAN(VCI_USBCAN2, self.device, channel, byref(self.init_config)) == STATUS_ERR: logger.error("VCI_InitCAN Error") self.shutdown() return - if CANalystII.VCI_StartCAN(VCI_USBCAN2, 0, channel) == STATUS_ERR: + if CANalystII.VCI_StartCAN(VCI_USBCAN2, self.device, channel) == STATUS_ERR: logger.error("VCI_StartCAN Error") self.shutdown() return @@ -79,7 +112,7 @@ def send(self, msg, timeout=None): :param timeout: timeout is not used here :return: """ - raw_message = VCI_CAN_OBJ(msg.arbitration_id, 0, 0, 0, 0, 0, msg.dlc, (c_ubyte * 8)(*msg.data), (c_byte * 3)(*[0, 0, 0])) + raw_message = VCI_CAN_OBJ(msg.arbitration_id, 0, 0, 0, msg.is_remote_frame, 0, msg.dlc, (c_ubyte * 8)(*msg.data), (c_byte * 3)(*[0, 0, 0])) if msg.channel is not None: channel = msg.channel @@ -89,7 +122,7 @@ def send(self, msg, timeout=None): raise ValueError( "msg.channel must be set when using multiple channels.") - CANalystII.VCI_Transmit(VCI_USBCAN2, 0, channel, byref(raw_message), 1) + CANalystII.VCI_Transmit(VCI_USBCAN2, self.device, channel, byref(raw_message), 1) def _recv_internal(self, timeout=None): """ @@ -101,10 +134,20 @@ def _recv_internal(self, timeout=None): timeout = -1 if timeout is None else int(timeout * 1000) - if CANalystII.VCI_Receive(VCI_USBCAN2, 0, self.channels[0], byref(raw_message), 1, timeout) <= STATUS_ERR: + if CANalystII.VCI_Receive(VCI_USBCAN2, self.device, self.channels[0], byref(raw_message), 1, timeout) <= STATUS_ERR: return None, False else: - return Message(arbitration_id=raw_message.ID, channel=0, data=raw_message.Data), False + return ( + Message( + timestamp=raw_message.TimeStamp if raw_message.TimeFlag else 0.0, + arbitration_id=raw_message.ID, + is_remote_frame=raw_message.RemoteFlag, + channel=0, + dlc=raw_message.DataLen, + data=raw_message.Data, + ), + False, + ) def shutdown(self): - CANalystII.VCI_CloseDevice(VCI_USBCAN2, 0) + CANalystII.VCI_CloseDevice(VCI_USBCAN2, self.device) From e5cede3176785484caef8908e493ef913aeabd17 Mon Sep 17 00:00:00 2001 From: Shaoyu Date: Tue, 18 Dec 2018 14:34:36 -0600 Subject: [PATCH 0038/1235] add canalysstii documentation --- can/interfaces/canalystii.py | 15 +++++++++++++++ doc/interfaces.rst | 1 + doc/interfaces/canalystii.rst | 13 +++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 doc/interfaces/canalystii.rst diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 019917add..93c95b84f 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -46,6 +46,15 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): def __init__(self, channel, device=0, baud=10000000, Timing0=0x00, Timing1=0x14, can_filters=None): + """ + + :param channel: channel number + :param device: device number + :param baud: baud rate + :param Timing0: customize the timing register if baudrate is not specified + :param Timing1: + :param can_filters: filters for packet + """ super(CANalystIIBus, self).__init__(channel, can_filters) if isinstance(channel, (list, tuple)): @@ -58,6 +67,8 @@ def __init__(self, channel, device=0, baud=10000000, Timing0=0x00, Timing1=0x14, self.device = device + self.channel_info = "CANalyst-II: device {}, channels {}".format(self.device, self.channels) + if baud == 1000000: Timing0, Timing1 = (0x00, 0x14) elif baud == 800000: @@ -149,5 +160,9 @@ def _recv_internal(self, timeout=None): False, ) + def flush_tx_buffer(self): + for channel in self.channels: + CANalystII.VCI_ClearBUffer(VCI_USBCAN2, self.device, channel) + def shutdown(self): CANalystII.VCI_CloseDevice(VCI_USBCAN2, self.device) diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 794959ee1..ca06dd55a 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -23,6 +23,7 @@ The available interfaces are: interfaces/neovi interfaces/vector interfaces/virtual + interfaces/canalystii 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/canalystii.rst b/doc/interfaces/canalystii.rst new file mode 100644 index 000000000..1c73f4816 --- /dev/null +++ b/doc/interfaces/canalystii.rst @@ -0,0 +1,13 @@ +CANalyst-II +=========== + +CANalyst-II(+) is a USB to CAN Analyzer. The controlcan library is originally developed by `ZLG ZHIYUAN Electronics`_. + + +Bus +--- + +.. autoclass:: can.interfaces.canalystii.CANalystIIBus + + +.. _ZLG ZHIYUAN Electronics: http://www.zlg.com/can/can/product/id/42.html From d290d93ba0e0f3f24ee3302bd4e8d2aaabf8d903 Mon Sep 17 00:00:00 2001 From: Shaoyu Date: Fri, 28 Dec 2018 14:18:10 -0600 Subject: [PATCH 0039/1235] add timing register table --- can/interfaces/canalystii.py | 61 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 93c95b84f..55f349a2d 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -33,6 +33,27 @@ class VCI_CAN_OBJ(Structure): STATUS_OK = 0x01 STATUS_ERR = 0x00 +TIMING_DICT = { + 5000: (0xBF, 0xFF), + 10000: (0x31, 0x1C), + 20000: (0x18, 0x1C), + 33330: (0x09, 0x6F), + 40000: (0x87, 0xFF), + 50000: (0x09, 0x1C), + 66660: (0x04, 0x6F), + 80000: (0x83, 0xFF), + 83330: (0x03, 0x6F), + 100000: (0x04, 0x1C), + 125000: (0x03, 0x1C), + 200000: (0x81, 0xFA), + 250000: (0x01, 0x1C), + 400000: (0x80, 0xFA), + 500000: (0x00, 0x1C), + 666000: (0x80, 0xB6), + 800000: (0x00, 0x16), + 1000000: (0x00, 0x14), +} + try: if platform.system() == "Windows": CANalystII = WinDLL("./ControlCAN.dll") @@ -45,7 +66,7 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): - def __init__(self, channel, device=0, baud=10000000, Timing0=0x00, Timing1=0x14, can_filters=None): + def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None): """ :param channel: channel number @@ -69,36 +90,14 @@ def __init__(self, channel, device=0, baud=10000000, Timing0=0x00, Timing1=0x14, self.channel_info = "CANalyst-II: device {}, channels {}".format(self.device, self.channels) - if baud == 1000000: - Timing0, Timing1 = (0x00, 0x14) - elif baud == 800000: - Timing0, Timing1 = (0x00, 0x16) - elif baud == 666000: - Timing0, Timing1 = (0x80, 0xB6) - elif baud == 500000: - Timing0, Timing1 = (0x00, 0x1C) - elif baud == 400000: - Timing0, Timing1 = (0x80, 0xFA) - elif baud == 250000: - Timing0, Timing1 = (0x01, 0x1C) - elif baud == 200000: - Timing0, Timing1 = (0x81, 0xFA) - elif baud == 125000: - Timing0, Timing1 = (0x03, 0x1C) - elif baud == 100000: - Timing0, Timing1 = (0x04, 0x1C) - elif baud == 80000: - Timing0, Timing1 = (0x83, 0xFF) - elif baud == 50000: - Timing0, Timing1 = (0x09, 0x1C) - elif baud == 40000: - Timing0, Timing1 = (0x87, 0xFF) - elif baud == 20000: - Timing0, Timing1 = (0x18, 0x1C) - elif baud == 10000: - Timing0, Timing1 = (0x31, 0x1C) - elif baud == 5000: - Timing0, Timing1 = (0xBF, 0xFF) + if baud is not None: + try: + Timing0, Timing1 = TIMING_DICT[baud] + except KeyError: + raise ValueError("Baudrate is not supported") + + if Timing0 is None or Timing1 is None: + raise ValueError("Timing registers are not set") self.init_config = VCI_INIT_CONFIG(0, 0xFFFFFFFF, 0, 1, Timing0, Timing1, 0) From 7d0783cb5ca7773693411b51043d93437bcafee9 Mon Sep 17 00:00:00 2001 From: Shaoyu Date: Fri, 28 Dec 2018 18:53:41 -0600 Subject: [PATCH 0040/1235] add to contributors list and history --- CONTRIBUTORS.txt | 1 + doc/history.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 0576c2b66..68d82034b 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -22,3 +22,4 @@ Boris Wenzlaff Pierre-Luc Tessier Gagné Felix Divo Kristian Sloth Lauszus +Shaoyu Meng diff --git a/doc/history.rst b/doc/history.rst index 3ffc9bb3b..54193aff2 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -44,6 +44,8 @@ and 2018. The CAN viewer terminal script was contributed by Kristian Sloth Lauszus in 2018. +The CANalyst-II interface was contributed by Shaoyu Meng in 2018. + Support for CAN within Python ----------------------------- From b2a7bde67391e0574b480d5331cd914e6cc104a7 Mon Sep 17 00:00:00 2001 From: idaniel86 Date: Mon, 7 Jan 2019 05:32:56 +0100 Subject: [PATCH 0041/1235] New SYSTEC interface (#466) New SYSTEC interface with tests and documentation. --- can/interfaces/__init__.py | 3 +- can/interfaces/systec/__init__.py | 6 + can/interfaces/systec/constants.py | 653 ++++++++++++++++ can/interfaces/systec/exceptions.py | 89 +++ can/interfaces/systec/structures.py | 337 +++++++++ can/interfaces/systec/ucan.py | 1073 +++++++++++++++++++++++++++ can/interfaces/systec/ucanbus.py | 268 +++++++ doc/interfaces.rst | 1 + doc/interfaces/systec.rst | 76 ++ test/test_systec.py | 284 +++++++ 10 files changed, 2789 insertions(+), 1 deletion(-) create mode 100644 can/interfaces/systec/__init__.py create mode 100644 can/interfaces/systec/constants.py create mode 100644 can/interfaces/systec/exceptions.py create mode 100644 can/interfaces/systec/structures.py create mode 100644 can/interfaces/systec/ucan.py create mode 100644 can/interfaces/systec/ucanbus.py create mode 100644 doc/interfaces/systec.rst create mode 100644 test/test_systec.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 1542aa2dd..ec79e51d6 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -22,7 +22,8 @@ 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), 'vector': ('can.interfaces.vector', 'VectorBus'), 'slcan': ('can.interfaces.slcan', 'slcanBus'), - 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus') + 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus'), + 'systec': ('can.interfaces.systec', 'UcanBus') } BACKENDS.update({ diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py new file mode 100644 index 000000000..ed8eb8eb7 --- /dev/null +++ b/can/interfaces/systec/__init__.py @@ -0,0 +1,6 @@ +# coding: utf-8 + +""" +""" + +from can.interfaces.systec.ucanbus import UcanBus diff --git a/can/interfaces/systec/constants.py b/can/interfaces/systec/constants.py new file mode 100644 index 000000000..64122dac9 --- /dev/null +++ b/can/interfaces/systec/constants.py @@ -0,0 +1,653 @@ +# coding: utf-8 + +from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD + +#: Maximum number of modules that are supported. +MAX_MODULES = 64 + +#: Maximum number of applications that can use the USB-CAN-library. +MAX_INSTANCES = 64 + +#: With the method :meth:`UcanServer.init_can` the module is used, which is detected at first. +#: This value only should be used in case only one module is connected to the computer. +ANY_MODULE = 255 + +#: No valid USB-CAN Handle (only used internally). +INVALID_HANDLE = 0xFF + + +class Baudrate(WORD): + """ + Specifies pre-defined baud rate values for GW-001, GW-002 and all systec USB-CANmoduls. + + .. seealso:: + + :meth:`UcanServer.init_can` + + :meth:`UcanServer.set_baudrate` + + :meth:`UcanServer.get_baudrate_message` + + :class:`BaudrateEx` + """ + + #: 1000 kBit/sec + BAUD_1MBit = 0x14 + #: 800 kBit/sec + BAUD_800kBit = 0x16 + #: 500 kBit/sec + BAUD_500kBit = 0x1C + #: 250 kBit/sec + BAUD_250kBit = 0x11C + #: 125 kBit/sec + BAUD_125kBit = 0x31C + #: 100 kBit/sec + BAUD_100kBit = 0x432F + #: 50 kBit/sec + BAUD_50kBit = 0x472F + #: 20 kBit/sec + BAUD_20kBit = 0x532F + #: 10 kBit/sec + BAUD_10kBit = 0x672F + #: Uses pre-defined extended values of baudrate for all systec USB-CANmoduls. + BAUD_USE_BTREX = 0x0 + #: Automatic baud rate detection (not implemented in this version). + BAUD_AUTO = -1 + + +class BaudrateEx(DWORD): + """ + Specifies pre-defined baud rate values for all systec USB-CANmoduls. + + These values cannot be used for GW-001 and GW-002! Use values from enum :class:`Baudrate` instead. + + .. seealso:: + + :meth:`UcanServer.init_can` + + :meth:`UcanServer.set_baudrate` + + :meth:`UcanServer.get_baudrate_ex_message` + + :class:`Baudrate` + """ + + #: G3: 1000 kBit/sec + BAUDEX_1MBit = 0x20354 + #: G3: 800 kBit/sec + BAUDEX_800kBit = 0x30254 + #: G3: 500 kBit/sec + BAUDEX_500kBit = 0x50354 + #: G3: 250 kBit/sec + BAUDEX_250kBit = 0xB0354 + #: G3: 125 kBit/sec + BAUDEX_125kBit = 0x170354 + #: G3: 100 kBit/sec + BAUDEX_100kBit = 0x170466 + #: G3: 50 kBit/sec + BAUDEX_50kBit = 0x2F0466 + #: G3: 20 kBit/sec + BAUDEX_20kBit = 0x770466 + #: G3: 10 kBit/sec (half CPU clock) + BAUDEX_10kBit = 0x80770466 + #: G3: 1000 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_1MBit = 0x20741 + #: G3: 800 kBit/sec Sample Point: 86,67% + BAUDEX_SP2_800kBit = 0x30731 + #: G3: 500 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_500kBit = 0x50741 + #: G3: 250 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_250kBit = 0xB0741 + #: G3: 125 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_125kBit = 0x170741 + #: G3: 100 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_100kBit = 0x1D1741 + #: G3: 50 kBit/sec Sample Point: 87,50% + BAUDEX_SP2_50kBit = 0x3B1741 + #: G3: 20 kBit/sec Sample Point: 85,00% + BAUDEX_SP2_20kBit = 0x771772 + #: G3: 10 kBit/sec Sample Point: 85,00% (half CPU clock) + BAUDEX_SP2_10kBit = 0x80771772 + + #: G4: 1000 kBit/sec Sample Point: 83,33% + BAUDEX_G4_1MBit = 0x406F0000 + #: G4: 800 kBit/sec Sample Point: 80,00% + BAUDEX_G4_800kBit = 0x402A0001 + #: G4: 500 kBit/sec Sample Point: 83,33% + BAUDEX_G4_500kBit = 0x406F0001 + #: G4: 250 kBit/sec Sample Point: 83,33% + BAUDEX_G4_250kBit = 0x406F0003 + #: G4: 125 kBit/sec Sample Point: 83,33% + BAUDEX_G4_125kBit = 0x406F0007 + #: G4: 100 kBit/sec Sample Point: 83,33% + BAUDEX_G4_100kBit = 0x416F0009 + #: G4: 50 kBit/sec Sample Point: 83,33% + BAUDEX_G4_50kBit = 0x416F0013 + #: G4: 20 kBit/sec Sample Point: 84,00% + BAUDEX_G4_20kBit = 0x417F002F + #: G4: 10 kBit/sec Sample Point: 84,00% (half CPU clock) + BAUDEX_G4_10kBit = 0x417F005F + #: Uses pre-defined values of baud rates of :class:`Baudrate`. + BAUDEX_USE_BTR01 = 0x0 + #: Automatic baud rate detection (not implemented in this version). + BAUDEX_AUTO = 0xFFFFFFFF + + +class MsgFrameFormat(BYTE): + """ + Specifies values for the frame format of CAN messages for member :attr:`CanMsg.m_bFF` in structure + :class:`CanMsg`. These values can be combined. + + .. seealso:: :class:`CanMsg` + """ + + #: standard CAN data frame with 11 bit ID (CAN2.0A spec.) + MSG_FF_STD = 0x0 + #: transmit echo + MSG_FF_ECHO = 0x20 + #: CAN remote request frame with + MSG_FF_RTR = 0x40 + #: extended CAN data frame with 29 bit ID (CAN2.0B spec.) + MSG_FF_EXT = 0x80 + + +class ReturnCode(BYTE): + """ + Specifies all return codes of all methods of this class. + """ + + #: no error + SUCCESSFUL = 0x0 + # start of error codes coming from USB-CAN-library + ERR = 0x1 + # start of error codes coming from command interface between host and USB-CANmodul + ERRCMD = 0x40 + # start of warning codes + WARNING = 0x80 + # start of reserved codes which are only used internally + RESERVED = 0xC0 + + #: could not created a resource (memory, handle, ...) + ERR_RESOURCE = 0x1 + #: the maximum number of opened modules is reached + ERR_MAXMODULES = 0x2 + #: the specified module is already in use + ERR_HWINUSE = 0x3 + #: the software versions of the module and library are incompatible + ERR_ILLVERSION = 0x4 + #: the module with the specified device number is not connected (or used by an other application) + ERR_ILLHW = 0x5 + #: wrong USB-CAN-Handle handed over to the function + ERR_ILLHANDLE = 0x6 + #: wrong parameter handed over to the function + ERR_ILLPARAM = 0x7 + #: instruction can not be processed at this time + ERR_BUSY = 0x8 + #: no answer from module + ERR_TIMEOUT = 0x9 + #: a request to the driver failed + ERR_IOFAILED = 0xA + #: a CAN message did not fit into the transmit buffer + ERR_DLL_TXFULL = 0xB + #: maximum number of applications is reached + ERR_MAXINSTANCES = 0xC + #: CAN interface is not yet initialized + ERR_CANNOTINIT = 0xD + #: USB-CANmodul was disconnected + ERR_DISCONECT = 0xE + #: the needed device class does not exist + ERR_NOHWCLASS = 0xF + #: illegal CAN channel + ERR_ILLCHANNEL = 0x10 + #: reserved + ERR_RESERVED1 = 0x11 + #: the API function can not be used with this hardware + ERR_ILLHWTYPE = 0x12 + + #: the received response does not match to the transmitted command + ERRCMD_NOTEQU = 0x40 + #: no access to the CAN controller + ERRCMD_REGTST = 0x41 + #: the module could not interpret the command + ERRCMD_ILLCMD = 0x42 + #: error while reading the EEPROM + ERRCMD_EEPROM = 0x43 + #: reserved + ERRCMD_RESERVED1 = 0x44 + #: reserved + ERRCMD_RESERVED2 = 0x45 + #: reserved + ERRCMD_RESERVED3 = 0x46 + #: illegal baud rate value specified in BTR0/BTR1 for systec USB-CANmoduls + ERRCMD_ILLBDR = 0x47 + #: CAN channel is not initialized + ERRCMD_NOTINIT = 0x48 + #: CAN channel is already initialized + ERRCMD_ALREADYINIT = 0x49 + #: illegal sub-command specified + ERRCMD_ILLSUBCMD = 0x4A + #: illegal index specified (e.g. index for cyclic CAN messages) + ERRCMD_ILLIDX = 0x4B + #: cyclic CAN message(s) can not be defined because transmission of cyclic CAN messages is already running + ERRCMD_RUNNING = 0x4C + + #: no CAN messages received + WARN_NODATA = 0x80 + #: overrun in receive buffer of the kernel driver + WARN_SYS_RXOVERRUN = 0x81 + #: overrun in receive buffer of the USB-CAN-library + WARN_DLL_RXOVERRUN = 0x82 + #: reserved + WARN_RESERVED1 = 0x83 + #: reserved + WARN_RESERVED2 = 0x84 + #: overrun in transmit buffer of the firmware (but this CAN message was successfully stored in buffer of the + #: library) + WARN_FW_TXOVERRUN = 0x85 + #: overrun in receive buffer of the firmware (but this CAN message was successfully read) + WARN_FW_RXOVERRUN = 0x86 + #: reserved + WARN_FW_TXMSGLOST = 0x87 + #: pointer is NULL + WARN_NULL_PTR = 0x90 + #: not all CAN messages could be stored to the transmit buffer in USB-CAN-library (check output of parameter + #: pdwCount_p) + WARN_TXLIMIT = 0x91 + #: reserved + WARN_BUSY = 0x92 + + +class CbEvent(BYTE): + """ + This enum defines events for the callback functions of the library. + + .. seealso:: :meth:`UcanServer.get_status` + """ + + #: The USB-CANmodul has been initialized. + EVENT_INITHW = 0 + #: The CAN interface has been initialized. + EVENT_init_can = 1 + #: A new CAN message has been received. + EVENT_RECEIVE = 2 + #: The error state in the module has changed. + EVENT_STATUS = 3 + #: The CAN interface has been deinitialized. + EVENT_DEINIT_CAN = 4 + #: The USB-CANmodul has been deinitialized. + EVENT_DEINITHW = 5 + #: A new USB-CANmodul has been connected. + EVENT_CONNECT = 6 + #: Any USB-CANmodul has been disconnected. + EVENT_DISCONNECT = 7 + #: A USB-CANmodul has been disconnected during operation. + EVENT_FATALDISCON = 8 + #: Reserved + EVENT_RESERVED1 = 0x80 + + +class CanStatus(WORD): + """ + CAN error status bits. These bit values occurs in combination with the method :meth:`UcanServer.get_status`. + + .. seealso:: + + :meth:`UcanServer.get_status` + + :meth:`UcanServer.get_can_status_message` + """ + + #: No error. + CANERR_OK = 0x0 + #: Transmit buffer of the CAN controller is full. + CANERR_XMTFULL = 0x1 + #: Receive buffer of the CAN controller is full. + CANERR_OVERRUN = 0x2 + #: Bus error: Error Limit 1 exceeded (Warning Limit reached) + CANERR_BUSLIGHT = 0x4 + #: Bus error: Error Limit 2 exceeded (Error Passive) + CANERR_BUSHEAVY = 0x8 + #: Bus error: CAN controller has gone into Bus-Off state. + #: Method :meth:`UcanServer.reset_can` has to be called. + CANERR_BUSOFF = 0x10 + #: No CAN message is within the receive buffer. + CANERR_QRCVEMPTY = 0x20 + #: Receive buffer is full. CAN messages has been lost. + CANERR_QOVERRUN = 0x40 + #: Transmit buffer is full. + CANERR_QXMTFULL = 0x80 + #: Register test of the CAN controller failed. + CANERR_REGTEST = 0x100 + #: Memory test on hardware failed. + CANERR_MEMTEST = 0x200 + #: Transmit CAN message(s) was/were automatically deleted by firmware (transmit timeout). + CANERR_TXMSGLOST = 0x400 + + +class UsbStatus(WORD): + """ + USB error status bits. These bit values occurs in combination with the method :meth:`UcanServer.get_status`. + + .. seealso:: :meth:`UcanServer.get_status` + """ + + #: No error. + USBERR_OK = 0x0 + + +#: Specifies the acceptance mask for receiving all CAN messages. +#: +#: .. seealso:: +#: +#: :const:`ACR_ALL` +#: +#: :meth:`UcanServer.init_can` +#: +#: :meth:`UcanServer.set_acceptance` +AMR_ALL = 0xFFFFFFFF + +#: Specifies the acceptance code for receiving all CAN messages. +#: +#: .. seealso:: +#: +#: :const:`AMR_ALL` +#: +#: :meth:`UcanServer.init_can` +#: +#: :meth:`UcanServer.set_acceptance` +ACR_ALL = 0x0 + + +class OutputControl(BYTE): + """ + Specifies pre-defined values for the Output Control Register of SJA1000 on GW-001 and GW-002. + These values are only important for GW-001 and GW-002. + They does not have an effect on systec USB-CANmoduls. + """ + #: default OCR value for the standard USB-CANmodul GW-001/GW-002 + OCR_DEFAULT = 0x1A + #: OCR value for RS485 interface and galvanic isolation + OCR_RS485_ISOLATED = 0x1E + #: OCR value for RS485 interface but without galvanic isolation + OCR_RS485_NOT_ISOLATED = 0xA + + +#: Specifies the default value for the maximum number of entries in the receive and transmit buffer. +DEFAULT_BUFFER_ENTRIES = 4096 + + +class Channel(BYTE): + """ + Specifies values for the CAN channel to be used on multi-channel USB-CANmoduls. + """ + + #: Specifies the first CAN channel (GW-001/GW-002 and USB-CANmodul1 only can be used with this channel). + CHANNEL_CH0 = 0 + #: Specifies the second CAN channel (this channel cannot be used with GW-001/GW-002 and USB-CANmodul1). + CHANNEL_CH1 = 1 + #: Specifies all CAN channels (can only be used with the method :meth:`UcanServer.shutdown`). + CHANNEL_ALL = 254 + #: Specifies the use of any channel (can only be used with the method :meth:`UcanServer.read_can_msg`). + CHANNEL_ANY = 255 + #: Specifies the first CAN channel (equivalent to :data:`CHANNEL_CH0`). + CHANNEL_CAN1 = CHANNEL_CH0 + #: Specifies the second CAN channel (equivalent to :data:`CHANNEL_CH1`). + CHANNEL_CAN2 = CHANNEL_CH1 + #: Specifies the LIN channel (currently not supported by the software). + CHANNEL_LIN = CHANNEL_CH1 + + +class ResetFlags(DWORD): + """ + Specifies flags for resetting USB-CANmodul with method :meth:`UcanServer.reset_can`. + These flags can be used in combination. + + .. seealso:: :meth:`UcanServer.reset_can` + """ + + #: reset everything + RESET_ALL = 0x0 + #: no CAN status reset (only supported for systec USB-CANmoduls) + RESET_NO_STATUS = 0x1 + #: no CAN controller reset + RESET_NO_CANCTRL = 0x2 + #: no transmit message counter reset + RESET_NO_TXCOUNTER = 0x4 + #: no receive message counter reset + RESET_NO_RXCOUNTER = 0x8 + #: no transmit message buffer reset at channel level + RESET_NO_TXBUFFER_CH = 0x10 + #: no transmit message buffer reset at USB-CAN-library level + RESET_NO_TXBUFFER_DLL = 0x20 + #: no transmit message buffer reset at firmware level + RESET_NO_TXBUFFER_FW = 0x80 + #: no receive message buffer reset at channel level + RESET_NO_RXBUFFER_CH = 0x100 + #: no receive message buffer reset at USB-CAN-library level + RESET_NO_RXBUFFER_DLL = 0x200 + #: no receive message buffer reset at kernel driver level + RESET_NO_RXBUFFER_SYS = 0x400 + #: no receive message buffer reset at firmware level + RESET_NO_RXBUFFER_FW = 0x800 + #: complete firmware reset (module will automatically reconnect at USB port in 500msec) + RESET_FIRMWARE = 0xFFFFFFFF + + #: no reset of all message counters + RESET_NO_COUNTER_ALL = (RESET_NO_TXCOUNTER | RESET_NO_RXCOUNTER) + #: no reset of transmit message buffers at communication level (firmware, kernel and library) + RESET_NO_TXBUFFER_COMM = (RESET_NO_TXBUFFER_DLL | 0x40 | RESET_NO_TXBUFFER_FW) + #: no reset of receive message buffers at communication level (firmware, kernel and library) + RESET_NO_RXBUFFER_COMM = (RESET_NO_RXBUFFER_DLL | RESET_NO_RXBUFFER_SYS | RESET_NO_RXBUFFER_FW) + #: no reset of all transmit message buffers + RESET_NO_TXBUFFER_ALL = (RESET_NO_TXBUFFER_CH | RESET_NO_TXBUFFER_COMM) + #: no reset of all receive message buffers + RESET_NO_RXBUFFER_ALL = (RESET_NO_RXBUFFER_CH | RESET_NO_RXBUFFER_COMM) + #: no reset of all message buffers at communication level (firmware, kernel and library) + RESET_NO_BUFFER_COMM = (RESET_NO_TXBUFFER_COMM | RESET_NO_RXBUFFER_COMM) + #: no reset of all message buffers + RESET_NO_BUFFER_ALL = (RESET_NO_TXBUFFER_ALL | RESET_NO_RXBUFFER_ALL) + #: reset of the CAN status only + RESET_ONLY_STATUS = (0xFFFF & ~RESET_NO_STATUS) + #: reset of the CAN controller only + RESET_ONLY_CANCTRL = (0xFFFF & ~RESET_NO_CANCTRL) + #: reset of the transmit buffer in firmware only + RESET_ONLY_TXBUFFER_FW = (0xFFFF & ~RESET_NO_TXBUFFER_FW) + #: reset of the receive buffer in firmware only + RESET_ONLY_RXBUFFER_FW = (0xFFFF & ~RESET_NO_RXBUFFER_FW) + #: reset of the specified channel of the receive buffer only + RESET_ONLY_RXCHANNEL_BUFF = (0xFFFF & ~RESET_NO_RXBUFFER_CH) + #: reset of the specified channel of the transmit buffer only + RESET_ONLY_TXCHANNEL_BUFF = (0xFFFF & ~RESET_NO_TXBUFFER_CH) + #: reset of the receive buffer and receive message counter only + RESET_ONLY_RX_BUFF = (0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER)) + #: reset of the receive buffer and receive message counter (for GW-002) only + RESET_ONLY_RX_BUFF_GW002 = (0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER | + RESET_NO_TXBUFFER_FW)) + #: reset of the transmit buffer and transmit message counter only + RESET_ONLY_TX_BUFF = (0xFFFF & ~(RESET_NO_TXBUFFER_ALL | RESET_NO_TXCOUNTER)) + #: reset of all buffers and all message counters only + RESET_ONLY_ALL_BUFF = (RESET_ONLY_RX_BUFF & RESET_ONLY_TX_BUFF) + #: reset of all message counters only + RESET_ONLY_ALL_COUNTER = (0xFFFF & ~RESET_NO_COUNTER_ALL) + + +PRODCODE_PID_TWO_CHA = 0x1 +PRODCODE_PID_TERM = 0x1 +PRODCODE_PID_RBUSER = 0x1 +PRODCODE_PID_RBCAN = 0x1 +PRODCODE_PID_G4 = 0x20 +PRODCODE_PID_RESVD = 0x40 + +PRODCODE_MASK_DID = 0xFFFF0000 +PRODCODE_MASK_PID = 0xFFFF +PRODCODE_MASK_PIDG3 = (PRODCODE_MASK_PID & 0xFFFFFFBF) + + +class ProductCode(WORD): + """ + These values defines product codes for all known USB-CANmodul derivatives received in member + :attr:`HardwareInfoEx.m_dwProductCode` of structure :class:`HardwareInfoEx` + with method :meth:`UcanServer.get_hardware_info`. + + .. seealso:: + + :meth:`UcanServer.get_hardware_info` + + :class:`HardwareInfoEx` + """ + + #: Product code for GW-001 (outdated). + PRODCODE_PID_GW001 = 0x1100 + #: Product code for GW-002 (outdated). + PRODCODE_PID_GW002 = 0x1102 + #: Product code for Multiport CAN-to-USB G3. + PRODCODE_PID_MULTIPORT = 0x1103 + #: Product code for USB-CANmodul1 G3. + PRODCODE_PID_BASIC = 0x1104 + #: Product code for USB-CANmodul2 G3. + PRODCODE_PID_ADVANCED = 0x1105 + #: Product code for USB-CANmodul8 G3. + PRODCODE_PID_USBCAN8 = 0x1107 + #: Product code for USB-CANmodul16 G3. + PRODCODE_PID_USBCAN16 = 0x1109 + #: Reserved. + PRODCODE_PID_RESERVED3 = 0x1110 + #: Product code for USB-CANmodul2 G4. + PRODCODE_PID_ADVANCED_G4 = 0x1121 + #: Product code for USB-CANmodul1 G4. + PRODCODE_PID_BASIC_G4 = 0x1122 + #: Reserved. + PRODCODE_PID_RESERVED1 = 0x1144 + #: Reserved. + PRODCODE_PID_RESERVED2 = 0x1145 + + +#: Definitions for cyclic CAN messages. +MAX_CYCLIC_CAN_MSG = 16 + + +class CyclicFlags(DWORD): + """ + Specifies flags for cyclical CAN messages. + These flags can be used in combinations with method :meth:`UcanServer.enable_cyclic_can_msg`. + + .. seealso:: :meth:`UcanServer.enable_cyclic_can_msg` + """ + + #: Stops the transmission of cyclic CAN messages. + CYCLIC_FLAG_STOPP = 0x0 + #: Global enable of transmission of cyclic CAN messages. + CYCLIC_FLAG_START = 0x80000000 + #: List of cyclic CAN messages will be processed in sequential mode (otherwise in parallel mode). + CYCLIC_FLAG_SEQUMODE = 0x40000000 + #: No echo will be sent back if echo mode is enabled with method :meth:`UcanServer.init_can`. + CYCLIC_FLAG_NOECHO = 0x10000 + #: CAN message with index 0 of the list will not be sent. + CYCLIC_FLAG_LOCK_0 = 0x1 + #: CAN message with index 1 of the list will not be sent. + CYCLIC_FLAG_LOCK_1 = 0x2 + #: CAN message with index 2 of the list will not be sent. + CYCLIC_FLAG_LOCK_2 = 0x4 + #: CAN message with index 3 of the list will not be sent. + CYCLIC_FLAG_LOCK_3 = 0x8 + #: CAN message with index 4 of the list will not be sent. + CYCLIC_FLAG_LOCK_4 = 0x10 + #: CAN message with index 5 of the list will not be sent. + CYCLIC_FLAG_LOCK_5 = 0x20 + #: CAN message with index 6 of the list will not be sent. + CYCLIC_FLAG_LOCK_6 = 0x40 + #: CAN message with index 7 of the list will not be sent. + CYCLIC_FLAG_LOCK_7 = 0x80 + #: CAN message with index 8 of the list will not be sent. + CYCLIC_FLAG_LOCK_8 = 0x100 + #: CAN message with index 9 of the list will not be sent. + CYCLIC_FLAG_LOCK_9 = 0x200 + #: CAN message with index 10 of the list will not be sent. + CYCLIC_FLAG_LOCK_10 = 0x400 + #: CAN message with index 11 of the list will not be sent. + CYCLIC_FLAG_LOCK_11 = 0x800 + #: CAN message with index 12 of the list will not be sent. + CYCLIC_FLAG_LOCK_12 = 0x1000 + #: CAN message with index 13 of the list will not be sent. + CYCLIC_FLAG_LOCK_13 = 0x2000 + #: CAN message with index 14 of the list will not be sent. + CYCLIC_FLAG_LOCK_14 = 0x4000 + #: CAN message with index 15 of the list will not be sent. + CYCLIC_FLAG_LOCK_15 = 0x8000 + + +class PendingFlags(BYTE): + """ + Specifies flags for method :meth:`UcanServer.get_msg_pending`. + These flags can be uses in combinations. + + .. seealso:: :meth:`UcanServer.get_msg_pending` + """ + + #: number of pending CAN messages in receive buffer of USB-CAN-library + PENDING_FLAG_RX_DLL = 0x1 + #: reserved + PENDING_FLAG_RX_SYS = 0x2 + #: number of pending CAN messages in receive buffer of firmware + PENDING_FLAG_RX_FW = 0x4 + #: number of pending CAN messages in transmit buffer of USB-CAN-library + PENDING_FLAG_TX_DLL = 0x10 + #: reserved + PENDING_FLAG_TX_SYS = 0x20 + #: number of pending CAN messages in transmit buffer of firmware + PENDING_FLAG_TX_FW = 0x40 + #: number of pending CAN messages in all receive buffers + PENDING_FLAG_RX_ALL = (PENDING_FLAG_RX_DLL | PENDING_FLAG_RX_SYS | PENDING_FLAG_RX_FW) + #: number of pending CAN messages in all transmit buffers + PENDING_FLAG_TX_ALL = (PENDING_FLAG_TX_DLL | PENDING_FLAG_TX_SYS | PENDING_FLAG_TX_FW) + #: number of pending CAN messages in all buffers + PENDING_FLAG_ALL = (PENDING_FLAG_RX_ALL | PENDING_FLAG_TX_ALL) + + +class Mode(BYTE): + """ + Specifies values for operation mode of a CAN channel. + These values can be combined by OR operation with the method :meth:`UcanServer.init_can`. + """ + + #: normal operation mode (transmitting and receiving) + MODE_NORMAL = 0 + #: listen only mode (receiving only, no ACK at CAN bus) + MODE_LISTEN_ONLY = 1 + #: CAN messages which was sent will be received back with method :meth:`UcanServer.read_can_msg` + MODE_TX_ECHO = 2 + #: reserved (not implemented in this version) + MODE_RX_ORDER_CH = 4 + #: high resolution time stamps in received CAN messages (only available with STM derivatives) + MODE_HIGH_RES_TIMER = 8 + + +class VersionType(BYTE): + """ + Specifies values for receiving the version information of several driver files. + + .. note:: This structure is only used internally. + """ + + #: version of the USB-CAN-library + VER_TYPE_USER_LIB = 1 + #: equivalent to :attr:`VER_TYPE_USER_LIB` + VER_TYPE_USER_DLL = 1 + #: version of USBCAN.SYS (not supported in this version) + VER_TYPE_SYS_DRV = 2 + #: version of firmware in hardware (not supported, use method :meth:`UcanServer.get_fw_version`) + VER_TYPE_FIRMWARE = 3 + #: version of UCANNET.SYS + VER_TYPE_NET_DRV = 4 + #: version of USBCANLD.SYS + VER_TYPE_SYS_LD = 5 + #: version of USBCANL2.SYS + VER_TYPE_SYS_L2 = 6 + #: version of USBCANL3.SYS + VER_TYPE_SYS_L3 = 7 + #: version of USBCANL4.SYS + VER_TYPE_SYS_L4 = 8 + #: version of USBCANL5.SYS + VER_TYPE_SYS_L5 = 9 + #: version of USBCANCP.CPL + VER_TYPE_CPL = 10 diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py new file mode 100644 index 000000000..d3525cd88 --- /dev/null +++ b/can/interfaces/systec/exceptions.py @@ -0,0 +1,89 @@ +# coding: utf-8 + +from .constants import ReturnCode +from can import CanError + + +class UcanException(CanError): + """ Base class for USB can errors. """ + def __init__(self, result, func, arguments): + self.result = result.value + self.func = func + self.arguments = arguments + self.return_msgs = NotImplemented + + def __str__(self): + return "Function %s returned %d: %s" % \ + (self.func.__name__, self.result, self.return_msgs.get(self.result, "unknown")) + + +class UcanError(UcanException): + """ Exception class for errors from USB-CAN-library. """ + def __init__(self, result, func, arguments): + super(UcanError, self).__init__(result, func, arguments) + self.return_msgs = { + ReturnCode.ERR_RESOURCE: "could not created a resource (memory, handle, ...)", + ReturnCode.ERR_MAXMODULES: "the maximum number of opened modules is reached", + ReturnCode.ERR_HWINUSE: "the specified module is already in use", + ReturnCode.ERR_ILLVERSION: "the software versions of the module and library are incompatible", + ReturnCode.ERR_ILLHW: "the module with the specified device number is not connected " + "(or used by an other application)", + ReturnCode.ERR_ILLHANDLE: "wrong USB-CAN-Handle handed over to the function", + ReturnCode.ERR_ILLPARAM: "wrong parameter handed over to the function", + ReturnCode.ERR_BUSY: "instruction can not be processed at this time", + ReturnCode.ERR_TIMEOUT: "no answer from module", + ReturnCode.ERR_IOFAILED: "a request to the driver failed", + ReturnCode.ERR_DLL_TXFULL: "a CAN message did not fit into the transmit buffer", + ReturnCode.ERR_MAXINSTANCES: "maximum number of applications is reached", + ReturnCode.ERR_CANNOTINIT: "CAN interface is not yet initialized", + ReturnCode.ERR_DISCONECT: "USB-CANmodul was disconnected", + ReturnCode.ERR_NOHWCLASS: "the needed device class does not exist", + ReturnCode.ERR_ILLCHANNEL: "illegal CAN channel", + ReturnCode.ERR_RESERVED1: "reserved", + ReturnCode.ERR_ILLHWTYPE: "the API function can not be used with this hardware", + } + + +class UcanCmdError(UcanException): + """ Exception class for errors from firmware in USB-CANmodul.""" + def __init__(self, result, func, arguments): + super(UcanCmdError, self).__init__(result, func, arguments) + self.return_msgs = { + ReturnCode.ERRCMD_NOTEQU: "the received response does not match to the transmitted command", + ReturnCode.ERRCMD_REGTST: "no access to the CAN controller", + ReturnCode.ERRCMD_ILLCMD: "the module could not interpret the command", + ReturnCode.ERRCMD_EEPROM: "error while reading the EEPROM", + ReturnCode.ERRCMD_RESERVED1: "reserved", + ReturnCode.ERRCMD_RESERVED2: "reserved", + ReturnCode.ERRCMD_RESERVED3: "reserved", + ReturnCode.ERRCMD_ILLBDR: "illegal baud rate value specified in BTR0/BTR1 for systec " + "USB-CANmoduls", + ReturnCode.ERRCMD_NOTINIT: "CAN channel is not initialized", + ReturnCode.ERRCMD_ALREADYINIT: "CAN channel is already initialized", + ReturnCode.ERRCMD_ILLSUBCMD: "illegal sub-command specified", + ReturnCode.ERRCMD_ILLIDX: "illegal index specified (e.g. index for cyclic CAN messages)", + ReturnCode.ERRCMD_RUNNING: "cyclic CAN message(s) can not be defined because transmission of " + "cyclic CAN messages is already running", + } + + +class UcanWarning(UcanException): + """ Exception class for warnings, the function has been executed anyway. """ + def __init__(self, result, func, arguments): + super(UcanWarning, self).__init__(result, func, arguments) + self.return_msgs = { + ReturnCode.WARN_NODATA: "no CAN messages received", + ReturnCode.WARN_SYS_RXOVERRUN: "overrun in receive buffer of the kernel driver", + ReturnCode.WARN_DLL_RXOVERRUN: "overrun in receive buffer of the USB-CAN-library", + ReturnCode.WARN_RESERVED1: "reserved", + ReturnCode.WARN_RESERVED2: "reserved", + ReturnCode.WARN_FW_TXOVERRUN: "overrun in transmit buffer of the firmware (but this CAN message " + "was successfully stored in buffer of the ibrary)", + ReturnCode.WARN_FW_RXOVERRUN: "overrun in receive buffer of the firmware (but this CAN message " + "was successfully read)", + ReturnCode.WARN_FW_TXMSGLOST: "reserved", + ReturnCode.WARN_NULL_PTR: "pointer is NULL", + ReturnCode.WARN_TXLIMIT: "not all CAN messages could be stored to the transmit buffer in " + "USB-CAN-library", + ReturnCode.WARN_BUSY: "reserved" + } diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py new file mode 100644 index 000000000..a521e044f --- /dev/null +++ b/can/interfaces/systec/structures.py @@ -0,0 +1,337 @@ +# coding: utf-8 + +from ctypes import Structure, POINTER, sizeof +from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD, c_long as BOOL, c_void_p as LPVOID +import os +# Workaround for Unix based platforms to be able to load structures for testing, etc... +if os.name == "nt": + from ctypes import WINFUNCTYPE as FUNCTYPE +else: + from ctypes import CFUNCTYPE as FUNCTYPE + +from .constants import MsgFrameFormat + + +class CanMsg(Structure): + """ + Structure of a CAN message. + + .. seealso:: + + :meth:`UcanServer.read_can_msg` + + :meth:`UcanServer.write_can_msg` + + :meth:`UcanServer.define_cyclic_can_msg` + + :meth:`UcanServer.read_cyclic_can_msg` + """ + _pack_ = 1 + _fields_ = [ + ("m_dwID", DWORD), # CAN Identifier + ("m_bFF", BYTE), # CAN Frame Format (see enum :class:`MsgFrameFormat`) + ("m_bDLC", BYTE), # CAN Data Length Code + ("m_bData", BYTE * 8), # CAN Data (array of 8 bytes) + ("m_dwTime", DWORD,) # Receive time stamp in ms (for transmit messages no meaning) + ] + + def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=[]): + super(CanMsg, self).__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) + + def __eq__(self, other): + if not isinstance(other, CanMsg): + return False + + return self.id == other.id and self.frame_format == other.frame_format and self.data == other.data + + @property + def id(self): return self.m_dwID + + @id.setter + def id(self, id): self.m_dwID = id + + @property + def frame_format(self): return self.m_bFF + + @frame_format.setter + def frame_format(self, frame_format): self.m_bFF = frame_format + + @property + def data(self): return self.m_bData[:self.m_bDLC] + + @data.setter + def data(self, data): + self.m_bDLC = len(data) + self.m_bData((BYTE * 8)(*data)) + + @property + def time(self): return self.m_dwTime + + +class Status(Structure): + """ + Structure with the error status of CAN and USB. + Use this structure with the method :meth:`UcanServer.get_status` + + .. seealso:: + + :meth:`UcanServer.get_status` + + :meth:`UcanServer.get_can_status_message` + """ + _pack_ = 1 + _fields_ = [ + ("m_wCanStatus", WORD), # CAN error status (see enum :class:`CanStatus`) + ("m_wUsbStatus", WORD), # USB error status (see enum :class:`UsbStatus`) + ] + + def __eq__(self, other): + if not isinstance(other, Status): + return False + + return self.can_status == other.can_status and self.usb_status == other.usb_status + + @property + def can_status(self): return self.m_wCanStatus + + @property + def usb_status(self): return self.m_wUsbStatus + + +class InitCanParam(Structure): + """ + Structure including initialisation parameters used internally in :meth:`UcanServer.init_can`. + + .. note:: This structure is only used internally. + """ + _pack_ = 1 + _fields_ = [ + ("m_dwSize", DWORD), # size of this structure (only used internally) + ("m_bMode", BYTE), # selects the mode of CAN controller (see enum :class:`Mode`) + # Baudrate Registers for GW-001 or GW-002 + ("m_bBTR0", BYTE), # Bus Timing Register 0 (see enum :class:`Baudrate`) + ("m_bBTR1", BYTE), # Bus Timing Register 1 (see enum :class:`Baudrate`) + ("m_bOCR", BYTE), # Output Control Register (see enum :class:`OutputControl`) + ("m_dwAMR", DWORD), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) + ("m_dwACR", DWORD), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) + ("m_dwBaudrate", DWORD), # Baudrate Register for all systec USB-CANmoduls + # (see enum :class:`BaudrateEx`) + ("m_wNrOfRxBufferEntries", WORD), # number of receive buffer entries (default is 4096) + ("m_wNrOfTxBufferEntries", WORD), # number of transmit buffer entries (default is 4096) + ] + + def __init__(self, mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries): + super(InitCanParam, self).__init__(sizeof(InitCanParam), mode, BTR >> 8, BTR, OCR, AMR, ACR, + baudrate, rx_buffer_entries, tx_buffer_entries) + + def __eq__(self, other): + if not isinstance(other, InitCanParam): + return False + + return self.mode == other.mode and self.BTR == other.BTR and self.OCR == other.OCR and \ + self.baudrate == other.baudrate and self.rx_buffer_entries == other.rx_buffer_entries and \ + self.tx_buffer_entries == other.tx_buffer_entries + + @property + def mode(self): return self.m_bMode + + @mode.setter + def mode(self, mode): self.m_bMode = mode + + @property + def BTR(self): return self.m_bBTR0 << 8 | self.m_bBTR1 + + @BTR.setter + def BTR(self, BTR): self.m_bBTR0, self.m_bBTR1 = BTR >> 8, BTR + + @property + def OCR(self): return self.m_bOCR + + @OCR.setter + def OCR(self, OCR): self.m_bOCR = OCR + + @property + def baudrate(self): return self.m_dwBaudrate + + @baudrate.setter + def baudrate(self, baudrate): self.m_dwBaudrate = baudrate + + @property + def rx_buffer_entries(self): return self.m_wNrOfRxBufferEntries + + @rx_buffer_entries.setter + def rx_buffer_entries(self, rx_buffer_entries): self.m_wNrOfRxBufferEntries = rx_buffer_entries + + @property + def tx_buffer_entries(self): return self.m_wNrOfTxBufferEntries + + @rx_buffer_entries.setter + def tx_buffer_entries(self, tx_buffer_entries): self.m_wNrOfTxBufferEntries = tx_buffer_entries + + +class Handle(BYTE): + pass + + +class HardwareInfoEx(Structure): + """ + Structure including hardware information about the USB-CANmodul. + This structure is used with the method :meth:`UcanServer.get_hardware_info`. + + .. seealso:: :meth:`UcanServer.get_hardware_info` + """ + _pack_ = 1 + _fields_ = [ + ("m_dwSize", DWORD), # size of this structure (only used internally) + ("m_UcanHandle", Handle), # USB-CAN-Handle assigned by the DLL + ("m_bDeviceNr", BYTE), # device number of the USB-CANmodul + ("m_dwSerialNr", DWORD), # serial number from USB-CANmodul + ("m_dwFwVersionEx", DWORD), # version of firmware + ("m_dwProductCode", DWORD), # product code (see enum :class:`ProductCode`) + # unique ID (available since V5.01) !!! m_dwSize must be >= HWINFO_SIZE_V2 + ("m_dwUniqueId0", DWORD), + ("m_dwUniqueId1", DWORD), + ("m_dwUniqueId2", DWORD), + ("m_dwUniqueId3", DWORD), + ("m_dwFlags", DWORD), # additional flags + ] + + def __init__(self): + super(HardwareInfoEx, self).__init__(sizeof(HardwareInfoEx)) + + def __eq__(self, other): + if not isinstance(other, HardwareInfoEx): + return False + + return self.device_number == other.device_number and self.serial == other.serial and \ + self.fw_version == other.fw_version and self.product_code == other.product_code and \ + self.unique_id == other.unique_id and self.flags == other.flags + + @property + def device_number(self): return self.m_bDeviceNr + + @property + def serial(self): return self.m_dwSerialNr + + @property + def fw_version(self): return self.m_dwFwVersionEx + + @property + def product_code(self): return self.m_dwProductCode + + @property + def unique_id(self): return self.m_dwUniqueId0, self.m_dwUniqueId1, self.m_dwUniqueId2, self.m_dwUniqueId3 + + @property + def flags(self): return self.m_dwFlags + + +# void PUBLIC UcanCallbackFktEx (Handle UcanHandle_p, DWORD dwEvent_p, +# BYTE bChannel_p, void* pArg_p); +CallbackFktEx = FUNCTYPE(None, Handle, DWORD, BYTE, LPVOID) + + +class HardwareInitInfo(Structure): + """ + Structure including information about the enumeration of USB-CANmoduls. + + .. seealso:: :meth:`UcanServer.enumerate_hardware` + + .. note:: This structure is only used internally. + """ + _pack_ = 1 + _fields_ = [ + ("m_dwSize", DWORD), # size of this structure + ("m_fDoInitialize", BOOL), # specifies if the found module should be initialized by the DLL + ("m_pUcanHandle", Handle), # pointer to variable receiving the USB-CAN-Handle + ("m_fpCallbackFktEx", CallbackFktEx), # pointer to callback function + ("m_pCallbackArg", LPVOID), # pointer to user defined parameter for callback function + ("m_fTryNext", BOOL), # specifies if a further module should be found + ] + + +class ChannelInfo(Structure): + """ + Structure including CAN channel information. + This structure is used with the method :meth:`UcanServer.get_hardware_info`. + + .. seealso:: :meth:`UcanServer.get_hardware_info` + """ + _pack_ = 1 + _fields_ = [ + ("m_dwSize", DWORD), # size of this structure + ("m_bMode", BYTE), # operation mode of CAN controller (see enum :class:`Mode`) + ("m_bBTR0", BYTE), # Bus Timing Register 0 (see enum :class:`Baudrate`) + ("m_bBTR1", BYTE), # Bus Timing Register 1 (see enum :class:`Baudrate`) + ("m_bOCR", BYTE), # Output Control Register (see enum :class:`OutputControl`) + ("m_dwAMR", DWORD), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) + ("m_dwACR", DWORD), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) + ("m_dwBaudrate", DWORD), # Baudrate Register for all systec USB-CANmoduls + # (see enum :class:`BaudrateEx`) + ("m_fCanIsInit", BOOL), # True if the CAN interface is initialized, otherwise false + ("m_wCanStatus", WORD), # CAN status (same as received by method :meth:`UcanServer.get_status`) + ] + + def __init__(self): + super(ChannelInfo, self).__init__(sizeof(ChannelInfo)) + + def __eq__(self, other): + if not isinstance(other, ChannelInfo): + return False + + return self.mode == other.mode and self.BTR == other.BTR and self.OCR == other.OCR and \ + self.AMR == other.AMR and self.ACR == other.ACR and self.baudrate == other.baudrate and \ + self.can_is_init == other.can_is_init and self.can_status == other.can_status + + @property + def mode(self): return self.m_bMode + + @property + def BTR(self): return self.m_bBTR0 << 8 | self.m_bBTR1 + + @property + def OCR(self): return self.m_bOCR + + @property + def AMR(self): return self.m_dwAMR + + @property + def ACR(self): return self.m_dwACR + + @property + def baudrate(self): return self.m_dwBaudrate + + @property + def can_is_init(self): return self.m_fCanIsInit + + @property + def can_status(self): return self.m_wCanStatus + + +class MsgCountInfo(Structure): + """ + Structure including the number of sent and received CAN messages. + This structure is used with the method :meth:`UcanServer.get_msg_count_info`. + + .. seealso:: :meth:`UcanServer.get_msg_count_info` + + .. note:: This structure is only used internally. + """ + _fields_ = [ + ("m_wSentMsgCount", WORD), # number of sent CAN messages + ("m_wRecvdMsgCount", WORD), # number of received CAN messages + ] + + @property + def sent_msg_count(self): return self.m_wSentMsgCount + + @property + def recv_msg_count(self): return self.m_wRecvdMsgCount + + +# void (PUBLIC *ConnectControlFktEx) (DWORD dwEvent_p, DWORD dwParam_p, void* pArg_p); +ConnectControlFktEx = FUNCTYPE(None, DWORD, DWORD, LPVOID) + +# typedef void (PUBLIC *EnumCallback) (DWORD dwIndex_p, BOOL fIsUsed_p, +# HardwareInfoEx* pHwInfoEx_p, HardwareInitInfo* pInitInfo_p, void* pArg_p); +EnumCallback = FUNCTYPE(None, DWORD, BOOL, POINTER(HardwareInfoEx), POINTER(HardwareInitInfo), LPVOID) diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py new file mode 100644 index 000000000..e42c187eb --- /dev/null +++ b/can/interfaces/systec/ucan.py @@ -0,0 +1,1073 @@ +# coding: utf-8 + +import logging +import sys + +from ctypes import byref +from ctypes import c_wchar_p as LPWSTR + +from .constants import * +from .structures import * +from .exceptions import * + +log = logging.getLogger("can.systec") + + +def check_valid_rx_can_msg(result): + """ + Checks if function :meth:`UcanServer.read_can_msg` returns a valid CAN message. + + :param ReturnCode result: Error code of the function. + :return: True if a valid CAN messages was received, otherwise False. + :rtype: bool + """ + return (result.value == ReturnCode.SUCCESSFUL) or (result.value > ReturnCode.WARNING) + + +def check_tx_ok(result): + """ + Checks if function :meth:`UcanServer.write_can_msg` successfully wrote CAN message(s). + + While using :meth:`UcanServer.write_can_msg_ex` the number of sent CAN messages can be less than + the number of CAN messages which should be sent. + + :param ReturnCode result: Error code of the function. + :return: True if CAN message(s) was(were) written successfully, otherwise False. + :rtype: bool + + .. :seealso: :const:`ReturnCode.WARN_TXLIMIT` + """ + return (result.value == ReturnCode.SUCCESSFUL) or (result.value > ReturnCode.WARNING) + + +def check_tx_success(result): + """ + Checks if function :meth:`UcanServer.write_can_msg_ex` successfully wrote all CAN message(s). + + :param ReturnCode result: Error code of the function. + :return: True if CAN message(s) was(were) written successfully, otherwise False. + :rtype: bool + """ + return result.value == ReturnCode.SUCCESSFUL + + +def check_tx_not_all(result): + """ + Checks if function :meth:`UcanServer.write_can_msg_ex` did not sent all CAN messages. + + :param ReturnCode result: Error code of the function. + :return: True if not all CAN messages were written, otherwise False. + :rtype: bool + """ + return result.value == ReturnCode.WARN_TXLIMIT + + +def check_warning(result): + """ + Checks if any function returns a warning. + + :param ReturnCode result: Error code of the function. + :return: True if a function returned warning, otherwise False. + :rtype: bool + """ + return result.value >= ReturnCode.WARNING + + +def check_error(result): + """ + Checks if any function returns an error from USB-CAN-library. + + :param ReturnCode result: Error code of the function. + :return: True if a function returned error, otherwise False. + :rtype: bool + """ + return (result.value != ReturnCode.SUCCESSFUL) and (result.value < ReturnCode.WARNING) + + +def check_error_cmd(result): + """ + Checks if any function returns an error from firmware in USB-CANmodul. + + :param ReturnCode result: Error code of the function. + :return: True if a function returned error from firmware, otherwise False. + :rtype: bool + """ + return (result.value >= ReturnCode.ERRCMD) and (result.value < ReturnCode.WARNING) + + +def check_result(result, func, arguments): + if check_warning(result) and (result.value != ReturnCode.WARN_NODATA): + log.warning(UcanWarning(result, func, arguments)) + elif check_error(result): + if check_error_cmd(result): + raise UcanCmdError(result, func, arguments) + else: + raise UcanError(result, func, arguments) + return result + + +if os.name != "nt": + log.warning("SYSTEC ucan library does not work on %s platform.", sys.platform) +else: + from ctypes import WinDLL + + try: + # Select the proper dll architecture + lib = WinDLL('usbcan64.dll' if sys.maxsize > 2 ** 32 else 'usbcan32.dll') + + # BOOL PUBLIC UcanSetDebugMode (DWORD dwDbgLevel_p, _TCHAR* pszFilePathName_p, DWORD dwFlags_p); + UcanSetDebugMode = lib.UcanSetDebugMode + UcanSetDebugMode.restype = BOOL + UcanSetDebugMode.argtypes = [DWORD, LPWSTR, DWORD] + + # DWORD PUBLIC UcanGetVersionEx (VersionType VerType_p); + UcanGetVersionEx = lib.UcanGetVersionEx + UcanGetVersionEx.restype = DWORD + UcanGetVersionEx.argtypes = [VersionType] + + # DWORD PUBLIC UcanGetFwVersion (Handle UcanHandle_p); + UcanGetFwVersion = lib.UcanGetFwVersion + UcanGetFwVersion.restype = DWORD + UcanGetFwVersion.argtypes = [Handle] + + # BYTE PUBLIC UcanInitHwConnectControlEx (ConnectControlFktEx fpConnectControlFktEx_p, void* pCallbackArg_p); + UcanInitHwConnectControlEx = lib.UcanInitHwConnectControlEx + UcanInitHwConnectControlEx.restype = ReturnCode + UcanInitHwConnectControlEx.argtypes = [ConnectControlFktEx, LPVOID] + UcanInitHwConnectControlEx.errcheck = check_result + + # BYTE PUBLIC UcanDeinitHwConnectControl (void) + UcanDeinitHwConnectControl = lib.UcanDeinitHwConnectControl + UcanDeinitHwConnectControl.restype = ReturnCode + UcanDeinitHwConnectControl.argtypes = [] + UcanDeinitHwConnectControl.errcheck = check_result + + # DWORD PUBLIC UcanEnumerateHardware (EnumCallback fpCallback_p, void* pCallbackArg_p, + # BOOL fEnumUsedDevs_p, + # BYTE bDeviceNrLow_p, BYTE bDeviceNrHigh_p, + # DWORD dwSerialNrLow_p, DWORD dwSerialNrHigh_p, + # DWORD dwProductCodeLow_p, DWORD dwProductCodeHigh_p); + UcanEnumerateHardware = lib.UcanEnumerateHardware + UcanEnumerateHardware.restype = DWORD + UcanEnumerateHardware.argtypes = [EnumCallback, LPVOID, BOOL, BYTE, BYTE, DWORD, DWORD, DWORD, DWORD] + + # BYTE PUBLIC UcanInitHardwareEx (Handle* pUcanHandle_p, BYTE bDeviceNr_p, + # CallbackFktEx fpCallbackFktEx_p, void* pCallbackArg_p); + UcanInitHardwareEx = lib.UcanInitHardwareEx + UcanInitHardwareEx.restype = ReturnCode + UcanInitHardwareEx.argtypes = [POINTER(Handle), BYTE, CallbackFktEx, LPVOID] + UcanInitHardwareEx.errcheck = check_result + + # BYTE PUBLIC UcanInitHardwareEx2 (Handle* pUcanHandle_p, DWORD dwSerialNr_p, + # CallbackFktEx fpCallbackFktEx_p, void* pCallbackArg_p); + UcanInitHardwareEx2 = lib.UcanInitHardwareEx2 + UcanInitHardwareEx2.restype = ReturnCode + UcanInitHardwareEx2.argtypes = [POINTER(Handle), DWORD, CallbackFktEx, LPVOID] + UcanInitHardwareEx2.errcheck = check_result + + # BYTE PUBLIC UcanGetModuleTime (Handle UcanHandle_p, DWORD* pdwTime_p); + UcanGetModuleTime = lib.UcanGetModuleTime + UcanGetModuleTime.restype = ReturnCode + UcanGetModuleTime.argtypes = [Handle, POINTER(DWORD)] + UcanGetModuleTime.errcheck = check_result + + # BYTE PUBLIC UcanGetHardwareInfoEx2 (Handle UcanHandle_p, + # HardwareInfoEx* pHwInfo_p, + # ChannelInfo* pCanInfoCh0_p, ChannelInfo* pCanInfoCh1_p); + UcanGetHardwareInfoEx2 = lib.UcanGetHardwareInfoEx2 + UcanGetHardwareInfoEx2.restype = ReturnCode + UcanGetHardwareInfoEx2.argtypes = [Handle, POINTER(HardwareInfoEx), POINTER(ChannelInfo), + POINTER(ChannelInfo)] + UcanGetHardwareInfoEx2.errcheck = check_result + + # BYTE PUBLIC UcanInitCanEx2 (Handle UcanHandle_p, BYTE bChannel_p, tUcaninit_canParam* pinit_canParam_p); + UcanInitCanEx2 = lib.UcanInitCanEx2 + UcanInitCanEx2.restype = ReturnCode + UcanInitCanEx2.argtypes = [Handle, BYTE, POINTER(InitCanParam)] + UcanInitCanEx2.errcheck = check_result + + # BYTE PUBLIC UcanSetBaudrateEx (Handle UcanHandle_p, + # BYTE bChannel_p, BYTE bBTR0_p, BYTE bBTR1_p, DWORD dwBaudrate_p); + UcanSetBaudrateEx = lib.UcanSetBaudrateEx + UcanSetBaudrateEx.restype = ReturnCode + UcanSetBaudrateEx.argtypes = [Handle, BYTE, BYTE, BYTE, DWORD] + UcanSetBaudrateEx.errcheck = check_result + + # BYTE PUBLIC UcanSetAcceptanceEx (Handle UcanHandle_p, BYTE bChannel_p, + # DWORD dwAMR_p, DWORD dwACR_p); + UcanSetAcceptanceEx = lib.UcanSetAcceptanceEx + UcanSetAcceptanceEx.restype = ReturnCode + UcanSetAcceptanceEx.argtypes = [Handle, BYTE, DWORD, DWORD] + UcanSetAcceptanceEx.errcheck = check_result + + # BYTE PUBLIC UcanResetCanEx (Handle UcanHandle_p, BYTE bChannel_p, DWORD dwResetFlags_p); + UcanResetCanEx = lib.UcanResetCanEx + UcanResetCanEx.restype = ReturnCode + UcanResetCanEx.argtypes = [Handle, BYTE, DWORD] + UcanResetCanEx.errcheck = check_result + + # BYTE PUBLIC UcanReadCanMsgEx (Handle UcanHandle_p, BYTE* pbChannel_p, + # CanMsg* pCanMsg_p, DWORD* pdwCount_p); + UcanReadCanMsgEx = lib.UcanReadCanMsgEx + UcanReadCanMsgEx.restype = ReturnCode + UcanReadCanMsgEx.argtypes = [Handle, POINTER(BYTE), POINTER(CanMsg), POINTER(DWORD)] + UcanReadCanMsgEx.errcheck = check_result + + # BYTE PUBLIC UcanWriteCanMsgEx (Handle UcanHandle_p, BYTE bChannel_p, + # CanMsg* pCanMsg_p, DWORD* pdwCount_p); + UcanWriteCanMsgEx = lib.UcanWriteCanMsgEx + UcanWriteCanMsgEx.restype = ReturnCode + UcanWriteCanMsgEx.argtypes = [Handle, BYTE, POINTER(CanMsg), POINTER(DWORD)] + UcanWriteCanMsgEx.errcheck = check_result + + # BYTE PUBLIC UcanGetStatusEx (Handle UcanHandle_p, BYTE bChannel_p, Status* pStatus_p); + UcanGetStatusEx = lib.UcanGetStatusEx + UcanGetStatusEx.restype = ReturnCode + UcanGetStatusEx.argtypes = [Handle, BYTE, POINTER(Status)] + UcanGetStatusEx.errcheck = check_result + + # BYTE PUBLIC UcanGetMsgCountInfoEx (Handle UcanHandle_p, BYTE bChannel_p, + # MsgCountInfo* pMsgCountInfo_p); + UcanGetMsgCountInfoEx = lib.UcanGetMsgCountInfoEx + UcanGetMsgCountInfoEx.restype = ReturnCode + UcanGetMsgCountInfoEx.argtypes = [Handle, BYTE, POINTER(MsgCountInfo)] + UcanGetMsgCountInfoEx.errcheck = check_result + + # BYTE PUBLIC UcanGetMsgPending (Handle UcanHandle_p, + # BYTE bChannel_p, DWORD dwFlags_p, DWORD* pdwPendingCount_p); + UcanGetMsgPending = lib.UcanGetMsgPending + UcanGetMsgPending.restype = ReturnCode + UcanGetMsgPending.argtypes = [Handle, BYTE, DWORD, POINTER(DWORD)] + UcanGetMsgPending.errcheck = check_result + + # BYTE PUBLIC UcanGetCanErrorCounter (Handle UcanHandle_p, + # BYTE bChannel_p, DWORD* pdwTxErrorCounter_p, DWORD* pdwRxErrorCounter_p); + UcanGetCanErrorCounter = lib.UcanGetCanErrorCounter + UcanGetCanErrorCounter.restype = ReturnCode + UcanGetCanErrorCounter.argtypes = [Handle, BYTE, POINTER(DWORD), POINTER(DWORD)] + UcanGetCanErrorCounter.errcheck = check_result + + # BYTE PUBLIC UcanSetTxTimeout (Handle UcanHandle_p, + # BYTE bChannel_p, DWORD dwTxTimeout_p); + UcanSetTxTimeout = lib.UcanSetTxTimeout + UcanSetTxTimeout.restype = ReturnCode + UcanSetTxTimeout.argtypes = [Handle, BYTE, DWORD] + UcanSetTxTimeout.errcheck = check_result + + # BYTE PUBLIC UcanDeinitCanEx (Handle UcanHandle_p, BYTE bChannel_p); + UcanDeinitCanEx = lib.UcanDeinitCanEx + UcanDeinitCanEx.restype = ReturnCode + UcanDeinitCanEx.argtypes = [Handle, BYTE] + UcanDeinitCanEx.errcheck = check_result + + # BYTE PUBLIC UcanDeinitHardware (Handle UcanHandle_p); + UcanDeinitHardware = lib.UcanDeinitHardware + UcanDeinitHardware.restype = ReturnCode + UcanDeinitHardware.argtypes = [Handle] + UcanDeinitHardware.errcheck = check_result + + # BYTE PUBLIC UcanDefineCyclicCanMsg (Handle UcanHandle_p, + # BYTE bChannel_p, CanMsg* pCanMsgList_p, DWORD dwCount_p); + UcanDefineCyclicCanMsg = lib.UcanDefineCyclicCanMsg + UcanDefineCyclicCanMsg.restype = ReturnCode + UcanDefineCyclicCanMsg.argtypes = [Handle, BYTE, POINTER(CanMsg), DWORD] + UcanDefineCyclicCanMsg.errcheck = check_result + + # BYTE PUBLIC UcanReadCyclicCanMsg (Handle UcanHandle_p, + # BYTE bChannel_p, CanMsg* pCanMsgList_p, DWORD* pdwCount_p); + UcanReadCyclicCanMsg = lib.UcanReadCyclicCanMsg + UcanReadCyclicCanMsg.restype = ReturnCode + UcanReadCyclicCanMsg.argtypes = [Handle, BYTE, POINTER(CanMsg), POINTER(DWORD)] + UcanReadCyclicCanMsg.errcheck = check_result + + # BYTE PUBLIC UcanEnableCyclicCanMsg (Handle UcanHandle_p, + # BYTE bChannel_p, DWORD dwFlags_p); + UcanEnableCyclicCanMsg = lib.UcanEnableCyclicCanMsg + UcanEnableCyclicCanMsg.restype = ReturnCode + UcanEnableCyclicCanMsg.argtypes = [Handle, BYTE, DWORD] + UcanEnableCyclicCanMsg.errcheck = check_result + + except Exception as ex: + log.warning("Cannot load SYSTEC ucan library: %s.", ex) + + +class UcanServer(object): + """ + UcanServer is a Python wrapper class for using the usbcan32.dll / usbcan64.dll. + """ + _modules_found = [] + _connect_control_ref = None + + def __init__(self): + self._handle = Handle(INVALID_HANDLE) + self._is_initialized = False + self._hw_is_initialized = False + self._ch_is_initialized = { + Channel.CHANNEL_CH0: False, + Channel.CHANNEL_CH1: False + } + self._callback_ref = CallbackFktEx(self._callback) + if self._connect_control_ref is None: + self._connect_control_ref = ConnectControlFktEx(self._connect_control) + UcanInitHwConnectControlEx(self._connect_control_ref, None) + + @property + def is_initialized(self): + """ + Returns whether hardware interface is initialized. + + :return: True if initialized, otherwise False. + :rtype: bool + """ + return self._is_initialized + + @property + def is_can0_initialized(self): + """ + Returns whether CAN interface for channel 0 is initialized. + + :return: True if initialized, otherwise False. + :rtype: bool + """ + return self._ch_is_initialized[Channel.CHANNEL_CH0] + + @property + def is_can1_initialized(self): + """ + Returns whether CAN interface for channel 1 is initialized. + + :return: True if initialized, otherwise False. + :rtype: bool + """ + return self._ch_is_initialized[Channel.CHANNEL_CH1] + + @classmethod + def _enum_callback(cls, index, is_used, hw_info_ex, init_info, arg): + cls._modules_found.append((index, bool(is_used), hw_info_ex.contents, init_info.contents)) + + @classmethod + def enumerate_hardware(cls, device_number_low=0, device_number_high=-1, serial_low=0, serial_high=-1, + product_code_low=0, product_code_high=-1, enum_used_devices=False): + cls._modules_found = [] + UcanEnumerateHardware(cls._enum_callback_ref, None, enum_used_devices, + device_number_low, device_number_high, + serial_low, serial_high, + product_code_low, product_code_high) + return cls._modules_found + + def init_hardware(self, serial=None, device_number=ANY_MODULE): + """ + Initializes the device with the corresponding serial or device number. + + :param int or None serial: Serial number of the USB-CANmodul. + :param int device_number: Device number (0 – 254, or :const:`ANY_MODULE` for the first device). + """ + if not self._hw_is_initialized: + # initialize hardware either by device number or serial + if serial is None: + UcanInitHardwareEx(byref(self._handle), device_number, self._callback_ref, None) + else: + UcanInitHardwareEx2(byref(self._handle), serial, self._callback_ref, None) + self._hw_is_initialized = True + + def init_can(self, channel=Channel.CHANNEL_CH0, BTR=Baudrate.BAUD_1MBit, baudrate=BaudrateEx.BAUDEX_USE_BTR01, + AMR=AMR_ALL, ACR=ACR_ALL, mode=Mode.MODE_NORMAL, OCR=OutputControl.OCR_DEFAULT, + rx_buffer_entries=DEFAULT_BUFFER_ENTRIES, tx_buffer_entries=DEFAULT_BUFFER_ENTRIES): + """ + Initializes a specific CAN channel of a device. + + :param int channel: CAN channel to be initialized (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int BTR: + Baud rate register BTR0 as high byte, baud rate register BTR1 as low byte (see enum :class:`Baudrate`). + :param int baudrate: Baud rate register for all systec USB-CANmoduls (see enum :class:`BaudrateEx`). + :param int AMR: Acceptance filter mask (see method :meth:`set_acceptance`). + :param int ACR: Acceptance filter code (see method :meth:`set_acceptance`). + :param int mode: Transmission mode of CAN channel (see enum :class:`Mode`). + :param int OCR: Output Control Register (see enum :class:`OutputControl`). + :param int rx_buffer_entries: The number of maximum entries in the receive buffer. + :param int tx_buffer_entries: The number of maximum entries in the transmit buffer. + """ + if not self._ch_is_initialized.get(channel, False): + init_param = InitCanParam(mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries) + UcanInitCanEx2(self._handle, channel, init_param) + self._ch_is_initialized[channel] = True + + def read_can_msg(self, channel, count): + """ + Reads one or more CAN-messages from the buffer of the specified CAN channel. + + :param int channel: + CAN channel to read from (:data:`Channel.CHANNEL_CH0`, :data:`Channel.CHANNEL_CH1`, + :data:`Channel.CHANNEL_ANY`). + :param int count: The number of CAN messages to be received. + :return: Tuple with list of CAN message/s received and the CAN channel where the read CAN messages came from. + :rtype: tuple(list(CanMsg), int) + """ + c_channel = BYTE(channel) + c_can_msg = (CanMsg * count)() + c_count = DWORD(count) + UcanReadCanMsgEx(self._handle, byref(c_channel), c_can_msg, byref(c_count)) + return c_can_msg[:c_count.value], c_channel.value + + def write_can_msg(self, channel, can_msg): + """ + Transmits one ore more CAN messages through the specified CAN channel of the device. + + :param int channel: + CAN channel, which is to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param list(CanMsg) can_msg: List of CAN message structure (see structure :class:`CanMsg`). + :return: The number of successfully transmitted CAN messages. + :rtype: int + """ + c_can_msg = (CanMsg * len(can_msg))(*can_msg) + c_count = DWORD(len(can_msg)) + UcanWriteCanMsgEx(self._handle, channel, c_can_msg, c_count) + return c_count + + def set_baudrate(self, channel, BTR, baudarate): + """ + This function is used to configure the baud rate of specific CAN channel of a device. + + :param int channel: + CAN channel, which is to be configured (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int BTR: + Baud rate register BTR0 as high byte, baud rate register BTR1 as low byte (see enum :class:`Baudrate`). + :param int baudarate: Baud rate register for all systec USB-CANmoduls (see enum :class:`BaudrateEx`>). + """ + UcanSetBaudrateEx(self._handle, channel, BTR >> 8, BTR, baudarate) + + def set_acceptance(self, channel=Channel.CHANNEL_CH0, AMR=AMR_ALL, ACR=ACR_ALL): + """ + This function is used to change the acceptance filter values for a specific CAN channel on a device. + + :param int channel: + CAN channel, which is to be configured (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int AMR: Acceptance filter mask (AMR). + :param int ACR: Acceptance filter code (ACR). + """ + UcanSetAcceptanceEx(self._handle, channel, AMR, ACR) + + def get_status(self, channel=Channel.CHANNEL_CH0): + """ + Returns the error status of a specific CAN channel. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :return: Tuple with CAN and USB status (see structure :class:`Status`). + :rtype: tuple(int, int) + """ + status = Status() + UcanGetStatusEx(self._handle, channel, byref(status)) + return status.can_status, status.usb_status + + def get_msg_count_info(self, channel=Channel.CHANNEL_CH0): + """ + Reads the message counters of the specified CAN channel. + + :param int channel: + CAN channel, which is to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :return: Tuple with number of CAN messages sent and received. + :rtype: tuple(int, int) + """ + msg_count_info = MsgCountInfo() + UcanGetMsgCountInfoEx(self._handle, channel, byref(msg_count_info)) + return msg_count_info.sent_msg_count, msg_count_info.recv_msg_count + + def reset_can(self, channel=Channel.CHANNEL_CH0, flags=ResetFlags.RESET_ALL): + """ + Resets a CAN channel of a device (hardware reset, empty buffer, and so on). + + :param int channel: CAN channel, to be reset (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int flags: Flags defines what should be reset (see enum :class:`ResetFlags`). + """ + UcanResetCanEx(self._handle, channel, flags) + + def get_hardware_info(self): + """ + Returns the extended hardware information of a device. With multi-channel USB-CANmoduls the information for + both CAN channels are returned separately. + + :return: + Tuple with extended hardware information structure (see structure :class:`HardwareInfoEx`) and + structures with information of CAN channel 0 and 1 (see structure :class:`ChannelInfo`). + :rtype: tuple(HardwareInfoEx, ChannelInfo, ChannelInfo) + """ + hw_info_ex = HardwareInfoEx() + can_info_ch0, can_info_ch1 = ChannelInfo(), ChannelInfo() + UcanGetHardwareInfoEx2(self._handle, byref(hw_info_ex), byref(can_info_ch0), byref(can_info_ch1)) + return hw_info_ex, can_info_ch0, can_info_ch1 + + def get_fw_version(self): + """ + Returns the firmware version number of the device. + + :return: Firmware version number. + :rtype: int + """ + return UcanGetFwVersion(self._handle) + + def define_cyclic_can_msg(self, channel, can_msg=None): + """ + Defines a list of CAN messages for automatic transmission. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param list(CanMsg) can_msg: + List of CAN messages (up to 16, see structure :class:`CanMsg`), or None to delete an older list. + """ + if can_msg is not None: + c_can_msg = (CanMsg * len(can_msg))(*can_msg) + c_count = DWORD(len(can_msg)) + else: + c_can_msg = CanMsg() + c_count = 0 + UcanDefineCyclicCanMsg(self._handle, channel, c_can_msg, c_count) + + def read_cyclic_can_msg(self, channel, count): + """ + Reads back the list of CAN messages for automatically sending. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int count: The number of cyclic CAN messages to be received. + :return: List of received CAN messages (up to 16, see structure :class:`CanMsg`). + :rtype: list(CanMsg) + """ + c_channel = BYTE(channel) + c_can_msg = (CanMsg * count)() + c_count = DWORD(count) + UcanReadCyclicCanMsg(self._handle, byref(c_channel), c_can_msg, c_count) + return c_can_msg[:c_count.value] + + def enable_cyclic_can_msg(self, channel, flags): + """ + Enables or disables the automatically sending. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int flags: Flags for enabling or disabling (see enum :class:`CyclicFlags`). + """ + UcanEnableCyclicCanMsg(self._handle, channel, flags) + + def get_msg_pending(self, channel, flags): + """ + Returns the number of pending CAN messages. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param int flags: Flags specifies which buffers should be checked (see enum :class:`PendingFlags`). + :return: The number of pending messages. + :rtype: int + """ + count = DWORD(0) + UcanGetMsgPending(self._handle, channel, flags, byref(count)) + return count.value + + def get_can_error_counter(self, channel): + """ + Reads the current value of the error counters within the CAN controller. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :return: Tuple with the TX and RX error counter. + :rtype: tuple(int, int) + + .. note:: Only available for systec USB-CANmoduls (NOT for GW-001 and GW-002 !!!). + """ + tx_error_counter = DWORD(0) + rx_error_counter = DWORD(0) + UcanGetCanErrorCounter(self._handle, channel, byref(tx_error_counter), byref(rx_error_counter)) + return tx_error_counter, rx_error_counter + + def set_tx_timeout(self, channel, timeout): + """ + Sets the transmission timeout. + + :param int channel: CAN channel, to be used (:data:`Channel.CHANNEL_CH0` or :data:`Channel.CHANNEL_CH1`). + :param float timeout: Transmit timeout in seconds (value 0 disables this feature). + """ + UcanSetTxTimeout(self._handle, channel, int(timeout * 1000)) + + def shutdown(self, channel=Channel.CHANNEL_ALL, shutdown_hardware=True): + """ + Shuts down all CAN interfaces and/or the hardware interface. + + :param int channel: + CAN channel, to be used (:data:`Channel.CHANNEL_CH0`, :data:`Channel.CHANNEL_CH1` or + :data:`Channel.CHANNEL_ALL`) + :param bool shutdown_hardware: If true then the hardware interface will be closed too. + """ + # shutdown each channel if it's initialized + for _channel, is_initialized in self._ch_is_initialized.items(): + if is_initialized and (_channel == channel or channel == Channel.CHANNEL_ALL or shutdown_hardware): + UcanDeinitCanEx(self._handle, _channel) + self._ch_is_initialized[_channel] = False + + # shutdown hardware + if self._hw_is_initialized and shutdown_hardware: + UcanDeinitHardware(self._handle) + self._hw_is_initialized = False + self._handle = Handle(INVALID_HANDLE) + + @staticmethod + def get_user_dll_version(): + """ + Returns the version number of the USBCAN-library. + + :return: Software version number. + :rtype: int + """ + return UcanGetVersionEx(VersionType.VER_TYPE_USER_DLL) + + @staticmethod + def set_debug_mode(level, filename, flags=0): + """ + This function enables the creation of a debug log file out of the USBCAN-library. If this + feature has already been activated via the USB-CANmodul Control, the content of the + “old” log file will be copied to the new file. Further debug information will be appended to + the new file. + + :param int level: Debug level (bit format). + :param str filename: File path to debug log file. + :param int flags: Additional flags (bit0: file append mode). + :return: False if logfile not created otherwise True. + :rtype: bool + """ + return UcanSetDebugMode(level, filename, flags) + + @staticmethod + def get_can_status_message(can_status): + """ + Converts a given CAN status value to the appropriate message string. + + :param can_status: CAN status value from method :meth:`get_status` (see enum :class:`CanStatus`) + :return: Status message string. + :rtype: str + """ + status_msgs = { + CanStatus.CANERR_TXMSGLOST: "Transmit message lost", + CanStatus.CANERR_MEMTEST: "Memory test failed", + CanStatus.CANERR_REGTEST: "Register test failed", + CanStatus.CANERR_QXMTFULL: "Transmit queue is full", + CanStatus.CANERR_QOVERRUN: "Receive queue overrun", + CanStatus.CANERR_QRCVEMPTY: "Receive queue is empty", + CanStatus.CANERR_BUSOFF: "Bus Off", + CanStatus.CANERR_BUSHEAVY: "Error Passive", + CanStatus.CANERR_BUSLIGHT: "Warning Limit", + CanStatus.CANERR_OVERRUN: "Rx-buffer is full", + CanStatus.CANERR_XMTFULL: "Tx-buffer is full", + } + return "OK" if can_status == CanStatus.CANERR_OK \ + else ", ".join(msg for status, msg in status_msgs.items() if can_status & status) + + @staticmethod + def get_baudrate_message(baudrate): + """ + Converts a given baud rate value for GW-001/GW-002 to the appropriate message string. + + :param Baudrate baudrate: + Bus Timing Registers, BTR0 in high order byte and BTR1 in low order byte + (see enum :class:`Baudrate`) + :return: Baud rate message string. + :rtype: str + """ + baudrate_msgs = { + Baudrate.BAUD_AUTO: "auto baudrate", + Baudrate.BAUD_10kBit: "10 kBit/sec", + Baudrate.BAUD_20kBit: "20 kBit/sec", + Baudrate.BAUD_50kBit: "50 kBit/sec", + Baudrate.BAUD_100kBit: "100 kBit/sec", + Baudrate.BAUD_125kBit: "125 kBit/sec", + Baudrate.BAUD_250kBit: "250 kBit/sec", + Baudrate.BAUD_500kBit: "500 kBit/sec", + Baudrate.BAUD_800kBit: "800 kBit/sec", + Baudrate.BAUD_1MBit: "1 MBit/s", + Baudrate.BAUD_USE_BTREX: "BTR Ext is used", + } + return baudrate_msgs.get(baudrate, "BTR is unknown (user specific)") + + @staticmethod + def get_baudrate_ex_message(baudrate_ex): + """ + Converts a given baud rate value for systec USB-CANmoduls to the appropriate message string. + + :param BaudrateEx baudrate_ex: Bus Timing Registers (see enum :class:`BaudrateEx`) + :return: Baud rate message string. + :rtype: str + """ + baudrate_ex_msgs = { + Baudrate.BAUDEX_AUTO: "auto baudrate", + Baudrate.BAUDEX_10kBit: "10 kBit/sec", + Baudrate.BAUDEX_SP2_10kBit: "10 kBit/sec", + Baudrate.BAUDEX_20kBit: "20 kBit/sec", + Baudrate.BAUDEX_SP2_20kBit: "20 kBit/sec", + Baudrate.BAUDEX_50kBit: "50 kBit/sec", + Baudrate.BAUDEX_SP2_50kBit: "50 kBit/sec", + Baudrate.BAUDEX_100kBit: "100 kBit/sec", + Baudrate.BAUDEX_SP2_100kBit: "100 kBit/sec", + Baudrate.BAUDEX_125kBit: "125 kBit/sec", + Baudrate.BAUDEX_SP2_125kBit: "125 kBit/sec", + Baudrate.BAUDEX_250kBit: "250 kBit/sec", + Baudrate.BAUDEX_SP2_250kBit: "250 kBit/sec", + Baudrate.BAUDEX_500kBit: "500 kBit/sec", + Baudrate.BAUDEX_SP2_500kBit: "500 kBit/sec", + Baudrate.BAUDEX_800kBit: "800 kBit/sec", + Baudrate.BAUDEX_SP2_800kBit: "800 kBit/sec", + Baudrate.BAUDEX_1MBit: "1 MBit/s", + Baudrate.BAUDEX_SP2_1MBit: "1 MBit/s", + Baudrate.BAUDEX_USE_BTR01: "BTR0/BTR1 is used", + } + return baudrate_ex_msgs.get(baudrate_ex, "BTR is unknown (user specific)") + + @staticmethod + def get_product_code_message(product_code): + product_code_msgs = { + ProductCode.PRODCODE_PID_GW001: "GW-001", + ProductCode.PRODCODE_PID_GW002: "GW-002", + ProductCode.PRODCODE_PID_MULTIPORT: "Multiport CAN-to-USB G3", + ProductCode.PRODCODE_PID_BASIC: "USB-CANmodul1 G3", + ProductCode.PRODCODE_PID_ADVANCED: "USB-CANmodul2 G3", + ProductCode.PRODCODE_PID_USBCAN8: "USB-CANmodul8 G3", + ProductCode.PRODCODE_PID_USBCAN16: "USB-CANmodul16 G3", + ProductCode.PRODCODE_PID_RESERVED3: "Reserved", + ProductCode.PRODCODE_PID_ADVANCED_G4: "USB-CANmodul2 G4", + ProductCode.PRODCODE_PID_BASIC_G4: "USB-CANmodul1 G4", + ProductCode.PRODCODE_PID_RESERVED1: "Reserved", + ProductCode.PRODCODE_PID_RESERVED2: "Reserved", + } + return product_code_msgs.get(product_code & PRODCODE_MASK_PID, "Product code is unknown") + + @classmethod + def convert_to_major_ver(cls, version): + """ + Converts the a version number into the major version. + + :param int version: Version number to be converted. + :return: Major version. + :rtype: int + """ + return version & 0xFF + + @classmethod + def convert_to_minor_ver(cls, version): + """ + Converts the a version number into the minor version. + + :param int version: Version number to be converted. + :return: Minor version. + :rtype: int + """ + return (version & 0xFF00) >> 8 + + @classmethod + def convert_to_release_ver(cls, version): + """ + Converts the a version number into the release version. + + :param int version: Version number to be converted. + :return: Release version. + :rtype: int + """ + return (version & 0xFFFF0000) >> 16 + + @classmethod + def check_version_is_equal_or_higher(cls, version, cmp_major, cmp_minor): + """ + Checks if the version is equal or higher than a specified value. + + :param int version: Version number to be checked. + :param int cmp_major: Major version to be compared with. + :param int cmp_minor: Minor version to be compared with. + :return: True if equal or higher, otherwise False. + :rtype: bool + """ + return (cls.convert_to_major_ver(version) > cmp_major) or \ + (cls.convert_to_major_ver(version) == cmp_major and cls.convert_to_minor_ver(version) >= cmp_minor) + + @classmethod + def check_is_systec(cls, hw_info_ex): + """ + Checks whether the module is a systec USB-CANmodul. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module is a systec USB-CANmodul, otherwise False. + :rtype: bool + """ + return (hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) >= ProductCode.PRODCODE_PID_MULTIPORT + + @classmethod + def check_is_G4(cls, hw_info_ex): + """ + Checks whether the module is an USB-CANmodul of fourth generation (G4). + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module is an USB-CANmodul G4, otherwise False. + :rtype: bool + """ + return hw_info_ex.m_dwProductCode & PRODCODE_PID_G4 + + @classmethod + def check_is_G3(cls, hw_info_ex): + """ + Checks whether the module is an USB-CANmodul of third generation (G3). + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module is an USB-CANmodul G3, otherwise False. + :rtype: bool + """ + return cls.check_is_systec(hw_info_ex) and not cls.check_is_G4(hw_info_ex) + + @classmethod + def check_support_cyclic_msg(cls, hw_info_ex): + """ + Checks whether the module supports automatically transmission of cyclic CAN messages. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support cyclic CAN messages, otherwise False. + :rtype: bool + """ + return cls.check_is_systec(hw_info_ex) and \ + cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 3, 6) + + @classmethod + def check_support_two_channel(cls, hw_info_ex): + """ + Checks whether the module supports two CAN channels (at logical device). + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module (logical device) does support two CAN channels, otherwise False. + :rtype: bool + """ + return cls.check_is_systec(hw_info_ex) and (hw_info_ex.m_dwProductCode & PRODCODE_PID_TWO_CHA) + + @classmethod + def check_support_term_resistor(cls, hw_info_ex): + """ + Checks whether the module supports a termination resistor at the CAN bus. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support a termination resistor. + :rtype: bool + """ + return hw_info_ex.m_dwProductCode & PRODCODE_PID_TERM + + @classmethod + def check_support_user_port(cls, hw_info_ex): + """ + Checks whether the module supports a user I/O port. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module supports a user I/O port, otherwise False. + :rtype: bool + """ + return ((hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) != ProductCode.PRODCODE_PID_BASIC) \ + and ((hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) != ProductCode.PRODCODE_PID_RESERVED1) \ + and cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 2, 16) + + @classmethod + def check_support_rb_user_port(cls, hw_info_ex): + """ + Checks whether the module supports a user I/O port including read back feature. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support a user I/O port including the read back feature, otherwise False. + :rtype: bool + """ + return hw_info_ex.m_dwProductCode & PRODCODE_PID_RBUSER + + @classmethod + def check_support_rb_can_port(cls, hw_info_ex): + """ + Checks whether the module supports a CAN I/O port including read back feature. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support a CAN I/O port including the read back feature, otherwise False. + :rtype: bool + """ + return hw_info_ex.m_dwProductCode & PRODCODE_PID_RBCAN + + @classmethod + def check_support_ucannet(cls, hw_info_ex): + """ + Checks whether the module supports the usage of USB-CANnetwork driver. + + :param HardwareInfoEx hw_info_ex: + Extended hardware information structure (see method :meth:`get_hardware_info`). + :return: True when the module does support the usage of the USB-CANnetwork driver, otherwise False. + :rtype: bool + """ + return cls.check_is_systec(hw_info_ex) and \ + cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 3, 8) + + @classmethod + def calculate_amr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True): + """ + Calculates AMR using CAN-ID range as parameter. + + :param bool is_extended: If True parameters from_id and to_id contains 29-bit CAN-ID. + :param int from_id: First CAN-ID which should be received. + :param int to_id: Last CAN-ID which should be received. + :param bool rtr_only: If True only RTR-Messages should be received, and rtr_too will be ignored. + :param bool rtr_too: If True CAN data frames and RTR-Messages should be received. + :return: Value for AMR. + :rtype: int + """ + return (((from_id ^ to_id) << 3) | (0x7 if rtr_too and not rtr_only else 0x3)) if is_extended else \ + (((from_id ^ to_id) << 21) | (0x1FFFFF if rtr_too and not rtr_only else 0xFFFFF)) + + @classmethod + def calculate_acr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True): + """ + Calculates ACR using CAN-ID range as parameter. + + :param bool is_extended: If True parameters from_id and to_id contains 29-bit CAN-ID. + :param int from_id: First CAN-ID which should be received. + :param int to_id: Last CAN-ID which should be received. + :param bool rtr_only: If True only RTR-Messages should be received, and rtr_too will be ignored. + :param bool rtr_too: If True CAN data frames and RTR-Messages should be received. + :return: Value for ACR. + :rtype: int + """ + return (((from_id & to_id) << 3) | (0x04 if rtr_only else 0)) if is_extended else \ + (((from_id & to_id) << 21) | (0x100000 if rtr_only else 0)) + + def _connect_control(self, event, param, arg): + """ + Is the actual callback function for :meth:`init_hw_connect_control_ex`. + + :param event: + Event (:data:`CbEvent.EVENT_CONNECT`, :data:`CbEvent.EVENT_DISCONNECT` or + :data:`CbEvent.EVENT_FATALDISCON`). + :param param: Additional parameter depending on the event. + - CbEvent.EVENT_CONNECT: always 0 + - CbEvent.EVENT_DISCONNECT: always 0 + - CbEvent.EVENT_FATALDISCON: USB-CAN-Handle of the disconnected module + :param arg: Additional parameter defined with :meth:`init_hardware_ex` (not used in this wrapper class). + """ + log.debug("Event: %s, Param: %s" % (event, param)) + + if event == CbEvent.EVENT_FATALDISCON: + self.fatal_disconnect_event(param) + elif event == CbEvent.EVENT_CONNECT: + self.connect_event() + elif event == CbEvent.EVENT_DISCONNECT: + self.disconnect_event() + + def _callback(self, handle, event, channel, arg): + """ + Is called if a working event occurred. + + :param int handle: USB-CAN-Handle returned by the function :meth:`init_hardware`. + :param int event: Event type. + :param int channel: + CAN channel (:data:`Channel.CHANNEL_CH0`, :data:`Channel.CHANNEL_CH1` or :data:`Channel.CHANNEL_ANY`). + :param arg: Additional parameter defined with :meth:`init_hardware_ex`. + """ + log.debug("Handle: %s, Event: %s, Channel: %s" % (handle, event, channel)) + + if event == CbEvent.EVENT_INITHW: + self.init_hw_event() + elif event == CbEvent.EVENT_init_can: + self.init_can_event(channel) + elif event == CbEvent.EVENT_RECEIVE: + self.can_msg_received_event(channel) + elif event == CbEvent.EVENT_STATUS: + self.status_event(channel) + elif event == CbEvent.EVENT_DEINIT_CAN: + self.deinit_can_event(channel) + elif event == CbEvent.EVENT_DEINITHW: + self.deinit_hw_event() + + def init_hw_event(self): + """ + Event occurs when an USB-CANmodul has been initialized (see method :meth:`init_hardware`). + + .. note:: To be overridden by subclassing. + """ + pass + + def init_can_event(self, channel): + """ + Event occurs when a CAN interface of an USB-CANmodul has been initialized. + + :param int channel: Specifies the CAN channel which was initialized (see method :meth:`init_can`). + + .. note:: To be overridden by subclassing. + """ + pass + + def can_msg_received_event(self, channel): + """ + Event occurs when at leas one CAN message has been received. + + Call the method :meth:`read_can_msg` to receive the CAN messages. + + :param int channel: Specifies the CAN channel which received CAN messages. + + .. note:: To be overridden by subclassing. + """ + pass + + def status_event(self, channel): + """ + Event occurs when the error status of a module has been changed. + + Call the method :meth:`get_status` to receive the error status. + + :param int channel: Specifies the CAN channel which status has been changed. + + .. note:: To be overridden by subclassing. + """ + pass + + def deinit_can_event(self, channel): + """ + Event occurs when a CAN interface has been deinitialized (see method :meth:`shutdown`). + + :param int channel: Specifies the CAN channel which status has been changed. + + .. note:: To be overridden by subclassing. + """ + pass + + def deinit_hw_event(self): + """ + Event occurs when an USB-CANmodul has been deinitialized (see method :meth:`shutdown`). + + .. note:: To be overridden by subclassing. + """ + pass + + def connect_event(self): + """ + Event occurs when a new USB-CANmodul has been connected to the host. + + .. note:: To be overridden by subclassing. + """ + pass + + def disconnect_event(self): + """ + Event occurs when an USB-CANmodul has been disconnected from the host. + + .. note:: To be overridden by subclassing. + """ + pass + + def fatal_disconnect_event(self, device_number): + """ + Event occurs when an USB-CANmodul has been disconnected from the host which was currently initialized. + + No method can be called for this module. + + :param int device_number: The device number which was disconnected. + + .. note:: To be overridden by subclassing. + """ + pass + + +UcanServer._enum_callback_ref = EnumCallback(UcanServer._enum_callback) diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py new file mode 100644 index 000000000..6426e883f --- /dev/null +++ b/can/interfaces/systec/ucanbus.py @@ -0,0 +1,268 @@ +# coding: utf-8 + +import logging +from threading import Event + +from can import BusABC, BusState, Message + +from .constants import * +from .structures import * +from .ucan import UcanServer + +log = logging.getLogger('can.systec') + + +class Ucan(UcanServer): + """ + Wrapper around UcanServer to read messages with timeout using events. + """ + def __init__(self): + super(Ucan, self).__init__() + self._msg_received_event = Event() + + def can_msg_received_event(self, channel): + self._msg_received_event.set() + + def read_can_msg(self, channel, count, timeout): + self._msg_received_event.clear() + if self.get_msg_pending(channel, PendingFlags.PENDING_FLAG_RX_DLL) == 0: + if not self._msg_received_event.wait(timeout): + return None, False + return super(Ucan, self).read_can_msg(channel, 1) + + +class UcanBus(BusABC): + """ + The CAN Bus implemented for the SYSTEC interface. + """ + + BITRATES = { + 10000: Baudrate.BAUD_10kBit, + 20000: Baudrate.BAUD_20kBit, + 50000: Baudrate.BAUD_50kBit, + 100000: Baudrate.BAUD_100kBit, + 125000: Baudrate.BAUD_125kBit, + 250000: Baudrate.BAUD_250kBit, + 500000: Baudrate.BAUD_500kBit, + 800000: Baudrate.BAUD_800kBit, + 1000000: Baudrate.BAUD_1MBit + } + + def __init__(self, channel, can_filters=None, **config): + """ + :param int channel: + The Channel id to create this bus with. + + :param list can_filters: + See :meth:`can.BusABC.set_filters`. + + Backend Configuration + + :param int bitrate: + Channel bitrate in bit/s. + Default is 500000. + + :param int device_number: + The device number of the USB-CAN. + Valid values: 0 through 254. Special value 255 is reserved to detect the first connected device (should only + be used, in case only one module is connected to the computer). + Default is 255. + + :param can.bus.BusState state: + BusState of the channel. + Default is ACTIVE. + + :param bool receive_own_messages: + If messages transmitted should also be received back. + Default is False. + + :param int rx_buffer_entries: + The maximum number of entries in the receive buffer. + Default is 4096. + + :param int tx_buffer_entries: + The maximum number of entries in the transmit buffer. + Default is 4096. + + :raises ValueError: + If invalid input parameter were passed. + + :raises can.CanError: + If hardware or CAN interface initialization failed. + """ + try: + self._ucan = Ucan() + except Exception: + raise ImportError("The SYSTEC ucan library has not been initialized.") + + self.channel = int(channel) + device_number = int(config.get('device_number', ANY_MODULE)) + + # configuration options + bitrate = config.get('bitrate', 500000) + if bitrate not in self.BITRATES: + raise ValueError("Invalid bitrate {}".format(bitrate)) + + state = config.get('state', BusState.ACTIVE) + if state is BusState.ACTIVE or BusState.PASSIVE: + self._state = state + else: + raise ValueError("BusState must be Active or Passive") + + # get parameters + self._params = { + "mode": Mode.MODE_NORMAL | + (Mode.MODE_TX_ECHO if config.get('receive_own_messages') else 0) | + (Mode.MODE_LISTEN_ONLY if state is BusState.PASSIVE else 0), + "BTR": self.BITRATES[bitrate] + } + # get extra parameters + if config.get("rx_buffer_entries"): + self._params["rx_buffer_entries"] = int(config.get("rx_buffer_entries")) + if config.get("tx_buffer_entries"): + self._params["tx_buffer_entries"] = int(config.get("tx_buffer_entries")) + + self._ucan.init_hardware(device_number=device_number) + self._ucan.init_can(self.channel, **self._params) + hw_info_ex, _, _ = self._ucan.get_hardware_info() + self.channel_info = '%s, S/N %s, CH %s, BTR %s' % ( + self._ucan.get_product_code_message(hw_info_ex.product_code), + hw_info_ex.serial, + self.channel, + self._ucan.get_baudrate_message(self.BITRATES[bitrate]) + ) + super(UcanBus, self).__init__(channel=channel, can_filters=can_filters, **config) + + def _recv_internal(self, timeout): + message, _ = self._ucan.read_can_msg(self.channel, 1, timeout) + if not message: + return None, False + + msg = Message(timestamp=float(message[0].time) / 1000.0, + is_remote_frame=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_RTR), + extended_id=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_EXT), + arbitration_id=message[0].id, + dlc=len(message[0].data), + data=message[0].data) + return msg, self._is_filtered + + def send(self, msg, timeout=None): + """ + Sends one CAN message. + + When a transmission timeout is set the firmware tries to send + a message within this timeout. If it could not be sent the firmware sets + the "auto delete" state. Within this state all transmit CAN messages for + this channel will be deleted automatically for not blocking the other channel. + + :param can.Message msg: + The CAN message. + + :param float timeout: + Transmit timeout in seconds (value 0 switches off the "auto delete") + + :raises can.CanError: + If the message could not be sent. + + """ + if timeout is not None and timeout >= 0: + self._ucan.set_tx_timeout(self.channel, int(timeout * 1000)) + + message = CanMsg(msg.arbitration_id, + MsgFrameFormat.MSG_FF_STD | + (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) | + (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), + msg.data) + self._ucan.write_can_msg(self.channel, [message]) + + @staticmethod + def _detect_available_configs(): + configs = [] + try: + for index, is_used, hw_info_ex, init_info in Ucan.enumerate_hardware(): + configs.append({'interface': 'systec', + 'channel': Channel.CHANNEL_CH0, + 'device_number': hw_info_ex.device_number}) + if Ucan.check_support_two_channel(hw_info_ex): + configs.append({'interface': 'systec', + 'channel': Channel.CHANNEL_CH1, + 'device_number': hw_info_ex.device_number}) + except: + log.warning("The SYSTEC ucan library has not been initialized.") + return configs + + def _apply_filters(self, filters): + if filters and len(filters) == 1: + can_id = filters[0]['can_id'] + can_mask = filters[0]['can_mask'] + self._ucan.set_acceptance(self.channel, can_mask, can_id) + self._is_filtered = True + log.info('Hardware filtering on ID 0x%X, mask 0x%X', can_id, can_mask) + else: + self._ucan.set_acceptance(self.channel) + self._is_filtered = False + log.info('Hardware filtering has been disabled') + + def flush_tx_buffer(self): + """ + Flushes the transmit buffer. + + :raises can.CanError: + If flushing of the transmit buffer failed. + """ + log.info('Flushing transmit buffer') + self._ucan.reset_can(self.channel, ResetFlags.RESET_ONLY_TX_BUFF) + + @staticmethod + def create_filter(extended, from_id, to_id, rtr_only, rtr_too): + """ + Calculates AMR and ACR using CAN-ID as parameter. + + :param bool extended: + if True parameters from_id and to_id contains 29-bit CAN-ID + + :param int from_id: + first CAN-ID which should be received + + :param int to_id: + last CAN-ID which should be received + + :param bool rtr_only: + if True only RTR-Messages should be received, and rtr_too will be ignored + + :param bool rtr_too: + if True CAN data frames and RTR-Messages should be received + + :return: Returns list with one filter containing a "can_id", a "can_mask" and "extended" key. + """ + return [{ + "can_id": Ucan.calculate_acr(extended, from_id, to_id, rtr_only, rtr_too), + "can_mask": Ucan.calculate_amr(extended, from_id, to_id, rtr_only, rtr_too), + "extended": extended + }] + + @property + def state(self): + return self._state + + @state.setter + def state(self, new_state): + if self._state != BusState.ERROR and (new_state == BusState.ACTIVE or new_state == BusState.PASSIVE): + # deinitialize CAN channel + self._ucan.shutdown(self.channel, False) + # set mode + if new_state == BusState.ACTIVE: + self._params["mode"] &= ~Mode.MODE_LISTEN_ONLY + else: + self._params["mode"] |= Mode.MODE_LISTEN_ONLY + # reinitialize CAN channel + self._ucan.init_can(self.channel, **self._params) + + def shutdown(self): + """ + Shuts down all CAN interfaces and hardware interface. + """ + try: + self._ucan.shutdown() + except Exception as ex: + log.error(ex) diff --git a/doc/interfaces.rst b/doc/interfaces.rst index ca06dd55a..7c8253f9e 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -24,6 +24,7 @@ The available interfaces are: interfaces/vector interfaces/virtual interfaces/canalystii + interfaces/systec 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/systec.rst b/doc/interfaces/systec.rst new file mode 100644 index 000000000..0aa4d9444 --- /dev/null +++ b/doc/interfaces/systec.rst @@ -0,0 +1,76 @@ +.. _systec: + +SYSTEC interface +================ + +Windows interface for the USBCAN devices supporting up to 2 channels based on the +particular product. There is support for the devices also on Linux through the :doc:`socketcan` interface and for Windows using this +``systec`` interface. + +Installation +------------ + +The interface requires installation of the **USBCAN32.dll** library. Download and install the +driver for specific `SYSTEC `__ device. + +Supported devices +----------------- + +The interface supports following devices: + +- GW-001 (obsolete), +- GW-002 (obsolete), +- Multiport CAN-to-USB G3, +- USB-CANmodul1 G3, +- USB-CANmodul2 G3, +- USB-CANmodul8 G3, +- USB-CANmodul16 G3, +- USB-CANmodul1 G4, +- USB-CANmodul2 G4. + +Bus +--- + +.. autoclass:: can.interfaces.systec.ucanbus.UcanBus + :members: + +Configuration +------------- + +The simplest configuration would be:: + + interface = systec + channel = 0 + +Python-can will search for the first device found if not specified explicitly by the +``device_number`` parameter. The ``interface`` and ``channel`` are the only mandatory +parameters. The interface supports two channels 0 and 1. The maximum number of entries in the receive and transmit buffer can be set by the +parameters ``rx_buffer_entries`` and ``tx_buffer_entries``, with default value 4096 +set for both. + +Optional parameters: + +* ``bitrate`` (default 500000) Channel bitrate in bit/s +* ``device_number`` (default first device) The device number of the USB-CAN +* ``rx_buffer_entries`` (default 4096) The maximum number of entries in the receive buffer +* ``tx_buffer_entries`` (default 4096) The maximum number of entries in the transmit buffer +* ``state`` (default BusState.ACTIVE) BusState of the channel +* ``receive_own_messages`` (default False) If messages transmitted should also be received back + +Internals +--------- + +Message filtering +~~~~~~~~~~~~~~~~~ + +The interface and driver supports only setting of one filter per channel. If one filter +is requested, this is will be handled by the driver itself. If more than one filter is +needed, these will be handled in Python code in the ``recv`` method. If a message does +not match any of the filters, ``recv()`` will return None. + +Periodic tasks +~~~~~~~~~~~~~~ + +The driver supports periodic message sending but without the possibility to set +the interval between messages. Therefore the handling of the periodic messages is done +by the interface using the :class:`~can.broadcastmanager.ThreadBasedCyclicSendTask`. diff --git a/test/test_systec.py b/test/test_systec.py new file mode 100644 index 000000000..ce5dda4a7 --- /dev/null +++ b/test/test_systec.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python +# coding: utf-8 + +import unittest +try: + from unittest.mock import Mock, patch +except ImportError: + from mock import Mock, patch + +import can +from can.interfaces.systec import ucan, ucanbus +from can.interfaces.systec.ucan import * + + +class SystecTest(unittest.TestCase): + + def compare_message(self, first, second, msg): + if first.arbitration_id != second.arbitration_id or first.data != second.data or \ + first.is_extended_id != second.is_extended_id: + raise self.failureException(msg) + + def setUp(self): + # add equality function for can.Message + self.addTypeEqualityFunc(can.Message, self.compare_message) + + ucan.UcanInitHwConnectControlEx = Mock() + ucan.UcanInitHardwareEx = Mock() + ucan.UcanInitHardwareEx2 = Mock() + ucan.UcanInitCanEx2 = Mock() + ucan.UcanGetHardwareInfoEx2 = Mock() + ucan.UcanSetAcceptanceEx = Mock() + ucan.UcanDeinitCanEx = Mock() + ucan.UcanDeinitHardware = Mock() + ucan.UcanWriteCanMsgEx = Mock() + ucan.UcanResetCanEx = Mock() + self.bus = can.Bus(bustype='systec', channel=0, bitrate=125000) + + def test_bus_creation(self): + self.assertIsInstance(self.bus, ucanbus.UcanBus) + self.assertTrue(ucan.UcanInitHwConnectControlEx.called) + self.assertTrue(ucan.UcanInitHardwareEx.called or ucan.UcanInitHardwareEx2.called) + self.assertTrue(ucan.UcanInitCanEx2.called) + self.assertTrue(ucan.UcanGetHardwareInfoEx2.called) + self.assertTrue(ucan.UcanSetAcceptanceEx.called) + + def test_bus_shutdown(self): + self.bus.shutdown() + self.assertTrue(ucan.UcanDeinitCanEx.called) + self.assertTrue(ucan.UcanDeinitHardware.called) + + def test_filter_setup(self): + # no filter in the constructor + expected_args = ( + (self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL), + ) + self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) + + # one filter is handled by the driver + ucan.UcanSetAcceptanceEx.reset_mock() + can_filter = (True, 0x123, 0x123, False, False) + self.bus.set_filters(ucanbus.UcanBus.create_filter(*can_filter)) + expected_args = ( + (self.bus._ucan._handle, + 0, + ucan.UcanServer.calculate_amr(*can_filter), + ucan.UcanServer.calculate_acr(*can_filter) + ), + ) + self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) + + # multiple filters are handled by the bus + ucan.UcanSetAcceptanceEx.reset_mock() + can_filter = ( + (False, 0x8, 0x8, False, False), + (False, 0x9, 0x9, False, False) + ) + self.bus.set_filters(ucanbus.UcanBus.create_filter(*can_filter[0]) + + ucanbus.UcanBus.create_filter(*can_filter[1])) + expected_args = ( + (self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL), + ) + self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) + + @patch('can.interfaces.systec.ucan.UcanServer.write_can_msg') + def test_send_extended(self, mock_write_can_msg): + msg = can.Message( + arbitration_id=0xc0ffee, + data=[0, 25, 0, 1, 3, 1, 4], + is_extended_id=True) + self.bus.send(msg) + + expected_args = ( + (0, [CanMsg(msg.arbitration_id, MsgFrameFormat.MSG_FF_EXT, msg.data)]), + ) + self.assertEqual(mock_write_can_msg.call_args, expected_args) + + @patch('can.interfaces.systec.ucan.UcanServer.write_can_msg') + def test_send_standard(self, mock_write_can_msg): + msg = can.Message( + arbitration_id=0x321, + data=[50, 51], + is_extended_id=False) + self.bus.send(msg) + + expected_args = ( + (0, [CanMsg(msg.arbitration_id, MsgFrameFormat.MSG_FF_STD, msg.data)]), + ) + self.assertEqual(mock_write_can_msg.call_args, expected_args) + + @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') + def test_recv_no_message(self, mock_get_msg_pending): + mock_get_msg_pending.return_value = 0 + self.assertEqual(self.bus.recv(timeout=0.5), None) + + @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') + @patch('can.interfaces.systec.ucan.UcanServer.read_can_msg') + def test_recv_extended(self, mock_read_can_msg, mock_get_msg_pending): + mock_read_can_msg.return_value = [CanMsg(0xc0ffef, MsgFrameFormat.MSG_FF_EXT, [1, 2, 3, 4, 5, 6, 7, 8])], 0 + mock_get_msg_pending.return_value = 1 + + msg = can.Message( + arbitration_id=0xc0ffef, + data=[1, 2, 3, 4, 5, 6, 7, 8], + is_extended_id=True) + can_msg = self.bus.recv() + self.assertEqual(can_msg, msg) + + @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') + @patch('can.interfaces.systec.ucan.UcanServer.read_can_msg') + def test_recv_standard(self, mock_read_can_msg, mock_get_msg_pending): + mock_read_can_msg.return_value = [CanMsg(0x321, MsgFrameFormat.MSG_FF_STD, [50, 51])], 0 + mock_get_msg_pending.return_value = 1 + + msg = can.Message( + arbitration_id=0x321, + data=[50, 51], + is_extended_id=False) + can_msg = self.bus.recv() + self.assertEqual(can_msg, msg) + + @staticmethod + def test_bus_defaults(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_bus_channel(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=1) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 1, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_bus_bitrate(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, bitrate=125000) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_125kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + def test_bus_custom_bitrate(self): + with self.assertRaises(ValueError): + can.Bus(bustype='systec', channel=0, bitrate=123456) + + @staticmethod + def test_receive_own_messages(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, receive_own_messages=True) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_TX_ECHO, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_bus_passive_state(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, state=can.BusState.PASSIVE) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_LISTEN_ONLY, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_rx_buffer_entries(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, rx_buffer_entries=1024) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + 1024, + DEFAULT_BUFFER_ENTRIES + ) + ) + + @staticmethod + def test_tx_buffer_entries(): + ucan.UcanInitCanEx2.reset_mock() + bus = can.Bus(bustype='systec', channel=0, tx_buffer_entries=1024) + ucan.UcanInitCanEx2.assert_called_once_with( + bus._ucan._handle, + 0, + InitCanParam( + Mode.MODE_NORMAL, + Baudrate.BAUD_500kBit, + OutputControl.OCR_DEFAULT, + AMR_ALL, + ACR_ALL, + BaudrateEx.BAUDEX_USE_BTR01, + DEFAULT_BUFFER_ENTRIES, + 1024 + ) + ) + + def test_flush_tx_buffer(self): + self.bus.flush_tx_buffer() + ucan.UcanResetCanEx.assert_called_once_with(self.bus._ucan._handle, 0, ResetFlags.RESET_ONLY_TX_BUFF) + + +if __name__ == '__main__': + unittest.main() From 0b9d240a13ed3fcaaf6315d65482e0c83dd5ee2e Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 7 Jan 2019 18:34:11 +1100 Subject: [PATCH 0042/1235] Prepare release 3.1.0 (#485) * Add changelog for release 3.1.0 --- CHANGELOG.txt | 27 +++++++++++++++++++++++++++ can/__init__.py | 3 ++- doc/history.rst | 2 ++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4402a0a53..df6de2d22 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,30 @@ +Version 3.1.0 +==== + +Major features +-------------- + +Two new interfaces this release: + +- SYSTEC contributed by @idaniel86 in PR #466 +- CANalyst-II contributed by @smeng9 in PR #476 + + +Other notable changes +--------------------- + +- #477 kvaser interface now supports bus statistics via a custom bus method. +- #471 fix CAN FD issue in kvaser interface +- #434 neovi supports receiving own messages +- #447 improvements to serial interface: + * to allow receiving partial messages + * to fix issue with DLC of remote frames + * addition of unit tests +- #462 `Notifier` issue with asyncio. +- #481 - Fix PCAN support on OSX. +- #455 minor fix to `Message` initializer. + + Version 3.0.0 ==== diff --git a/can/__init__.py b/can/__init__.py index c67cef0bd..fcc87fd25 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.0.0" +__version__ = "3.1.0-rc.1" log = logging.getLogger('can') @@ -21,6 +21,7 @@ class CanError(IOError): """ pass + from .listener import Listener, BufferedReader, RedirectReader try: from .listener import AsyncBufferedReader diff --git a/doc/history.rst b/doc/history.rst index 54193aff2..8eaa46685 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -46,6 +46,8 @@ The CAN viewer terminal script was contributed by Kristian Sloth Lauszus in 2018 The CANalyst-II interface was contributed by Shaoyu Meng in 2018. +The SYSTEC interface was contributed by @idaniel86 + Support for CAN within Python ----------------------------- From 1a74330d056431854a07a8a392eb7890d006ad47 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 7 Jan 2019 20:38:19 +1100 Subject: [PATCH 0043/1235] Documentation update (#486) * Add try except around importing curses to support windows * Update docstrings for MultiRateCyclicSendTask * Move some internal stuff into own internal api page. * More doc updates - trying to reduce sphinx warnings --- can/broadcastmanager.py | 4 +- can/bus.py | 4 +- can/interface.py | 7 +-- can/interfaces/socketcan/socketcan.py | 3 +- can/io/generic.py | 2 +- can/viewer.py | 18 ++++-- doc/api.rst | 5 +- doc/bus.rst | 1 + doc/development.rst | 49 +--------------- doc/history.rst | 2 - doc/interfaces/canalystii.rst | 3 +- doc/internal-api.rst | 84 +++++++++++++++++++++++++++ doc/message.rst | 4 +- 13 files changed, 113 insertions(+), 73 deletions(-) create mode 100644 doc/internal-api.rst diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 6837f0e67..79d586744 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -85,7 +85,7 @@ def modify_data(self, message): class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): - """Exposes more of the full power of the TX_SETUP opcode. + """A Cyclic send task that supports switches send frequency after a set time. """ def __init__(self, channel, message, count, initial_period, subsequent_period): @@ -93,7 +93,7 @@ def __init__(self, channel, message, count, initial_period, subsequent_period): Transmits a message `count` times at `initial_period` then continues to transmit message at `subsequent_period`. - :param can.interface.Bus channel: + :param channel: See interface specific documentation. :param can.Message message: :param int count: :param float initial_period: diff --git a/can/bus.py b/can/bus.py index 3b7f6c1a1..1a637441c 100644 --- a/can/bus.py +++ b/can/bus.py @@ -167,8 +167,8 @@ def send_periodic(self, msg, period, duration=None, store_task=True): - the (optional) duration expires - the Bus instance goes out of scope - the Bus instance is shutdown - - :meth:`Bus.stop_all_periodic_tasks()` is called - - the task's :meth:`Task.stop()` method is called. + - :meth:`BusABC.stop_all_periodic_tasks()` is called + - the task's :meth:`CyclicTask.stop()` method is called. :param can.Message msg: Message to transmit diff --git a/can/interface.py b/can/interface.py index 78a18f891..252ef9c75 100644 --- a/can/interface.py +++ b/can/interface.py @@ -1,8 +1,8 @@ # coding: utf-8 """ -This module contains the base implementation of `can.Bus` as well -as a list of all avalibale backends and some implemented +This module contains the base implementation of :class:`can.BusABC` as well +as a list of all available backends and some implemented CyclicSendTasks. """ @@ -11,7 +11,6 @@ import sys import importlib import logging -import re import can from .bus import BusABC @@ -145,7 +144,7 @@ def detect_available_configs(interfaces=None): - `None` to search in all known interfaces. :rtype: list[dict] :return: an iterable of dicts, each suitable for usage in - the constructor of :class:`can.interface.Bus`. + the constructor of :class:`can.BusABC`. """ # Figure out where to search diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 4b7850f2f..566ddfcb5 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -303,8 +303,7 @@ def start(self): class MultiRateCyclicSendTask(CyclicSendTask): """Exposes more of the full power of the TX_SETUP opcode. - Transmits a message `count` times at `initial_period` then - continues to transmit message at `subsequent_period`. + """ def __init__(self, channel, message, count, initial_period, subsequent_period): diff --git a/can/io/generic.py b/can/io/generic.py index 050e9f0e5..a61c33a9f 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -26,7 +26,7 @@ def __init__(self, file, mode='rt'): :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:`builtin.open`, ignored if *file* is `None` + :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 diff --git a/can/viewer.py b/can/viewer.py index 80201348d..316d3e3e4 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -25,18 +25,26 @@ from __future__ import absolute_import, print_function import argparse -import can -import curses import os import struct import sys import time - -from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE +import logging from typing import Dict, List, Tuple, Union - +import can from can import __version__ +logger = logging.getLogger('can.serial') + +try: + import curses + from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE +except ImportError: + # Probably on windows + logger.warning("You won't be able to use the viewer program without " + "curses installed!") + curses = None + class CanViewer: diff --git a/doc/api.rst b/doc/api.rst index 8e657bd3c..640f61e2d 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -17,19 +17,16 @@ A form of CAN interface is also required. listeners asyncio bcm + internal-api Utilities --------- -.. automodule:: can.util - :members: .. automethod:: can.detect_available_configs - - .. _notifier: Notifier diff --git a/doc/bus.rst b/doc/bus.rst index 0b8814c8f..5c1e95606 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -30,6 +30,7 @@ API :members: :undoc-members: + .. automethod:: __iter__ Transmitting '''''''''''' diff --git a/doc/development.rst b/doc/development.rst index 118c2a3cc..57864753f 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -35,7 +35,7 @@ These steps are a guideline on how to add a new backend to python-can. - Create a module (either a ``*.py`` or an entire subdirectory depending on the complexity) inside ``can.interfaces`` - Implement the central part of the backend: the bus class that extends - :class:`can.BusABC`. See below for more info on this one! + :class:`can.BusABC`. See :ref:`businternals` for more info on this one! - Register your backend bus class in ``can.interface.BACKENDS`` and ``can.interfaces.VALID_INTERFACES`` in ``can.interfaces.__init__.py``. - Add docs where appropriate. At a minimum add to ``doc/interfaces.rst`` and add @@ -44,53 +44,6 @@ These steps are a guideline on how to add a new backend to python-can. - Add tests in ``test/*`` where appropriate. -About the ``BusABC`` class --------------------------- - -Concrete implementations *have to* implement the following: - * :meth:`~can.BusABC.send` to send individual messages - * :meth:`~can.BusABC._recv_internal` to receive individual messages - (see note below!) - * set the :attr:`~can.BusABC.channel_info` attribute to a string describing - the underlying bus and/or channel - -They *might* implement the following: - * :meth:`~can.BusABC.flush_tx_buffer` to allow discarding any - messages yet to be sent - * :meth:`~can.BusABC.shutdown` to override how the bus should - shut down - * :meth:`~can.BusABC._send_periodic_internal` to override the software based - periodic sending and push it down to the kernel or hardware. - * :meth:`~can.BusABC._apply_filters` to apply efficient filters - to lower level systems like the OS kernel or hardware. - * :meth:`~can.BusABC._detect_available_configs` to allow the interface - to report which configurations are currently available for new - connections. - * :meth:`~can.BusABC.state` property to allow reading and/or changing - the bus state. - -.. note:: - - *TL;DR*: Only override :meth:`~can.BusABC._recv_internal`, - never :meth:`~can.BusABC.recv` directly. - - Previously, concrete bus classes had to override :meth:`~can.BusABC.recv` - directly instead of :meth:`~can.BusABC._recv_internal`, but that has - changed to allow the abstract base class to handle in-software message - filtering as a fallback. All internal interfaces now implement that new - behaviour. Older (custom) interfaces might still be implemented like that - and thus might not provide message filtering: - -This is the entire ABC bus class with all internal methods: - -.. autoclass:: can.BusABC - :private-members: - :special-members: - :noindex: - - -Concrete instances are created by :class:`can.Bus`. - Code Structure -------------- diff --git a/doc/history.rst b/doc/history.rst index 8eaa46685..54193aff2 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -46,8 +46,6 @@ The CAN viewer terminal script was contributed by Kristian Sloth Lauszus in 2018 The CANalyst-II interface was contributed by Shaoyu Meng in 2018. -The SYSTEC interface was contributed by @idaniel86 - Support for CAN within Python ----------------------------- diff --git a/doc/interfaces/canalystii.rst b/doc/interfaces/canalystii.rst index 1c73f4816..687f61fcc 100644 --- a/doc/interfaces/canalystii.rst +++ b/doc/interfaces/canalystii.rst @@ -1,7 +1,8 @@ CANalyst-II =========== -CANalyst-II(+) is a USB to CAN Analyzer. The controlcan library is originally developed by `ZLG ZHIYUAN Electronics`_. +CANalyst-II(+) is a USB to CAN Analyzer. The controlcan library is originally developed by +`ZLG ZHIYUAN Electronics`_. Bus diff --git a/doc/internal-api.rst b/doc/internal-api.rst new file mode 100644 index 000000000..d07c39c58 --- /dev/null +++ b/doc/internal-api.rst @@ -0,0 +1,84 @@ +Internal API +============ + +Here we document the odds and ends that are more helpful for creating your own interfaces +or listeners but generally shouldn't be required to interact with python-can. + + +.. _businternals: + + +Extending the ``BusABC`` class +------------------------------ + +Concrete implementations **must** implement the following: + * :meth:`~can.BusABC.send` to send individual messages + * :meth:`~can.BusABC._recv_internal` to receive individual messages + (see note below!) + * set the :attr:`~can.BusABC.channel_info` attribute to a string describing + the underlying bus and/or channel + +They **might** implement the following: + * :meth:`~can.BusABC.flush_tx_buffer` to allow discarding any + messages yet to be sent + * :meth:`~can.BusABC.shutdown` to override how the bus should + shut down + * :meth:`~can.BusABC._send_periodic_internal` to override the software based + periodic sending and push it down to the kernel or hardware. + * :meth:`~can.BusABC._apply_filters` to apply efficient filters + to lower level systems like the OS kernel or hardware. + * :meth:`~can.BusABC._detect_available_configs` to allow the interface + to report which configurations are currently available for new + connections. + * :meth:`~can.BusABC.state` property to allow reading and/or changing + the bus state. + +.. note:: + + *TL;DR*: Only override :meth:`~can.BusABC._recv_internal`, + never :meth:`~can.BusABC.recv` directly. + + Previously, concrete bus classes had to override :meth:`~can.BusABC.recv` + directly instead of :meth:`~can.BusABC._recv_internal`, but that has + changed to allow the abstract base class to handle in-software message + filtering as a fallback. All internal interfaces now implement that new + behaviour. Older (custom) interfaces might still be implemented like that + and thus might not provide message filtering: + + + +Concrete instances are usually created by :class:`can.Bus` which takes the users +configuration into account. + + +Bus Internals +~~~~~~~~~~~~~ + +Several methods are not documented in the main :class:`can.BusABC` +as they are primarily useful for library developers as opposed to +library users. This is the entire ABC bus class with all internal +methods: + +.. autoclass:: can.BusABC + :private-members: + :special-members: + :noindex: + + + +IO Utilities +------------ + + +.. automodule:: can.io.generic + :members: + + + +Other Util +---------- + + +.. automodule:: can.util + :members: + diff --git a/doc/message.rst b/doc/message.rst index 9014e52e3..921748cb9 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -113,7 +113,7 @@ Message .. note:: - The :meth:`Message.__init__` argument ``extended_id`` has been deprecated in favor of + The initializer argument and attribute ``extended_id`` has been deprecated in favor of ``is_extended_id``, but will continue to work for the ``3.x`` release series. @@ -129,7 +129,7 @@ Message .. attribute:: is_remote_frame - :type: boolean + :type: bool This boolean attribute indicates if the message is a remote frame or a data frame, and modifies the bit in the CAN message's flags field indicating this. From c819526c85cb0b07b1103d4fae8578e760128d63 Mon Sep 17 00:00:00 2001 From: Alexander Mueller Date: Sat, 12 Jan 2019 00:30:03 +0100 Subject: [PATCH 0044/1235] Allow simultaneous access to IXXAT cards (#488) --- can/interfaces/ixxat/canlib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 91f846554..e48323e01 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -123,6 +123,8 @@ def __check_status(result, function, arguments): raise VCIRxQueueEmptyError() elif result == constants.VCI_E_NO_MORE_ITEMS: raise StopIteration() + elif result == constants.VCI_E_ACCESSDENIED: + pass # not a real error, might happen if another program has initialized the bus elif result != constants.VCI_OK: raise VCIError(vciFormatError(function, result)) From 3ae0f37bfb79e9e3e4cc43ce878bd60a1e0eaa1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Mon, 14 Jan 2019 12:28:27 -0500 Subject: [PATCH 0045/1235] Adding option to override neovi library name --- can/interfaces/ics_neovi/neovi_bus.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 3f8585b51..82bb7d28d 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -91,6 +91,8 @@ def __init__(self, channel, can_filters=None, **config): :param int data_bitrate: Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. + :param override_library_name: + Absolute path or relative path to the library including filename. """ if ics is None: raise ImportError('Please install python-ics') @@ -101,6 +103,9 @@ def __init__(self, channel, can_filters=None, **config): logger.info("CAN Filters: {}".format(can_filters)) logger.info("Got configuration of: {}".format(config)) + if 'override_library_name' in config: + ics.override_library_name(config.get('override_library_name')) + if isinstance(channel, (list, tuple)): self.channels = channel elif isinstance(channel, int): From 8d24b3c968763cda69b527a794b32351d06cd247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Tue, 22 Jan 2019 13:53:32 -0500 Subject: [PATCH 0046/1235] Adding ms to ASC formatted date --- can/io/asc.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index c39bfa869..d05c87dc5 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -131,7 +131,7 @@ class ASCWriter(BaseIOHandler, Listener): """ FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" - FORMAT_DATE = "%a %b %m %I:%M:%S %p %Y" + FORMAT_DATE = "%a %b %m %I:%M:%S.{} %p %Y" FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" def __init__(self, file, channel=1): @@ -146,7 +146,7 @@ def __init__(self, file, channel=1): self.channel = channel # write start of file header - now = datetime.now().strftime("%a %b %m %I:%M:%S %p %Y") + now = datetime.now().strftime("%a %b %m %I:%M:%S.%f %p %Y") self.file.write("date %s\n" % now) self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") @@ -176,7 +176,8 @@ def log_event(self, message, timestamp=None): if not self.header_written: self.last_timestamp = (timestamp or 0.0) self.started = self.last_timestamp - formatted_date = time.strftime(self.FORMAT_DATE, time.localtime(self.last_timestamp)) + mlsec = repr(self.last_timestamp).split('.')[1][:3] + formatted_date = time.strftime(self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp)) 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! From 14c5d1401dee636190453ecc625622ece3382b59 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sat, 2 Feb 2019 20:01:31 +0100 Subject: [PATCH 0047/1235] add read to slcanBus --- can/interfaces/slcan.py | 87 ++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 7b276a078..ba65cd5de 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -47,6 +47,9 @@ class slcanBus(BusABC): _SLEEP_AFTER_SERIAL_OPEN = 2 # in seconds + _OK = b'\r' + _ERROR = b'\a' + LINE_TERMINATOR = b'\r' def __init__(self, channel, ttyBaudrate=115200, bitrate=None, @@ -97,6 +100,43 @@ def write(self, string): self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() + def read(self, timeout): + + # first read what is already in receive buffer + while self.serialPortOrig.in_waiting: + self._buffer += self.serialPortOrig.read(1) + + # if we still don't have a complete message, do a blocking read + start = time.time() + time_left = timeout + while not (self._OK in self._buffer or self._ERROR in self._buffer): + self.serialPortOrig.timeout = time_left + byte = self.serialPortOrig.read(1) + if byte: + self._buffer += byte + + # if timeout is None, try indefinitely + if timeout is None: + continue + # try next one only if there still is time, and with + # reduced timeout + else: + time_left = timeout - (time.time() - start) + if time_left > 0: + continue + else: + return None + + # return first message + for i in xrange(len(self._buffer)): + if ( chr(self._buffer[i]) == self._OK or + chr(self._buffer[i]) == self._ERROR ): + string = self._buffer[:i+1].decode() + del self._buffer[:i+1] + break + + return string + def open(self): self.write('O') @@ -104,53 +144,38 @@ def close(self): self.write('C') def _recv_internal(self, timeout): - if timeout != self.serialPortOrig.timeout: - self.serialPortOrig.timeout = timeout canId = None remote = False extended = False frame = [] - # First read what is already in the receive buffer - while (self.serialPortOrig.in_waiting and - self.LINE_TERMINATOR not in self._buffer): - self._buffer += self.serialPortOrig.read(1) - - # If we still don't have a complete message, do a blocking read - if self.LINE_TERMINATOR not in self._buffer: - self._buffer += self.serialPortOrig.read_until(self.LINE_TERMINATOR) - - if self.LINE_TERMINATOR not in self._buffer: - # Timed out - return None, False + string = self.read(timeout) - readStr = self._buffer.decode() - del self._buffer[:] - if not readStr: + if not string: pass - elif readStr[0] == 'T': + elif string[0] == 'T': # extended frame - canId = int(readStr[1:9], 16) - dlc = int(readStr[9]) + canId = int(string[1:9], 16) + dlc = int(string[9]) extended = True for i in range(0, dlc): - frame.append(int(readStr[10 + i * 2:12 + i * 2], 16)) - elif readStr[0] == 't': + frame.append(int(string[10 + i * 2:12 + i * 2], 16)) + elif string[0] == 't': # normal frame - canId = int(readStr[1:4], 16) - dlc = int(readStr[4]) + canId = int(string[1:4], 16) + dlc = int(string[4]) for i in range(0, dlc): - frame.append(int(readStr[5 + i * 2:7 + i * 2], 16)) - elif readStr[0] == 'r': + frame.append(int(string[5 + i * 2:7 + i * 2], 16)) + elif string[0] == 'r': # remote frame - canId = int(readStr[1:4], 16) - dlc = int(readStr[4]) + canId = int(string[1:4], 16) + dlc = int(string[4]) remote = True - elif readStr[0] == 'R': + elif string[0] == 'R': # remote extended frame - canId = int(readStr[1:9], 16) - dlc = int(readStr[9]) + canId = int(string[1:9], 16) + dlc = int(string[9]) extended = True remote = True From d39eb2c73215346f5114c6d9081f384c8351f613 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sat, 2 Feb 2019 20:20:34 +0100 Subject: [PATCH 0048/1235] add flush to slcanBus --- can/interfaces/slcan.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index ba65cd5de..49e4f8424 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -137,6 +137,11 @@ def read(self, timeout): return string + def flush(self): + del self._buffer[:] + while self.serialPortOrig.in_waiting: + self.serialPortOrig.read(1) + def open(self): self.write('O') From c20c93b3ff32b835fff3949f094cc705c3c70153 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sat, 2 Feb 2019 20:31:39 +0100 Subject: [PATCH 0049/1235] add get_version and get_serial to slcanBus --- can/interfaces/slcan.py | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 49e4f8424..f90dc7cb5 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -221,3 +221,59 @@ def fileno(self): return self.serialPortOrig.fileno() # Return an invalid file descriptor on Windows return -1 + + def get_version(self, timeout): + cmd = "V" + self.write(cmd) + + start = time.time() + time_left = timeout + while True: + string = self.read(time_left) + + if not string: + pass + elif string[0] == cmd and len(string) == 6: + # convert ASCII coded version + hw_version = int(string[1:3]) + sw_version = int(string[3:5]) + return hw_version, sw_version + + # if timeout is None, try indefinitely + if timeout is None: + continue + # try next one only if there still is time, and with + # reduced timeout + else: + time_left = timeout - (time.time() - start) + if time_left > 0: + continue + else: + return None, None + + def get_serial(self, timeout): + cmd = "N" + self.write(cmd) + + start = time.time() + time_left = timeout + while True: + string = self.read(time_left) + + if not string: + pass + elif string[0] == cmd and len(string) == 6: + serial = string[1:-1] + return serial + + # if timeout is None, try indefinitely + if timeout is None: + continue + # try next one only if there still is time, and with + # reduced timeout + else: + time_left = timeout - (time.time() - start) + if time_left > 0: + continue + else: + return None From 89a75036f5194271bb5a2230921bcdb035cb83e5 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sat, 2 Feb 2019 21:53:13 +0100 Subject: [PATCH 0050/1235] add tests for get_version and get_serial --- test/test_slcan.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/test_slcan.py b/test/test_slcan.py index 29869cb1c..519c71418 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -107,6 +107,24 @@ def test_partial_recv(self): msg = self.bus.recv(0) self.assertIsNotNone(msg) + def test_version(self): + self.serial.write(b'V1013\r') + hw_ver, sw_ver = self.bus.get_version(0) + self.assertEqual(hw_ver, 10) + self.assertEqual(sw_ver, 13) + + hw_ver, sw_ver = self.bus.get_version(0) + self.assertIsNone(hw_ver) + self.assertIsNone(sw_ver) + + def test_serial(self): + self.serial.write(b'NA123\r') + sn = self.bus.get_serial(0) + self.assertEqual(sn, "A123") + + sn = self.bus.get_serial(0) + self.assertIsNone(sn) + if __name__ == '__main__': unittest.main() From 55480cc024d291805ee73aa1e63297119b8b1a43 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sat, 2 Feb 2019 22:14:09 +0100 Subject: [PATCH 0051/1235] add set_bitrate to slcanBus --- can/interfaces/slcan.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index f90dc7cb5..4b1fed6c9 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -85,17 +85,21 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, time.sleep(sleep_after_open) if bitrate is not None: - self.close() - if bitrate in self._BITRATES: - self.write(self._BITRATES[bitrate]) - else: - raise ValueError("Invalid bitrate, choose one of " + (', '.join(self._BITRATES)) + '.') + self.set_bitrate(self, bitrate) self.open() super(slcanBus, self).__init__(channel, ttyBaudrate=115200, bitrate=None, rtscts=False, **kwargs) + def set_bitrate(self, bitrate): + self.close() + if bitrate in self._BITRATES: + self.write(self._BITRATES[bitrate]) + else: + raise ValueError("Invalid bitrate, choose one of " + (', '.join(self._BITRATES)) + '.') + self.open() + def write(self, string): self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() From 7f8c686604756c3251a457062f48d8e3ad28a81d Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sat, 2 Feb 2019 22:48:55 +0100 Subject: [PATCH 0052/1235] replace xrange with range --- can/interfaces/slcan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 4b1fed6c9..27810ac48 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -132,7 +132,7 @@ def read(self, timeout): return None # return first message - for i in xrange(len(self._buffer)): + for i in range(len(self._buffer)): if ( chr(self._buffer[i]) == self._OK or chr(self._buffer[i]) == self._ERROR ): string = self._buffer[:i+1].decode() From de94772a5a2f8c968097daccf5177f8d23e27ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Wed, 13 Feb 2019 09:40:21 +0100 Subject: [PATCH 0053/1235] Fix initialization of usb2can Bus interface object (#501) * Fully initialize usb2can Bus interface object. The usb2can interface backend currently does not work because the _filters member is not initialized in the constructor of can.interfaces.usb2can.Usb2canBus. Call the parent class constructor to pull in the missing object attributes. * Add a note about the 32-bit only CANAL DLL. The usb2can interface currently only supports 32-bit Python on Windows, because there is no 64-bit CANAL library available yet. Mention this in the documentation section. --- can/interfaces/usb2can/usb2canInterface.py | 2 ++ doc/interfaces/usb2can.rst | 1 + 2 files changed, 3 insertions(+) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index b4eca1d10..b395d2336 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -119,6 +119,8 @@ def __init__(self, channel, *args, **kwargs): self.handle = self.can.open(connector.encode('utf-8'), enable_flags) + super(Usb2canBus, self).__init__(channel=channel, *args, **kwargs) + def send(self, msg, timeout=None): tx = message_convert_tx(msg) if timeout: diff --git a/doc/interfaces/usb2can.rst b/doc/interfaces/usb2can.rst index 3a7e291cf..7bf01a64c 100644 --- a/doc/interfaces/usb2can.rst +++ b/doc/interfaces/usb2can.rst @@ -26,6 +26,7 @@ WINDOWS INSTALL 1. To install on Windows download the USB2CAN Windows driver. It is compatible with XP, Vista, Win7, Win8/8.1. (Written against driver version v1.0.2.1) 2. Install the appropriate version of `pywin32 `_ (win32com) 3. Download the USB2CAN CANAL DLL from the USB2CAN website. Place this in either the same directory you are running usb2can.py from or your DLL folder in your python install. + Note that only a 32-bit version is currently available, so this only works in a 32-bit Python environment. (Written against CANAL DLL version v1.0.6) Interface Layout From 4ea358f2d8906358309cd84ad9f5b43d58e5b859 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 13 Feb 2019 10:30:16 +0100 Subject: [PATCH 0054/1235] Message class changes (#497) * add hypothesis test for Message class * add runtime warning to deprecated Message's __init__() parameter 'extended_id' * move remaining parameter testing into _check() * remove data from remote test frames * add check for remote frames back to message constructor * cleanups & some docs for TestMessageClass * fix unicode error and change message's __len__ to use the DLC * correct behavior of check=True * fix remote_frame special case * use ValueError instead of assert statements in message's _check() --- can/message.py | 82 +++++++++++++++++++++++-------------- setup.py | 3 +- test/data/example_data.py | 17 +++----- test/message_helper.py | 10 ++++- test/notifier_test.py | 1 + test/test_message_class.py | 83 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 153 insertions(+), 43 deletions(-) create mode 100644 test/test_message_class.py diff --git a/can/message.py b/can/message.py index db34643ed..207ee6da5 100644 --- a/can/message.py +++ b/can/message.py @@ -9,9 +9,11 @@ """ from __future__ import absolute_import, division - + import warnings from copy import deepcopy +from math import isinf, isnan + class Message(object): """ @@ -53,7 +55,7 @@ def __getattr__(self, key): # can be removed in 4.0 # this method is only called if the attribute was not found elsewhere, like in __slots__ try: - warnings.warn("Custom attributes of messages are deprecated and will be removed in the next major version", DeprecationWarning) + warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) return self._dict[key] except KeyError: raise AttributeError("'message' object has no attribute '{}'".format(key)) @@ -63,26 +65,26 @@ def __setattr__(self, key, value): try: super(Message, self).__setattr__(key, value) except AttributeError: - warnings.warn("Custom attributes of messages are deprecated and will be removed in the next major version", DeprecationWarning) + warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) self._dict[key] = value @property def id_type(self): # TODO remove in 4.0 - warnings.warn("Message.id_type is deprecated, use is_extended_id", DeprecationWarning) + warnings.warn("Message.id_type is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) return self.is_extended_id @id_type.setter def id_type(self, value): # TODO remove in 4.0 - warnings.warn("Message.id_type is deprecated, use is_extended_id", DeprecationWarning) + warnings.warn("Message.id_type is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) self.is_extended_id = value def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, is_remote_frame=False, is_error_frame=False, channel=None, dlc=None, data=None, is_fd=False, bitrate_switch=False, error_state_indicator=False, - extended_id=True, # deprecated in 3.x, removed in 4.x + extended_id=None, # deprecated in 3.x, TODO remove in 4.x check=False): """ To create a message object, simply provide any of the below attributes @@ -100,10 +102,15 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, self.timestamp = timestamp self.arbitration_id = arbitration_id + + if extended_id is not None: + # TODO remove in 4.0 + warnings.warn("The extended_id parameter is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) + if is_extended_id is not None: self.is_extended_id = is_extended_id else: - self.is_extended_id = extended_id + self.is_extended_id = True if extended_id is None else extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame @@ -162,18 +169,19 @@ def __str__(self): field_strings.append(" " * 24) if (self.data is not None) and (self.data.isalnum()): - try: - field_strings.append("'{}'".format(self.data.decode('utf-8'))) - except UnicodeError: - pass + field_strings.append("'{}'".format(self.data.decode('utf-8', 'replace'))) if self.channel is not None: - field_strings.append("Channel: {}".format(self.channel)) + try: + field_strings.append("Channel: {}".format(self.channel)) + except UnicodeEncodeError: + pass return " ".join(field_strings).strip() def __len__(self): - return len(self.data) + # return the dlc such that it also works on remote frames + return self.dlc def __bool__(self): # For Python 3 @@ -255,33 +263,47 @@ def _check(self): """Checks if the message parameters are valid. Assumes that the types are already correct. - :raises AssertionError: iff one or more attributes are invalid + :raises ValueError: iff one or more attributes are invalid """ - assert 0.0 <= self.timestamp, "the timestamp may not be negative" + if self.timestamp < 0.0: + raise ValueError("the timestamp may not be negative") + if isinf(self.timestamp): + raise ValueError("the timestamp may not be infinite") + if isnan(self.timestamp): + raise ValueError("the timestamp may not be NaN") - assert not (self.is_remote_frame and self.is_error_frame), \ - "a message cannot be a remote and an error frame at the sane time" + if self.is_remote_frame and self.is_error_frame: + raise ValueError("a message cannot be a remote and an error frame at the sane time") - assert 0 <= self.arbitration_id, "arbitration IDs may not be negative" + if self.arbitration_id < 0: + raise ValueError("arbitration IDs may not be negative") if self.is_extended_id: - assert self.arbitration_id < 0x20000000, "Extended arbitration IDs must be less than 2^29" - else: - assert self.arbitration_id < 0x800, "Normal arbitration IDs must be less than 2^11" + if 0x20000000 <= self.arbitration_id: + raise ValueError("Extended arbitration IDs must be less than 2^29") + elif 0x800 <= self.arbitration_id: + raise ValueError("Normal arbitration IDs must be less than 2^11") - assert 0 <= self.dlc, "DLC may not be negative" + if self.dlc < 0: + raise ValueError("DLC may not be negative") if self.is_fd: - assert self.dlc <= 64, "DLC was {} but it should be <= 64 for CAN FD frames".format(self.dlc) - else: - assert self.dlc <= 8, "DLC was {} but it should be <= 8 for normal CAN frames".format(self.dlc) + if 64 < self.dlc: + raise ValueError("DLC was {} but it should be <= 64 for CAN FD frames".format(self.dlc)) + elif 8 < self.dlc: + raise ValueError("DLC was {} but it should be <= 8 for normal CAN frames".format(self.dlc)) - if not self.is_remote_frame: - assert self.dlc == len(self.data), "the length of the DLC and the length of the data must match up" + if self.is_remote_frame: + if self.data is not None and len(self.data) != 0: + raise ValueError("remote frames may not carry any data") + elif self.dlc != len(self.data): + raise ValueError("the DLC and the length of the data must match up for non remote frames") if not self.is_fd: - assert not self.bitrate_switch, "bitrate switch is only allowed for CAN FD frames" - assert not self.error_state_indicator, "error stat indicator is only allowed for CAN FD frames" + if self.bitrate_switch: + raise ValueError("bitrate switch is only allowed for CAN FD frames") + if self.error_state_indicator: + raise ValueError("error stat indicator is only allowed for CAN FD frames") def equals(self, other, timestamp_delta=1.0e-6): """ @@ -299,7 +321,7 @@ def equals(self, other, timestamp_delta=1.0e-6): # see https://github.com/hardbyte/python-can/pull/413 for a discussion # on why a delta of 1.0e-6 was chosen return ( - # check for identity first + # check for identity first and finish fast self is other or # then check for equality by value ( diff --git a/setup.py b/setup.py index d4431fe67..c4127baa0 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,8 @@ 'pytest-cov~=2.5', 'codecov~=2.0', 'future', - 'six' + 'six', + 'hypothesis' ] + extras_require['serial'] extras_require['test'] = tests_require diff --git a/test/data/example_data.py b/test/data/example_data.py index 1ed5466b6..dd433dc3c 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -30,7 +30,7 @@ def sort_messages(messages): # List of messages of different types that can be used in tests -TEST_MESSAGES_BASE = [ +TEST_MESSAGES_BASE = sort_messages([ Message( # empty ), @@ -98,20 +98,17 @@ def sort_messages(messages): arbitration_id=0x768, is_extended_id=False, timestamp=TEST_TIME + 3.165 ), -] -TEST_MESSAGES_BASE = sort_messages(TEST_MESSAGES_BASE) +]) -TEST_MESSAGES_REMOTE_FRAMES = [ +TEST_MESSAGES_REMOTE_FRAMES = sort_messages([ Message( arbitration_id=0xDADADA, is_extended_id=True, is_remote_frame=True, timestamp=TEST_TIME + .165, - data=[1, 2, 3, 4, 5, 6, 7, 8] ), Message( arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, timestamp=TEST_TIME + .365, - data=[254, 255] ), Message( arbitration_id=0x768, is_extended_id=False, is_remote_frame=True, @@ -121,11 +118,10 @@ def sort_messages(messages): arbitration_id=0xABCDEF, is_extended_id=True, is_remote_frame=True, timestamp=TEST_TIME + 7858.67 ), -] -TEST_MESSAGES_REMOTE_FRAMES = sort_messages(TEST_MESSAGES_REMOTE_FRAMES) +]) -TEST_MESSAGES_ERROR_FRAMES = [ +TEST_MESSAGES_ERROR_FRAMES = sort_messages([ Message( is_error_frame=True ), @@ -137,8 +133,7 @@ def sort_messages(messages): is_error_frame=True, timestamp=TEST_TIME + 17.157 ) -] -TEST_MESSAGES_ERROR_FRAMES = sort_messages(TEST_MESSAGES_ERROR_FRAMES) +]) TEST_ALL_MESSAGES = sort_messages(TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + \ diff --git a/test/message_helper.py b/test/message_helper.py index 497e5498f..1c139335c 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -1,13 +1,21 @@ #!/usr/bin/env python # coding: utf-8 +""" +This module contains a helper for writing test cases that need to compare messages. +""" + from __future__ import absolute_import, print_function from copy import copy class ComparingMessagesTestCase(object): - """Must be extended by a class also extending a unittest.TestCase. + """ + Must be extended by a class also extending a unittest.TestCase. + + .. note:: This class does not extend unittest.TestCase so it does not get + run as a test itself. """ def __init__(self, allowed_timestamp_delta=0.0, preserves_channel=True): diff --git a/test/notifier_test.py b/test/notifier_test.py index ca462a2ad..3ab257cf7 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # coding: utf-8 + import unittest import time try: diff --git a/test/test_message_class.py b/test/test_message_class.py new file mode 100644 index 000000000..85dbe8560 --- /dev/null +++ b/test/test_message_class.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# coding: utf-8 + +import unittest +import sys +from math import isinf, isnan +from copy import copy, deepcopy + +from hypothesis import given, settings, reproduce_failure +import hypothesis.strategies as st + +from can import Message + + +class TestMessageClass(unittest.TestCase): + """ + This test tries many inputs to the message class constructor and then sanity checks + all methods and ensures that nothing crashes. It also checks whether Message._check() + allows all valid can frames. + """ + + @given( + timestamp=st.floats(min_value=0.0), + arbitration_id=st.integers(), + is_extended_id=st.booleans(), + is_remote_frame=st.booleans(), + is_error_frame=st.booleans(), + channel=st.one_of(st.text(), st.integers()), + dlc=st.integers(min_value=0, max_value=8), + data=st.one_of(st.binary(min_size=0, max_size=8), st.none()), + is_fd=st.booleans(), + bitrate_switch=st.booleans(), + error_state_indicator=st.booleans() + ) + @settings(max_examples=2000) + def test_methods(self, **kwargs): + is_valid = not ( + (not kwargs['is_remote_frame'] and (len(kwargs['data'] or []) != kwargs['dlc'])) or + (kwargs['arbitration_id'] >= 0x800 and not kwargs['is_extended_id']) or + kwargs['arbitration_id'] >= 0x20000000 or + kwargs['arbitration_id'] < 0 or + (kwargs['is_remote_frame'] and kwargs['is_error_frame']) or + (kwargs['is_remote_frame'] and len(kwargs['data'] or []) > 0) or + ((kwargs['bitrate_switch'] or kwargs['error_state_indicator']) and not kwargs['is_fd']) or + isnan(kwargs['timestamp']) or + isinf(kwargs['timestamp']) + ) + + # this should return normally and not throw an exception + message = Message(check=is_valid, **kwargs) + + if kwargs['data'] is None or kwargs['is_remote_frame']: + kwargs['data'] = bytearray() + + if not is_valid and not kwargs['is_remote_frame']: + with self.assertRaises(ValueError): + Message(check=True, **kwargs) + + self.assertGreater(len(str(message)), 0) + self.assertGreater(len(message.__repr__()), 0) + if is_valid: + self.assertEqual(len(message), kwargs['dlc']) + self.assertTrue(bool(message)) + self.assertGreater(len("{}".format(message)), 0) + _ = "{}".format(message) + with self.assertRaises(Exception): + _ = "{somespec}".format(message) + if sys.version_info.major > 2: + self.assertEqual(bytearray(bytes(message)), kwargs['data']) + + # check copies and equalities + if is_valid: + self.assertEqual(message, message) + normal_copy = copy(message) + deep_copy = deepcopy(message) + for other in (normal_copy, deep_copy, message): + self.assertTrue(message.equals(other, timestamp_delta=None)) + self.assertTrue(message.equals(other)) + self.assertTrue(message.equals(other, timestamp_delta=0)) + + +if __name__ == '__main__': + unittest.main() From f8524c1048e4151d930898edcdb6693c63ed3809 Mon Sep 17 00:00:00 2001 From: JanGoeteyn <47741811+JanGoeteyn@users.noreply.github.com> Date: Mon, 18 Feb 2019 11:59:08 +0100 Subject: [PATCH 0055/1235] Update iscan.py Added descriptive text for error code 15: Found the device, but it is being used by another process. --- can/interfaces/iscan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index 1f1d0c485..a646bd96e 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -147,6 +147,7 @@ class IscanError(CanError): 10: "Thread already started", 11: "Buffer overrun", 12: "Device not initialized", + 15: "Found the device, but it is being used by another process", 16: "Bus error", 17: "Bus off", 18: "Error passive", From 119999d07c80310845913bea8b608b0703fffeaf Mon Sep 17 00:00:00 2001 From: ykzheng Date: Fri, 22 Feb 2019 20:50:02 +0800 Subject: [PATCH 0056/1235] canalystii interface take msg.is_extend_id into use and fix typo (#513) --- can/interfaces/canalystii.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 55f349a2d..35f240a66 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -122,7 +122,8 @@ def send(self, msg, timeout=None): :param timeout: timeout is not used here :return: """ - raw_message = VCI_CAN_OBJ(msg.arbitration_id, 0, 0, 0, msg.is_remote_frame, 0, msg.dlc, (c_ubyte * 8)(*msg.data), (c_byte * 3)(*[0, 0, 0])) + extern_flag = 1 if msg.is_extended_id else 0 + raw_message = VCI_CAN_OBJ(msg.arbitration_id, 0, 0, 1, msg.is_remote_frame, extern_flag, msg.dlc, (c_ubyte * 8)(*msg.data), (c_byte * 3)(*[0, 0, 0])) if msg.channel is not None: channel = msg.channel @@ -161,7 +162,7 @@ def _recv_internal(self, timeout=None): def flush_tx_buffer(self): for channel in self.channels: - CANalystII.VCI_ClearBUffer(VCI_USBCAN2, self.device, channel) + CANalystII.VCI_ClearBuffer(VCI_USBCAN2, self.device, channel) def shutdown(self): CANalystII.VCI_CloseDevice(VCI_USBCAN2, self.device) From 44cefbe7026be22e31c07c14591a63399ccbf790 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 23 Feb 2019 00:53:32 +0100 Subject: [PATCH 0057/1235] fix #510 --- can/io/asc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index d05c87dc5..3ed50f04a 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -19,6 +19,7 @@ from ..util import channel2int from .generic import BaseIOHandler + CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF @@ -48,7 +49,6 @@ def _extract_can_id(str_can_id): else: is_extended = False can_id = int(str_can_id, 16) - #logging.debug('ASCReader: _extract_can_id("%s") -> %x, %r', str_can_id, can_id, is_extended) return can_id, is_extended def __iter__(self): @@ -72,12 +72,12 @@ def __iter__(self): except ValueError: pass - if dummy.strip()[0:10] == 'ErrorFrame': + 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] == 'Statistic:': + elif not isinstance(channel, int) or dummy.strip()[0:10].lower() == 'statistic:': pass elif dummy[-1:].lower() == 'r': From 034843347aedb28a5ccbbd23979bd5fc08cbf402 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 23 Feb 2019 03:16:36 +0100 Subject: [PATCH 0058/1235] release 3.1.0 --- CHANGELOG.txt | 18 +++++++++++------- CONTRIBUTORS.txt | 3 +++ can/__init__.py | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index df6de2d22..1c3f71f13 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -13,16 +13,20 @@ Two new interfaces this release: Other notable changes --------------------- -- #477 kvaser interface now supports bus statistics via a custom bus method. -- #471 fix CAN FD issue in kvaser interface -- #434 neovi supports receiving own messages -- #447 improvements to serial interface: +- #477 The kvaser interface now supports bus statistics via a custom bus method. +- #434 neovi now supports receiving own messages +- #490 Adding option to override the neovi library name +- #488 Allow simultaneous access to IXXAT cards +- #447 Improvements to serial interface: * to allow receiving partial messages * to fix issue with DLC of remote frames * addition of unit tests -- #462 `Notifier` issue with asyncio. -- #481 - Fix PCAN support on OSX. -- #455 minor fix to `Message` initializer. +- #497 Small API changes to `Message` and added unit tests +- #471 Fix CAN FD issue in kvaser interface +- #462 Fix `Notifier` issue with asyncio +- #481 Fix PCAN support on OSX +- #455 Fix to `Message` initializer +- Small bugfixes and improvements Version 3.0.0 diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 68d82034b..c8129fb27 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -23,3 +23,6 @@ Pierre-Luc Tessier Gagné Felix Divo Kristian Sloth Lauszus Shaoyu Meng +Alexander Mueller +Jan Goeteyn +"ykzheng" diff --git a/can/__init__.py b/can/__init__.py index fcc87fd25..58b928ef6 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.1.0-rc.1" +__version__ = "3.1.0" log = logging.getLogger('can') From d2dace883e607c66e28a9e949d87a2f87b5e1c34 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 26 Feb 2019 07:27:38 +1100 Subject: [PATCH 0059/1235] Change version to 3.1.1 and record in changelog --- CHANGELOG.txt | 8 +++++++- can/__init__.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1c3f71f13..8a38c9319 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,4 @@ -Version 3.1.0 +Version 3.1.1 ==== Major features @@ -28,6 +28,12 @@ Other notable changes - #455 Fix to `Message` initializer - Small bugfixes and improvements +Version 3.1.0 +==== + +Version 3.1.0 was built with old wheel and/or setuptools +packages and was replaced with v3.1.1 after an installation +but was discovered. Version 3.0.0 ==== diff --git a/can/__init__.py b/can/__init__.py index 58b928ef6..fb48c6dd4 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.1.0" +__version__ = "3.1.1" log = logging.getLogger('can') From bcaf8cf4fc349b1da6e1d53e6312c0cd63cc8ab1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 26 Feb 2019 11:36:09 +0100 Subject: [PATCH 0060/1235] Update Badges in Readme --- README.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 1277403dc..562c1de6c 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ python-can ========== -|release| |docs| |build_travis| |build_appveyor| |coverage| +|release| |docs| |build_travis| |build_appveyor| |coverage| |downloads| .. |release| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ @@ -11,11 +11,11 @@ python-can :target: https://python-can.readthedocs.io/en/stable/ :alt: Documentation -.. |build_travis| image:: https://travis-ci.org/hardbyte/python-can.svg?branch=develop +.. |build_travis| image:: https://img.shields.io/travis/hardbyte/python-can/develop.svg?label=Travis%20CI :target: https://travis-ci.org/hardbyte/python-can/branches :alt: Travis CI Server for develop branch -.. |build_appveyor| image:: https://ci.appveyor.com/api/projects/status/github/hardbyte/python-can?branch=develop&svg=true +.. |build_appveyor| image:: https://img.shields.io/appveyor/ci/hardbyte/python-can/develop.svg?label=AppVeyor :target: https://ci.appveyor.com/project/hardbyte/python-can/history :alt: AppVeyor CI Server for develop branch @@ -23,6 +23,9 @@ 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 The **C**\ ontroller **A**\ rea **N**\ etwork is a bus standard designed to allow microcontrollers and devices to communicate with each other. It From bb7ea01c3b857ea253a3f77cde2e48c193586ef4 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 27 Feb 2019 21:41:12 +1100 Subject: [PATCH 0061/1235] Include all test scripts in manifest. (#526) Closes #518 --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 4079706c7..a3d6c7fbd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include *.txt +include test/*.py include test/data/*.* recursive-include doc *.rst From 232e6dbfe306a0bf308dde4e8e4b9fd2a0455427 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 26 Feb 2019 21:07:50 +1100 Subject: [PATCH 0062/1235] Handle missing app_name in vector interface. Closes #515 --- can/interfaces/vector/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 60e423276..1b84f3255 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -85,7 +85,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, else: # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(',')] - self._app_name = app_name.encode() + self._app_name = app_name.encode() if app_name is not None else '' self.channel_info = 'Application %s: %s' % ( app_name, ', '.join('CAN %d' % (ch + 1) for ch in self.channels)) From 4be2233786b3098a27785a03ecbe667b233046c6 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 4 Mar 2019 04:31:25 +0100 Subject: [PATCH 0063/1235] Improve USB2CAN interface (#511) * apply fixes from https://gist.github.com/acolomb/945cc5e5b29ced411f1df5b5f9c4a4d0 * simplify find_serial_devices() * fix sphinx warning about indentation --- can/interfaces/usb2can/serial_selector.py | 25 ++- can/interfaces/usb2can/usb2canInterface.py | 172 ++++++++++-------- .../usb2can/usb2canabstractionlayer.py | 55 ++++-- doc/interfaces/usb2can.rst | 2 +- 4 files changed, 154 insertions(+), 100 deletions(-) diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index 422347af0..b47396876 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -3,6 +3,8 @@ """ """ +from __future__ import division, print_function, absolute_import + import logging try: @@ -27,14 +29,17 @@ def WMIDateStringToDate(dtmDate): return strDateTime -def serial(): - strComputer = '.' +def find_serial_devices(serial_matcher="ED"): + """ + Finds a list of USB devices where the serial number (partially) matches the given string. + + :param str serial_matcher (optional): + only device IDs starting with this string are returned + + :rtype: List[str] + """ objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") - objSWbemServices = objWMIService.ConnectServer(strComputer, "root\cimv2") - colItems = objSWbemServices.ExecQuery("SELECT * FROM Win32_USBControllerDevice") - - for objItem in colItems: - string = objItem.Dependent - # find based on beginning of serial - if 'ED' in string: - return string[len(string) - 9:len(string) - 1] + objSWbemServices = objWMIService.ConnectServer(".", "root\cimv2") + items = objSWbemServices.ExecQuery("SELECT * FROM Win32_USBControllerDevice") + ids = (item.Dependent.strip('"')[-8:] for item in items) + return [e for e in ids if e.startswith(serial_matcher)] diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index b395d2336..eb87ffbd7 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -1,132 +1,130 @@ # coding: utf-8 """ -This interface is for windows only, otherwise use socketCAN. +This interface is for Windows only, otherwise use socketCAN. """ -from __future__ import absolute_import, division +from __future__ import division, print_function, absolute_import import logging +from ctypes import byref -from can import BusABC, Message +from can import BusABC, Message, CanError from .usb2canabstractionlayer import * - -bootTimeEpoch = 0 -try: - import uptime - import datetime - - bootTimeEpoch = (uptime.boottime() - datetime.datetime.utcfromtimestamp(0)).total_seconds() -except: - bootTimeEpoch = 0 +from .serial_selector import find_serial_devices # Set up logging log = logging.getLogger('can.usb2can') -def format_connection_string(deviceID, baudrate='500'): - """setup the string for the device - - config = deviceID + '; ' + baudrate - """ - return "%s; %s" % (deviceID, baudrate) - - def message_convert_tx(msg): - messagetx = CanalMsg() + message_tx = CanalMsg() - length = len(msg.data) - messagetx.sizeData = length + length = msg.dlc + message_tx.sizeData = length - messagetx.id = msg.arbitration_id + message_tx.id = msg.arbitration_id for i in range(length): - messagetx.data[i] = msg.data[i] + message_tx.data[i] = msg.data[i] - messagetx.flags = 0x80000000 + message_tx.flags = 0x80000000 if msg.is_error_frame: - messagetx.flags |= IS_ERROR_FRAME + message_tx.flags |= IS_ERROR_FRAME if msg.is_remote_frame: - messagetx.flags |= IS_REMOTE_FRAME + message_tx.flags |= IS_REMOTE_FRAME if msg.is_extended_id: - messagetx.flags |= IS_ID_TYPE + message_tx.flags |= IS_ID_TYPE - return messagetx + return message_tx -def message_convert_rx(messagerx): +def message_convert_rx(message_rx): """convert the message from the CANAL type to pythoncan type""" - ID_TYPE = bool(messagerx.flags & IS_ID_TYPE) - REMOTE_FRAME = bool(messagerx.flags & IS_REMOTE_FRAME) - ERROR_FRAME = bool(messagerx.flags & IS_ERROR_FRAME) + is_extended_id = bool(message_rx.flags & IS_ID_TYPE) + is_remote_frame = bool(message_rx.flags & IS_REMOTE_FRAME) + is_error_frame = bool(message_rx.flags & IS_ERROR_FRAME) - msgrx = Message(timestamp=messagerx.timestamp, - is_remote_frame=REMOTE_FRAME, - is_extended_id=ID_TYPE, - is_error_frame=ERROR_FRAME, - arbitration_id=messagerx.id, - dlc=messagerx.sizeData, - data=messagerx.data[:messagerx.sizeData] - ) - - return msgrx + return Message(timestamp=message_rx.timestamp, + is_remote_frame=is_remote_frame, + is_extended_id=is_extended_id, + is_error_frame=is_error_frame, + arbitration_id=message_rx.id, + dlc=message_rx.sizeData, + data=message_rx.data[:message_rx.sizeData]) class Usb2canBus(BusABC): """Interface to a USB2CAN Bus. - :param str channel: + This interface only works on Windows. + Please use socketcan on Linux. + + :param str channel (optional): The device's serial number. If not provided, Windows Management Instrumentation - will be used to identify the first such device. The *kwarg* `serial` may also be - used. + will be used to identify the first such device. - :param int bitrate: + :param int bitrate (optional): Bitrate of channel in bit/s. Values will be limited to a maximum of 1000 Kb/s. Default is 500 Kbs - :param int flags: + :param int flags (optional): Flags to directly pass to open function of the usb2can abstraction layer. + + :param str dll (optional): + Path to the DLL with the CANAL API to load + Defaults to 'usb2can.dll' + + :param str serial (optional): + Alias for `channel` that is provided for legacy reasons. + If both `serial` and `channel` are set, `serial` will be used and + channel will be ignored. + """ - def __init__(self, channel, *args, **kwargs): + def __init__(self, channel=None, dll="usb2can.dll", flags=0x00000008, + bitrate=500000, *args, **kwargs): - self.can = Usb2CanAbstractionLayer() + self.can = Usb2CanAbstractionLayer(dll) - # set flags on the connection - if 'flags' in kwargs: - enable_flags = kwargs["flags"] + # get the serial number of the device + if "serial" in kwargs: + device_id = kwargs["serial"] else: - enable_flags = 0x00000008 + device_id = channel - # code to get the serial number of the device - if 'serial' in kwargs: - deviceID = kwargs["serial"] - elif channel is not None: - deviceID = channel - else: - from can.interfaces.usb2can.serial_selector import serial - deviceID = serial() + # search for a serial number if the device_id is None or empty + if not device_id: + devices = find_serial_devices() + if not devices: + raise CanError("could not automatically find any device") + device_id = devices[0] - # get baudrate in b/s from bitrate or use default - bitrate = kwargs.get("bitrate", 500000) - # convert to kb/s (eg:500000 bitrate must be 500), max rate is 1000 kb/s - baudrate = min(1000, int(bitrate/1000)) + # convert to kb/s and cap: max rate is 1000 kb/s + baudrate = min(int(bitrate // 1000), 1000) - connector = format_connection_string(deviceID, baudrate) + self.channel_info = "USB2CAN device {}".format(device_id) - self.handle = self.can.open(connector.encode('utf-8'), enable_flags) + connector = "{}; {}".format(device_id, baudrate) + self.handle = self.can.open(connector, flags) - super(Usb2canBus, self).__init__(channel=channel, *args, **kwargs) + super(Usb2canBus, self).__init__(channel=channel, dll=dll, flags=flags, + bitrate=bitrate, *args, **kwargs) def send(self, msg, timeout=None): tx = message_convert_tx(msg) + if timeout: - self.can.blocking_send(self.handle, byref(tx), int(timeout * 1000)) + status = self.can.blocking_send(self.handle, byref(tx), int(timeout * 1000)) else: - self.can.send(self.handle, byref(tx)) + status = self.can.send(self.handle, byref(tx)) + + if status != CANAL_ERROR_SUCCESS: + raise CanError("could not send message: status == {}".format(status)) + def _recv_internal(self, timeout): @@ -139,10 +137,9 @@ def _recv_internal(self, timeout): time = 0 if timeout is None else int(timeout * 1000) status = self.can.blocking_receive(self.handle, byref(messagerx), time) - if status == 0: + if status == CANAL_ERROR_SUCCESS: rx = message_convert_rx(messagerx) - elif status == 19 or status == 32: - # CANAL_ERROR_RCV_EMPTY or CANAL_ERROR_TIMEOUT + elif status == CANAL_ERROR_RCV_EMPTY or status == CANAL_ERROR_TIMEOUT: rx = None else: log.error('Canal Error %s', status) @@ -151,6 +148,27 @@ def _recv_internal(self, timeout): return rx, False def shutdown(self): - """Shut down the device safely""" - # TODO handle error + """ + Shuts down connection to the device safely. + + :raise cam.CanError: is closing the connection did not work + """ status = self.can.close(self.handle) + + if status != CANAL_ERROR_SUCCESS: + raise CanError("could not shut down bus: status == {}".format(status)) + + @staticmethod + def _detect_available_configs(serial_matcher=None): + """ + Uses the Windows Management Instrumentation to identify serial devices. + + :param str serial_matcher (optional): + search string for automatic detection of the device serial + """ + if serial_matcher: + channels = find_serial_devices(serial_matcher) + else: + channels = find_serial_devices() + + return [{'interface': 'usb2can', 'channel': c} for c in channels] diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index 903b6d458..0a900443b 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -5,11 +5,14 @@ Socket CAN is recommended under Unix/Linux systems. """ -import can +from __future__ import division, print_function, absolute_import + from ctypes import * from struct import * import logging +import can + log = logging.getLogger('can.usb2can') # type definitions @@ -17,13 +20,17 @@ pConfigureStr = c_char_p handle = c_long timeout = c_ulong -filter = c_ulong +filter_t = c_ulong # flags mappings IS_ERROR_FRAME = 4 IS_REMOTE_FRAME = 2 IS_ID_TYPE = 1 +CANAL_ERROR_SUCCESS = 0 +CANAL_ERROR_RCV_EMPTY = 19 +CANAL_ERROR_TIMEOUT = 32 + class CanalStatistics(Structure): _fields_ = [('ReceiveFrams', c_ulong), @@ -59,21 +66,45 @@ class Usb2CanAbstractionLayer: """A low level wrapper around the usb2can library. Documentation: http://www.8devices.com/media/products/usb2can/downloads/CANAL_API.pdf - """ - def __init__(self): - self.__m_dllBasic = windll.LoadLibrary("usb2can.dll") + + def __init__(self, dll="usb2can.dll"): + """ + :type dll: str or path-like + :param dll (optional): the path to the usb2can DLL to load + :raises OSError: if the DLL could not be loaded + """ + self.__m_dllBasic = windll.LoadLibrary(dll) if self.__m_dllBasic is None: - log.warning('DLL failed to load') + log.warning('DLL failed to load at path: {}'.format(dll)) + + def open(self, configuration, flags): + """ + Opens a CAN connection using `CanalOpen()`. + + :param str configuration: the configuration: "device_id; baudrate" + :param int flags: the flags to be set - def open(self, pConfigureStr, flags): + :raises can.CanError: if any error occurred + :returns: Nothing + """ try: - res = self.__m_dllBasic.CanalOpen(pConfigureStr, flags) - return res - except: - log.warning('Failed to open') - raise + # we need to convert this into bytes, since the underlying DLL cannot + # handle non-ASCII configuration strings + config_ascii = configuration.encode('ascii', 'ignore') + result = self.__m_dllBasic.CanalOpen(config_ascii, flags) + except Exception as ex: + # catch any errors thrown by this call and re-raise + raise can.CanError('CanalOpen() failed, configuration: "{}", error: {}' + .format(configuration, ex)) + else: + # any greater-than-zero return value indicates a success + # (see https://grodansparadis.gitbooks.io/the-vscp-daemon/canal_interface_specification.html) + # raise an error if the return code is <= 0 + if result <= 0: + raise can.CanError('CanalOpen() failed, configuration: "{}", return code: {}' + .format(configuration, result)) def close(self, handle): try: diff --git a/doc/interfaces/usb2can.rst b/doc/interfaces/usb2can.rst index 7bf01a64c..e2e8d7517 100644 --- a/doc/interfaces/usb2can.rst +++ b/doc/interfaces/usb2can.rst @@ -27,7 +27,7 @@ WINDOWS INSTALL 2. Install the appropriate version of `pywin32 `_ (win32com) 3. Download the USB2CAN CANAL DLL from the USB2CAN website. Place this in either the same directory you are running usb2can.py from or your DLL folder in your python install. Note that only a 32-bit version is currently available, so this only works in a 32-bit Python environment. - (Written against CANAL DLL version v1.0.6) + (Written against CANAL DLL version v1.0.6) Interface Layout ---------------- From 15c8016ecbb637617851a139b930e9e2a704bb2a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 14 Mar 2019 10:06:14 +0100 Subject: [PATCH 0064/1235] Remove support for Python 3.4 + update testing dependencies (#532) * remove support for Python 3.4 * update testing dependencies * update README.rst --- .appveyor.yml | 6 ++---- .travis.yml | 3 +-- README.rst | 2 +- setup.py | 22 +++++++++------------- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index ee7b98300..3b52143e7 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -2,16 +2,14 @@ environment: matrix: # For Python versions available on Appveyor, see - # http://www.appveyor.com/docs/installed-software#python - # Python 3.0-3.3 have reached EOL + # https://www.appveyor.com/docs/windows-images-software/#python + # Python pre-2.7 and 3.0-3.4 have reached EOL - PYTHON: "C:\\Python27" - - PYTHON: "C:\\Python34" - PYTHON: "C:\\Python35" - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python37" - PYTHON: "C:\\Python27-x64" - - PYTHON: "C:\\Python34-x64" - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36-x64" - PYTHON: "C:\\Python37-x64" diff --git a/.travis.yml b/.travis.yml index 5f8204b29..82a52a0d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: python python: - # CPython; versions 3.0-3.3 have reached EOL + # CPython; versions pre-2.7 and 3.0-3.4 have reached EOL - "2.7" - - "3.4" - "3.5" - "3.6" - "3.7-dev" # TODO: change to "3.7" once it is supported by travis-ci diff --git a/README.rst b/README.rst index 562c1de6c..12b917355 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ Python developers; providing common abstractions to different hardware devices, and a suite of utilities for sending and receiving messages on a can bus. -The library supports Python 2.7, Python 3.4+ as well as PyPy 2 & 3 and runs on Mac, Linux and Windows. +The library supports Python 2.7, Python 3.5+ as well as PyPy 2 & 3 and runs on Mac, Linux and Windows. Features diff --git a/setup.py b/setup.py index c4127baa0..7c1fe1424 100644 --- a/setup.py +++ b/setup.py @@ -30,10 +30,9 @@ tests_require = [ 'mock~=2.0', - 'nose~=1.3', - 'pytest~=3.6', - 'pytest-timeout~=1.2', - 'pytest-cov~=2.5', + 'pytest~=4.3', + 'pytest-timeout~=1.3', + 'pytest-cov~=2.6', 'codecov~=2.0', 'future', 'six', @@ -53,7 +52,6 @@ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", @@ -80,7 +78,7 @@ # Code version=version, - packages=find_packages(exclude=["test", "test.*"]), + packages=find_packages(exclude=["test", "doc", "scripts", "examples"]), scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), # Author @@ -92,21 +90,19 @@ # Package data package_data={ - "": ["CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], - "doc": ["*.*"] + "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], + "doc": ["*.*"], + "examples": ["*.py"] }, # Installation # see https://www.python.org/dev/peps/pep-0345/#version-specifiers - python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", + python_requires=">=2.7", install_requires=[ 'wrapt~=1.10', 'typing;python_version<"3.5"', 'windows-curses;platform_system=="Windows"', ], extras_require=extras_require, - - # Testing - test_suite="nose.collector", - tests_require=tests_require, + tests_require=tests_require ) From 2dd02d1628eca6814fa326f5a775bcf0efd4cce4 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 9 Mar 2019 02:04:15 +0100 Subject: [PATCH 0065/1235] Simplify virtual can a little bit --- can/interfaces/virtual.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index ce863dc5e..6937a4877 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -8,7 +8,7 @@ and reside in the same process will receive the same messages. """ -import copy +from copy import deepcopy import logging import time try: @@ -40,6 +40,8 @@ class VirtualBus(BusABC): Implements :meth:`can.BusABC._detect_available_configs`; see :meth:`can.VirtualBus._detect_available_configs` for how it behaves here. + + The timeout when sending a message applies to each receiver. """ def __init__(self, channel=None, receive_own_messages=False, @@ -82,13 +84,12 @@ def _recv_internal(self, timeout): def send(self, msg, timeout=None): self._check_if_open() - # Create a shallow copy for this channel - msg_copy = copy.copy(msg) + + msg_copy = deepcopy(msg) msg_copy.timestamp = time.time() - msg_copy.data = bytearray(msg.data) - msg_copy.channel = self.channel_id - all_sent = True + # Add message to all listening on this channel + all_sent = True for bus_queue in self.channel: if bus_queue is not self.queue or self.receive_own_messages: try: From c6d770c7f25755d7d949530a57ba095d2f0313ea Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 9 Mar 2019 02:40:07 +0100 Subject: [PATCH 0066/1235] readd channel --- can/interfaces/virtual.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 6937a4877..3796a53d6 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -87,6 +87,7 @@ def send(self, msg, timeout=None): msg_copy = deepcopy(msg) msg_copy.timestamp = time.time() + msg_copy.channel = self.channel_id # Add message to all listening on this channel all_sent = True From 4afaf823bdcad3f552c602692b0058f4ec55b789 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 10 Mar 2019 22:36:20 +0100 Subject: [PATCH 0067/1235] clearify documentation --- can/interfaces/virtual.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 3796a53d6..07a74b9ce 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -41,7 +41,10 @@ class VirtualBus(BusABC): :meth:`can.VirtualBus._detect_available_configs` for how it behaves here. - The timeout when sending a message applies to each receiver. + .. note:: + The timeout when sending a message applies to each receiver + individually. This means that sending can block up to 5 seconds + if a message is sent to 5 receivers with the timeout set to 1.0. """ def __init__(self, channel=None, receive_own_messages=False, @@ -51,7 +54,7 @@ def __init__(self, channel=None, receive_own_messages=False, # the channel identifier may be an arbitrary object self.channel_id = channel - self.channel_info = 'Virtual bus channel %s' % self.channel_id + self.channel_info = "Virtual bus channel {}".format(self.channel_id) self.receive_own_messages = receive_own_messages self._open = True @@ -71,7 +74,7 @@ def _check_if_open(self): Has to be called in every method that accesses the bus. """ if not self._open: - raise CanError('Operation on closed bus') + raise CanError("Operation on closed bus") def _recv_internal(self, timeout): self._check_if_open() @@ -98,7 +101,7 @@ def send(self, msg, timeout=None): except queue.Full: all_sent = False if not all_sent: - raise CanError('Could not send message to one or more recipients') + raise CanError("Could not send message to one or more recipients") def shutdown(self): self._check_if_open() From 6090e945ac9ec1f559c94f8f3af5dfd83644fb54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Thu, 14 Mar 2019 15:11:34 +0100 Subject: [PATCH 0068/1235] Fix missing handle for CANAL API functions. The internal reference for an opened interface via the CANAL API is returned as an integer from the CanalOpen() DLL call in Usb2CanAbstractionLayer.open(). It is supposed to be returned from the abstraction layer and stored in the Usb2canBus.handle member for later reference by all other API calls. Add the missing return statement and adjust the docstring accordingly. Only valid handles (greater than 0) will be returned, otherwise an exception is thrown already. --- can/interfaces/usb2can/usb2canabstractionlayer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index 0a900443b..a318bcd6f 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -87,7 +87,7 @@ def open(self, configuration, flags): :param int flags: the flags to be set :raises can.CanError: if any error occurred - :returns: Nothing + :returns: Valid handle for CANAL API functions on success """ try: # we need to convert this into bytes, since the underlying DLL cannot @@ -105,6 +105,8 @@ def open(self, configuration, flags): if result <= 0: raise can.CanError('CanalOpen() failed, configuration: "{}", return code: {}' .format(configuration, result)) + else: + return result def close(self, handle): try: From b6c8752de012d6f478a235a5a6db35c329ce692d Mon Sep 17 00:00:00 2001 From: Peter Andersen Date: Wed, 20 Mar 2019 09:08:37 -0700 Subject: [PATCH 0069/1235] Updated instances of **config to **kwargs, closes #492 --- can/bus.py | 4 ++-- can/interface.py | 28 +++++++++++------------ can/interfaces/ics_neovi/neovi_bus.py | 28 +++++++++++------------ can/interfaces/ixxat/canlib.py | 16 +++++++------- can/interfaces/kvaser/canlib.py | 32 +++++++++++++-------------- can/interfaces/systec/ucanbus.py | 20 ++++++++--------- can/interfaces/vector/canlib.py | 7 +++--- can/interfaces/virtual.py | 6 ++--- 8 files changed, 70 insertions(+), 71 deletions(-) diff --git a/can/bus.py b/can/bus.py index 1a637441c..a15c37985 100644 --- a/can/bus.py +++ b/can/bus.py @@ -33,7 +33,7 @@ class BusABC(object): RECV_LOGGING_LEVEL = 9 @abstractmethod - def __init__(self, channel, can_filters=None, **config): + def __init__(self, channel, can_filters=None, **kwargs): """Construct and open a CAN bus instance of the specified type. Subclasses should call though this method with all given parameters @@ -45,7 +45,7 @@ def __init__(self, channel, can_filters=None, **config): :param list can_filters: See :meth:`~can.BusABC.set_filters` for details. - :param dict config: + :param dict kwargs: Any backend dependent configurations are passed in this dictionary """ self._periodic_tasks = [] diff --git a/can/interface.py b/can/interface.py index 252ef9c75..6c830d0c4 100644 --- a/can/interface.py +++ b/can/interface.py @@ -75,7 +75,7 @@ class Bus(BusABC): """ @staticmethod - def __new__(cls, channel=None, *args, **config): + def __new__(cls, channel=None, *args, **kwargs): """ Takes the same arguments as :class:`can.BusABC.__init__`. Some might have a special meaning, see below. @@ -86,7 +86,7 @@ def __new__(cls, channel=None, *args, **config): Expected type is backend dependent. - :param dict config: + :param dict kwargs: Should contain an ``interface`` key with a valid interface name. If not, it is completed using :meth:`can.util.load_config`. @@ -99,32 +99,32 @@ def __new__(cls, channel=None, *args, **config): # figure out the rest of the configuration; this might raise an error if channel is not None: - config['channel'] = channel - if 'context' in config: - context = config['context'] - del config['context'] + kwargs['channel'] = channel + if 'context' in kwargs: + context = kwargs['context'] + del kwargs['context'] else: context = None - config = load_config(config=config, context=context) + kwargs = load_config(config=kwargs, context=context) # resolve the bus class to use for that interface - cls = _get_class_for_interface(config['interface']) + cls = _get_class_for_interface(kwargs['interface']) # remove the 'interface' key so it doesn't get passed to the backend - del config['interface'] + del kwargs['interface'] # make sure the bus can handle this config format - if 'channel' not in config: + if 'channel' not in kwargs: raise ValueError("'channel' argument missing") else: - channel = config['channel'] - del config['channel'] + channel = kwargs['channel'] + del kwargs['channel'] if channel is None: # Use the default channel for the backend - return cls(*args, **config) + return cls(*args, **kwargs) else: - return cls(channel, *args, **config) + return cls(channel, *args, **kwargs) def detect_available_configs(interfaces=None): diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 82bb7d28d..4baee6177 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -66,7 +66,7 @@ class NeoViBus(BusABC): https://github.com/intrepidcs/python_ics """ - def __init__(self, channel, can_filters=None, **config): + def __init__(self, channel, can_filters=None, **kwargs): """ :param channel: The channel ids to create this bus with. @@ -98,13 +98,13 @@ def __init__(self, channel, can_filters=None, **config): raise ImportError('Please install python-ics') super(NeoViBus, self).__init__( - channel=channel, can_filters=can_filters, **config) + channel=channel, can_filters=can_filters, **kwargs) logger.info("CAN Filters: {}".format(can_filters)) - logger.info("Got configuration of: {}".format(config)) + logger.info("Got configuration of: {}".format(kwargs)) - if 'override_library_name' in config: - ics.override_library_name(config.get('override_library_name')) + if 'override_library_name' in kwargs: + ics.override_library_name(kwargs.get('override_library_name')) if isinstance(channel, (list, tuple)): self.channels = channel @@ -115,26 +115,26 @@ def __init__(self, channel, can_filters=None, **config): self.channels = [ch.strip() for ch in channel.split(',')] self.channels = [NeoViBus.channel_to_netid(ch) for ch in self.channels] - type_filter = config.get('type_filter') - serial = config.get('serial') + type_filter = kwargs.get('type_filter') + serial = kwargs.get('serial') self.dev = self._find_device(type_filter, serial) ics.open_device(self.dev) - if 'bitrate' in config: + if 'bitrate' in kwargs: for channel in self.channels: - ics.set_bit_rate(self.dev, config.get('bitrate'), channel) + ics.set_bit_rate(self.dev, kwargs.get('bitrate'), channel) - fd = config.get('fd', False) + fd = kwargs.get('fd', False) if fd: - if 'data_bitrate' in config: + if 'data_bitrate' in kwargs: for channel in self.channels: ics.set_fd_bit_rate( - self.dev, config.get('data_bitrate'), channel) + self.dev, kwargs.get('data_bitrate'), channel) self._use_system_timestamp = bool( - config.get('use_system_timestamp', False) + kwargs.get('use_system_timestamp', False) ) - self._receive_own_messages = config.get('receive_own_messages', True) + self._receive_own_messages = kwargs.get('receive_own_messages', True) self.channel_info = '%s %s CH:%s' % ( self.dev.Name, diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index e48323e01..84c8751c1 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -272,7 +272,7 @@ class IXXATBus(BusABC): } } - def __init__(self, channel, can_filters=None, **config): + def __init__(self, channel, can_filters=None, **kwargs): """ :param int channel: The Channel id to create this bus with. @@ -292,13 +292,13 @@ def __init__(self, channel, can_filters=None, **config): if _canlib is None: raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") log.info("CAN Filters: %s", can_filters) - log.info("Got configuration of: %s", config) + log.info("Got configuration of: %s", kwargs) # Configuration options - bitrate = config.get('bitrate', 500000) - UniqueHardwareId = config.get('UniqueHardwareId', None) - rxFifoSize = config.get('rxFifoSize', 16) - txFifoSize = config.get('txFifoSize', 16) - self._receive_own_messages = config.get('receive_own_messages', False) + bitrate = kwargs.get('bitrate', 500000) + UniqueHardwareId = kwargs.get('UniqueHardwareId', None) + rxFifoSize = kwargs.get('rxFifoSize', 16) + txFifoSize = kwargs.get('txFifoSize', 16) + self._receive_own_messages = kwargs.get('receive_own_messages', False) # Usually comes as a string from the config file channel = int(channel) @@ -395,7 +395,7 @@ def __init__(self, channel, can_filters=None, **config): except (VCITimeout, VCIRxQueueEmptyError): break - super(IXXATBus, self).__init__(channel=channel, can_filters=None, **config) + super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) def _inWaiting(self): try: diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 34f7f6e7c..fa3a70221 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -302,7 +302,7 @@ class KvaserBus(BusABC): The CAN Bus implemented for the Kvaser interface. """ - def __init__(self, channel, can_filters=None, **config): + def __init__(self, channel, can_filters=None, **kwargs): """ :param int channel: The Channel id to create this bus with. @@ -353,18 +353,18 @@ def __init__(self, channel, can_filters=None, **config): """ log.info("CAN Filters: {}".format(can_filters)) - log.info("Got configuration of: {}".format(config)) - bitrate = config.get('bitrate', 500000) - tseg1 = config.get('tseg1', 0) - tseg2 = config.get('tseg2', 0) - sjw = config.get('sjw', 0) - no_samp = config.get('no_samp', 0) - driver_mode = config.get('driver_mode', DRIVER_MODE_NORMAL) - single_handle = config.get('single_handle', False) - receive_own_messages = config.get('receive_own_messages', False) - accept_virtual = config.get('accept_virtual', True) - fd = config.get('fd', False) - data_bitrate = config.get('data_bitrate', None) + log.info("Got configuration of: {}".format(kwargs)) + bitrate = kwargs.get('bitrate', 500000) + tseg1 = kwargs.get('tseg1', 0) + tseg2 = kwargs.get('tseg2', 0) + sjw = kwargs.get('sjw', 0) + no_samp = kwargs.get('no_samp', 0) + driver_mode = kwargs.get('driver_mode', DRIVER_MODE_NORMAL) + single_handle = kwargs.get('single_handle', False) + receive_own_messages = kwargs.get('receive_own_messages', False) + accept_virtual = kwargs.get('accept_virtual', True) + fd = kwargs.get('fd', False) + data_bitrate = kwargs.get('data_bitrate', None) try: channel = int(channel) @@ -400,7 +400,7 @@ def __init__(self, channel, can_filters=None, **config): 4) if fd: - if 'tseg1' not in config and bitrate in BITRATE_FD: + if 'tseg1' not in kwargs and bitrate in BITRATE_FD: # Use predefined bitrate for arbitration bitrate = BITRATE_FD[bitrate] if data_bitrate in BITRATE_FD: @@ -411,7 +411,7 @@ def __init__(self, channel, can_filters=None, **config): data_bitrate = bitrate canSetBusParamsFd(self._read_handle, data_bitrate, tseg1, tseg2, sjw) else: - if 'tseg1' not in config and bitrate in BITRATE_OBJS: + if 'tseg1' not in kwargs and bitrate in BITRATE_OBJS: bitrate = BITRATE_OBJS[bitrate] canSetBusParams(self._read_handle, bitrate, tseg1, tseg2, sjw, no_samp, 0) @@ -446,7 +446,7 @@ def __init__(self, channel, can_filters=None, **config): self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) self._is_filtered = False - super(KvaserBus, self).__init__(channel=channel, can_filters=can_filters, **config) + super(KvaserBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) def _apply_filters(self, filters): if filters and len(filters) == 1: diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 6426e883f..f776c93ae 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -48,7 +48,7 @@ class UcanBus(BusABC): 1000000: Baudrate.BAUD_1MBit } - def __init__(self, channel, can_filters=None, **config): + def __init__(self, channel, can_filters=None, **kwargs): """ :param int channel: The Channel id to create this bus with. @@ -96,14 +96,14 @@ def __init__(self, channel, can_filters=None, **config): raise ImportError("The SYSTEC ucan library has not been initialized.") self.channel = int(channel) - device_number = int(config.get('device_number', ANY_MODULE)) + device_number = int(kwargs.get('device_number', ANY_MODULE)) # configuration options - bitrate = config.get('bitrate', 500000) + bitrate = kwargs.get('bitrate', 500000) if bitrate not in self.BITRATES: raise ValueError("Invalid bitrate {}".format(bitrate)) - state = config.get('state', BusState.ACTIVE) + state = kwargs.get('state', BusState.ACTIVE) if state is BusState.ACTIVE or BusState.PASSIVE: self._state = state else: @@ -112,15 +112,15 @@ def __init__(self, channel, can_filters=None, **config): # get parameters self._params = { "mode": Mode.MODE_NORMAL | - (Mode.MODE_TX_ECHO if config.get('receive_own_messages') else 0) | + (Mode.MODE_TX_ECHO if kwargs.get('receive_own_messages') else 0) | (Mode.MODE_LISTEN_ONLY if state is BusState.PASSIVE else 0), "BTR": self.BITRATES[bitrate] } # get extra parameters - if config.get("rx_buffer_entries"): - self._params["rx_buffer_entries"] = int(config.get("rx_buffer_entries")) - if config.get("tx_buffer_entries"): - self._params["tx_buffer_entries"] = int(config.get("tx_buffer_entries")) + if kwargs.get("rx_buffer_entries"): + self._params["rx_buffer_entries"] = int(kwargs.get("rx_buffer_entries")) + if kwargs.get("tx_buffer_entries"): + self._params["tx_buffer_entries"] = int(kwargs.get("tx_buffer_entries")) self._ucan.init_hardware(device_number=device_number) self._ucan.init_can(self.channel, **self._params) @@ -131,7 +131,7 @@ def __init__(self, channel, can_filters=None, **config): self.channel, self._ucan.get_baudrate_message(self.BITRATES[bitrate]) ) - super(UcanBus, self).__init__(channel=channel, can_filters=can_filters, **config) + super(UcanBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) def _recv_internal(self, timeout): message, _ = self._ucan.read_can_msg(self.channel, 1, timeout) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 1b84f3255..251b9fa56 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -49,7 +49,9 @@ class VectorBus(BusABC): def __init__(self, channel, can_filters=None, poll_interval=0.01, receive_own_messages=False, - bitrate=None, rx_queue_size=2**14, app_name="CANalyzer", serial=None, fd=False, data_bitrate=None, sjwAbr=2, tseg1Abr=6, tseg2Abr=3, sjwDbr=2, tseg1Dbr=6, tseg2Dbr=3, **config): + bitrate=None, rx_queue_size=2**14, app_name="CANalyzer", + serial=None, fd=False, data_bitrate=None, sjwAbr=2, tseg1Abr=6, + tseg2Abr=3, sjwDbr=2, tseg1Dbr=6, tseg2Dbr=3, **kwargs): """ :param list channel: The channel indexes to create this bus with. @@ -208,8 +210,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self._time_offset = time.time() - offset.value * 1e-9 self._is_filtered = False - super(VectorBus, self).__init__(channel=channel, can_filters=can_filters, - **config) + super(VectorBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) def _apply_filters(self, filters): if filters: diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 07a74b9ce..163579387 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -47,10 +47,8 @@ class VirtualBus(BusABC): if a message is sent to 5 receivers with the timeout set to 1.0. """ - def __init__(self, channel=None, receive_own_messages=False, - rx_queue_size=0, **config): - super(VirtualBus, self).__init__(channel=channel, - receive_own_messages=receive_own_messages, **config) + def __init__(self, channel=None, receive_own_messages=False, rx_queue_size=0, **kwargs): + super(VirtualBus, self).__init__(channel=channel, receive_own_messages=receive_own_messages, **kwargs) # the channel identifier may be an arbitrary object self.channel_id = channel From dd89de4974d141a0b46c179133679b667669e185 Mon Sep 17 00:00:00 2001 From: Bill Hass Date: Mon, 1 Apr 2019 14:49:44 -0400 Subject: [PATCH 0070/1235] Fixes BusState.PASSIVE from config file for PCAN Before, the config value was not being actually sent to the PEAK hardware due to the setter not being invoked. Instead, the private _state variable was being written to directly. Now, the setter is being invoked and the state is being properly sent to the PEAK hardware. --- CONTRIBUTORS.txt | 1 + can/interfaces/pcan/pcan.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index c8129fb27..9a37da3b5 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -26,3 +26,4 @@ Shaoyu Meng Alexander Mueller Jan Goeteyn "ykzheng" +Lear Corporation diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 09e9457b4..6fb0e5185 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -98,7 +98,7 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 self.m_PcanHandle = globals()[channel] if state is BusState.ACTIVE or BusState.PASSIVE: - self._state = state + self.state = state else: raise ArgumentError("BusState must be Active or Passive") From f848e9f686447f1880c6dbae1e31649b578df997 Mon Sep 17 00:00:00 2001 From: Bill Hass Date: Mon, 1 Apr 2019 11:14:46 -0400 Subject: [PATCH 0071/1235] Fixed simplecyclic test Originally, the testcase would fail because timestamps would not match. This was due to a Can.Message being initialized had a timestamp value of 0, while a recv'd message had a virtual timestamp. When compared they never matched. Modified testcase now allows for some variation (0.016 s) in the timestamp for a 0.01 s periodic message and compares the last message with the next last message. --- test/simplecyclic_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 40a2e8685..d45069bbc 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -20,7 +20,7 @@ class SimpleCyclicSendTaskTest(unittest.TestCase, ComparingMessagesTestCase): def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) - ComparingMessagesTestCase.__init__(self, allowed_timestamp_delta=None, preserves_channel=True) + ComparingMessagesTestCase.__init__(self, allowed_timestamp_delta=0.016, preserves_channel=True) @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_cycle_time(self): @@ -36,7 +36,8 @@ def test_cycle_time(self): self.assertTrue(80 <= size <= 120, '100 +/- 20 messages should have been transmitted. But queue contained {}'.format(size)) last_msg = bus2.recv() - self.assertMessageEqual(last_msg, msg) + next_last_msg = bus2.recv() + self.assertMessageEqual(last_msg, next_last_msg) bus1.shutdown() bus2.shutdown() From b1d64cf28ebff8c9d5ba7a6f80b6f516f47eaf7b Mon Sep 17 00:00:00 2001 From: Bill Hass Date: Thu, 4 Apr 2019 14:11:10 -0400 Subject: [PATCH 0072/1235] Readded test to compare sent msg and recvd msg in simplecyclic --- test/simplecyclic_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index d45069bbc..a8896a16f 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -37,8 +37,18 @@ def test_cycle_time(self): '100 +/- 20 messages should have been transmitted. But queue contained {}'.format(size)) last_msg = bus2.recv() next_last_msg = bus2.recv() + # Check consecutive messages are spaced properly in time and have + # the same id/data self.assertMessageEqual(last_msg, next_last_msg) + + # Check the message id/data sent is the same as message received + # Set timestamp and channel to match recv'd because we don't care + # and they are not initialized by the can.Message constructor. + msg.timestamp = last_msg.timestamp + msg.channel = last_msg.channel + self.assertMessageEqual(msg, last_msg) + bus1.shutdown() bus2.shutdown() From 16637643d1519acad96962b2dbc59861c9ed092c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 6 Apr 2019 12:18:18 +0200 Subject: [PATCH 0073/1235] Fix Dead Link (#546) Closes #545. --- examples/send_one.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/send_one.py b/examples/send_one.py index 67fddf437..2533ca37c 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -12,7 +12,7 @@ def send_one(): # this uses the default configuration (for example from the config file) - # see http://python-can.readthedocs.io/en/latest/configuration.html + # see https://python-can.readthedocs.io/en/stable/configuration.html bus = can.interface.Bus() # Using specific buses works similar: From a13138db13ad84ce640bbdbacfe9edf6ee2ca945 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 16 Mar 2019 15:58:29 +1100 Subject: [PATCH 0074/1235] Apply travis lint and stop building/testing on Python 3.5 --- .travis.yml | 77 ++++++++++++++++++----------------------------------- 1 file changed, 26 insertions(+), 51 deletions(-) diff --git a/.travis.yml b/.travis.yml index 82a52a0d5..cb38b28f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,61 +1,36 @@ language: python - python: - # CPython; versions pre-2.7 and 3.0-3.4 have reached EOL - - "2.7" - - "3.5" - - "3.6" - - "3.7-dev" # TODO: change to "3.7" once it is supported by travis-ci - - "nightly" - # PyPy: - - "pypy" # Python 2.7 - - "pypy3.5" # Python 3.5 - +- '2.7' +- '3.6' +- 3.7-dev +- nightly +- pypy +- pypy3.5 os: - - linux # Linux is officially supported and we test the library under - # many different Python verions (see "python: ..." above) - -# - osx # OSX + Python is not officially supported by Travis CI as of Feb. 2018 - # nevertheless, "nightly" and some "*-dev" versions seem to work, so we - # include them explicitly below (see "matrix: include: ..." below). - # They only seem to work with the xcode8.3 image, and not the newer ones. - # Thus we will leave this in, until it breaks one day, at which point we - # will probably reomve testing on OSX if it is not supported then. - # See #385 on Github. - -# - windows # Windows is not supported at all by Travis CI as of Feb. 2018 - -# Linux setup +- linux dist: trusty sudo: required - matrix: - # see "os: ..." above include: - - os: osx - osx_image: xcode8.3 - python: "3.6-dev" - - os: osx - osx_image: xcode8.3 - python: "3.7-dev" - - os: osx - osx_image: xcode8.3 - python: "nightly" - + - os: osx + osx_image: xcode8.3 + python: 3.6-dev + - os: osx + osx_image: xcode8.3 + python: 3.7-dev + - os: osx + osx_image: xcode8.3 + python: nightly allow_failures: - # allow all nighly builds to fail, since these python versions might be unstable - - python: "nightly" - # we do not allow dev builds to fail, since these builds are considered stable enough - + - python: nightly install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi - - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then travis_retry pip install -r doc/doc-requirements.txt; fi - - travis_retry pip install .[test] - +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi +- if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then travis_retry pip install -r doc/doc-requirements.txt; + fi +- travis_retry pip install .[test] script: - - pytest - - codecov - # Build Docs with Sphinx - # -a Write all files - # -n nitpicky - - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then python -m sphinx -an doc build; fi +- pytest +- codecov +- if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then python -m sphinx -an doc build; + fi + From e2562f65dc85c58e27580b7fb6d398841bee8dcd Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 16 Mar 2019 16:02:32 +1100 Subject: [PATCH 0075/1235] Start building/testing on Python 3.8-dev --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cb38b28f7..f4decc232 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ python: - '2.7' - '3.6' - 3.7-dev +- 3.8-dev - nightly - pypy - pypy3.5 From 4a288d46fb34b8f11368bf371dee6d5388af1051 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 16 Mar 2019 16:03:22 +1100 Subject: [PATCH 0076/1235] Have travis deploy tagged commits to PyPi --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f4decc232..9059ec67f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,4 +34,11 @@ script: - codecov - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then python -m sphinx -an doc build; fi - +deploy: + provider: pypi + user: hardbyte + password: + secure: oQ9XpEkcilkZgKp+rKvPb2J1GrZe2ZvtOq/IjzCpiA8NeWixl/ai3BkPrLbd8t1wNIFoGwx7IQ7zxWL79aPYeG6XrljEomv3g45NR6dkQewUH+dQFlnT75Rm96Ycxvme0w1+71vM4PqxIuzyXUrF2n7JjC0XCCxHdTuYmPGbxVO1fOsE5R5b9inAbpEUtJuWz5AIrDEZ0OgoQpLSC8fLwbymTThX3JZ5GBLpRScVvLazjIYfRkZxvCqQ4mp1UNTdoMzekxsvxOOcEW6+j3fQO+Q/8uvMksKP0RgT8HE69oeYOeVic4Q4wGqORw+ur4A56NvBqVKtizVLCzzEG9ZfoSDy7ryvGWGZykkh8HX0PFQAEykC3iYihHK8ZFz5bEqRMegTmuRYZwPsel61wVd5posxnQkGm0syIoJNKuuRc5sUK+E3GviYcT8NntdR+4WBrvpQAYa1ZHpVrfnQXyaDmGzOjwCRGPoIDJweEqGVmLycEC5aT8rX3/W9tie9iPnjmFJh4CwNMxDgVQRo80m6Gtlf/DQpA3mH39IvWGqd5fHdTPxYPs32EQSCsaYLJV5pM8xBNv6M2S/KriGnGZU0xT7MEr46da0LstKsK/U8O0yamjyugMvQoC3zQcKLrDzWFSBsT7/vG+AuV5SK8yzfEHugo7jkPQQ+NTw29xzk4dY= + on: + tags: true + skip_cleanup: true \ No newline at end of file From 78ad867612e66a60f07f7193d9f8c13c62c97217 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 16 Mar 2019 23:06:11 +1100 Subject: [PATCH 0077/1235] remove python 3.8 for now --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9059ec67f..de586b350 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ python: - '2.7' - '3.6' - 3.7-dev -- 3.8-dev - nightly - pypy - pypy3.5 From 1c955757a62d70e6592f14fbb78fa9e54df97a24 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 6 Apr 2019 21:24:19 +1100 Subject: [PATCH 0078/1235] Revert "Apply travis lint" This reverts commit 9669ab503c1ffc85d9500d0867cfae5d5f4fea2b. --- .travis.yml | 78 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index de586b350..06cbd460a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,38 +1,66 @@ language: python + python: -- '2.7' -- '3.6' -- 3.7-dev -- nightly -- pypy -- pypy3.5 + # CPython; versions pre-2.7 and 3.0-3.4 have reached EOL + - "2.7" + - "3.5" + - "3.6" + - "3.7-dev" # TODO: change to "3.7" once it is supported by travis-ci + - "nightly" + # PyPy: + - "pypy" # Python 2.7 + - "pypy3.5" # Python 3.5 + os: -- linux + - linux # Linux is officially supported and we test the library under + # many different Python verions (see "python: ..." above) + +# - osx # OSX + Python is not officially supported by Travis CI as of Feb. 2018 + # nevertheless, "nightly" and some "*-dev" versions seem to work, so we + # include them explicitly below (see "matrix: include: ..." below). + # They only seem to work with the xcode8.3 image, and not the newer ones. + # Thus we will leave this in, until it breaks one day, at which point we + # will probably reomve testing on OSX if it is not supported then. + # See #385 on Github. + +# - windows # Windows is not supported at all by Travis CI as of Feb. 2018 + +# Linux setup dist: trusty sudo: required + matrix: + # see "os: ..." above include: - - os: osx - osx_image: xcode8.3 - python: 3.6-dev - - os: osx - osx_image: xcode8.3 - python: 3.7-dev - - os: osx - osx_image: xcode8.3 - python: nightly + - os: osx + osx_image: xcode8.3 + python: "3.6-dev" + - os: osx + osx_image: xcode8.3 + python: "3.7-dev" + - os: osx + osx_image: xcode8.3 + python: "nightly" + allow_failures: - - python: nightly + # allow all nighly builds to fail, since these python versions might be unstable + - python: "nightly" + # we do not allow dev builds to fail, since these builds are considered stable enough + install: -- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi -- if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then travis_retry pip install -r doc/doc-requirements.txt; - fi -- travis_retry pip install .[test] + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi + - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then travis_retry pip install -r doc/doc-requirements.txt; fi + - travis_retry pip install .[test] + script: -- pytest -- codecov -- if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then python -m sphinx -an doc build; - fi + - pytest + - codecov + # Build Docs with Sphinx + # -a Write all files + # -n nitpicky + - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then python -m sphinx -an doc build; fi + +# Have travis deploy tagged commits to PyPi deploy: provider: pypi user: hardbyte From c17ff89daeab3159efa790165bf59ead87f2aa94 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 6 Apr 2019 21:25:16 +1100 Subject: [PATCH 0079/1235] remove python 3.5 from travis build --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 06cbd460a..0a063684f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: python python: # CPython; versions pre-2.7 and 3.0-3.4 have reached EOL - "2.7" - - "3.5" - "3.6" - "3.7-dev" # TODO: change to "3.7" once it is supported by travis-ci - "nightly" @@ -68,4 +67,4 @@ deploy: secure: oQ9XpEkcilkZgKp+rKvPb2J1GrZe2ZvtOq/IjzCpiA8NeWixl/ai3BkPrLbd8t1wNIFoGwx7IQ7zxWL79aPYeG6XrljEomv3g45NR6dkQewUH+dQFlnT75Rm96Ycxvme0w1+71vM4PqxIuzyXUrF2n7JjC0XCCxHdTuYmPGbxVO1fOsE5R5b9inAbpEUtJuWz5AIrDEZ0OgoQpLSC8fLwbymTThX3JZ5GBLpRScVvLazjIYfRkZxvCqQ4mp1UNTdoMzekxsvxOOcEW6+j3fQO+Q/8uvMksKP0RgT8HE69oeYOeVic4Q4wGqORw+ur4A56NvBqVKtizVLCzzEG9ZfoSDy7ryvGWGZykkh8HX0PFQAEykC3iYihHK8ZFz5bEqRMegTmuRYZwPsel61wVd5posxnQkGm0syIoJNKuuRc5sUK+E3GviYcT8NntdR+4WBrvpQAYa1ZHpVrfnQXyaDmGzOjwCRGPoIDJweEqGVmLycEC5aT8rX3/W9tie9iPnjmFJh4CwNMxDgVQRo80m6Gtlf/DQpA3mH39IvWGqd5fHdTPxYPs32EQSCsaYLJV5pM8xBNv6M2S/KriGnGZU0xT7MEr46da0LstKsK/U8O0yamjyugMvQoC3zQcKLrDzWFSBsT7/vG+AuV5SK8yzfEHugo7jkPQQ+NTw29xzk4dY= on: tags: true - skip_cleanup: true \ No newline at end of file + skip_cleanup: true From 6535a076e89466ca1ae8a23fc4d8dd8faed5aa75 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 7 Apr 2019 17:45:16 +0200 Subject: [PATCH 0080/1235] Fix typos & add docs Tiny change. --- can/message.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/message.py b/can/message.py index 207ee6da5..f85218fc0 100644 --- a/can/message.py +++ b/can/message.py @@ -29,8 +29,8 @@ class Message(object): :func:`~copy.copy`/:func:`~copy.deepcopy` is supported as well. - Messages do not support "dynamic" attributes, meaning any others that the - documented ones. + Messages do not support "dynamic" attributes, meaning any others than the + documented ones, since it uses :attr:`~object.__slots__`. """ __slots__ = ( @@ -303,7 +303,7 @@ def _check(self): if self.bitrate_switch: raise ValueError("bitrate switch is only allowed for CAN FD frames") if self.error_state_indicator: - raise ValueError("error stat indicator is only allowed for CAN FD frames") + raise ValueError("error state indicator is only allowed for CAN FD frames") def equals(self, other, timestamp_delta=1.0e-6): """ From 5a2f011f498b86b40614648bff1b14fde625c868 Mon Sep 17 00:00:00 2001 From: Bill Hass Date: Mon, 15 Apr 2019 09:11:21 -0400 Subject: [PATCH 0081/1235] Direct setup.py test to use pytest. --- setup.cfg | 3 +++ setup.py | 1 + 2 files changed, 4 insertions(+) diff --git a/setup.cfg b/setup.cfg index 21ffc0053..49177e68e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,9 @@ [bdist_wheel] universal = 1 +[aliases] +test=pytest + [metadata] license_file = LICENSE.txt diff --git a/setup.py b/setup.py index 7c1fe1424..f58493d9b 100644 --- a/setup.py +++ b/setup.py @@ -103,6 +103,7 @@ 'typing;python_version<"3.5"', 'windows-curses;platform_system=="Windows"', ], + setup_requires=["pytest-runner"], extras_require=extras_require, tests_require=tests_require ) From a2f07fb8c3341189d92b099cd8d336a8a4610f9a Mon Sep 17 00:00:00 2001 From: Giovanni Berlanda-Scorza Date: Tue, 16 Apr 2019 10:38:24 -0400 Subject: [PATCH 0082/1235] fixed typo --- can/io/csv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/csv.py b/can/io/csv.py index 029a2c373..37ca0e5a7 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -1,7 +1,7 @@ # coding: utf-8 """ -This module contains handling for CSV (comma seperated values) files. +This module contains handling for CSV (comma separated values) files. TODO: CAN FD messages are not yet supported. From cfb5721ee79b554a494fda16c75837fcca2a8cde Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 18 Apr 2019 03:10:23 +0200 Subject: [PATCH 0083/1235] Add guide for new io formats (#548) * better developer docs * Add section "About the IO module" * add note about newly added section * Update doc/internal-api.rst --- can/io/__init__.py | 2 +- doc/development.rst | 14 +++++++++++--- doc/internal-api.rst | 45 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/can/io/__init__.py b/can/io/__init__.py index 967b9e555..a0d89f28b 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -1,7 +1,7 @@ # coding: utf-8 """ -Read and Write CAN bus messages using a range of Readers +Read and write CAN bus messages using a range of Readers and Writers based off the file extension. """ diff --git a/doc/development.rst b/doc/development.rst index 57864753f..602e4e347 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -11,6 +11,11 @@ https://github.com/hardbyte/python-can There is also a `python-can `__ mailing list for development discussion. +Some more information about the internals of this library can be found +in the chapter :ref:`internalapi`. +There is also additional information on extending the ``can.io`` module. + + Building & Installing --------------------- @@ -35,7 +40,8 @@ These steps are a guideline on how to add a new backend to python-can. - Create a module (either a ``*.py`` or an entire subdirectory depending on the complexity) inside ``can.interfaces`` - Implement the central part of the backend: the bus class that extends - :class:`can.BusABC`. See :ref:`businternals` for more info on this one! + :class:`can.BusABC`. + See :ref:`businternals` for more info on this one! - Register your backend bus class in ``can.interface.BACKENDS`` and ``can.interfaces.VALID_INTERFACES`` in ``can.interfaces.__init__.py``. - Add docs where appropriate. At a minimum add to ``doc/interfaces.rst`` and add @@ -44,7 +50,6 @@ These steps are a guideline on how to add a new backend to python-can. - Add tests in ``test/*`` where appropriate. - Code Structure -------------- @@ -84,4 +89,7 @@ Creating a new Release - Upload with twine ``twine upload dist/python-can-X.Y.Z*``. - In a new virtual env check that the package can be installed with pip: ``pip install python-can==X.Y.Z``. - Create a new tag in the repository. -- Check the release on PyPi, Read the Docs and GitHub. +- Check the release on + `PyPi `__, + `Read the Docs `__ and + `GitHub `__. diff --git a/doc/internal-api.rst b/doc/internal-api.rst index d07c39c58..54978e0ce 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -1,3 +1,5 @@ +.. _internalapi: + Internal API ============ @@ -46,7 +48,6 @@ They **might** implement the following: and thus might not provide message filtering: - Concrete instances are usually created by :class:`can.Bus` which takes the users configuration into account. @@ -66,8 +67,43 @@ methods: +About the IO module +------------------- + +Handling of the different file formats is implemented in :mod:`can.io`. +Each file/IO type is within a separate module and ideally implements both a *Reader* and a *Writer*. +The reader usually extends :class:`can.io.generic.BaseIOHandler`, while +the writer often additionally extends :class:`can.Listener`, +to be able to be passed directly to a :class:`can.Notifier`. + + + +Adding support for new file formats +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This assumes that you want to add a new file format, called *canstore*. +Ideally add both reading and writing support for the new file format, although this is not strictly required. + +1. Create a new module: *can/io/canstore.py* + (*or* simply copy some existing one like *can/io/csv.py*) +2. Implement a reader ``CanstoreReader`` (which often extends :class:`can.io.generic.BaseIOHandler`, but does not have to). + Besides from a constructor, only ``__iter__(self)`` needs to be implemented. +3. Implement a writer ``CanstoreWriter`` (which often extends :class:`can.io.generic.BaseIOHandler` and :class:`can.Listener`, but does not have to). + Besides from a constructor, only ``on_message_received(self, msg)`` needs to be implemented. +4. Document the two new classes (and possibly additional helpers) with docstrings and comments. + Please mention features and limitations of the implementation. +5. Add a short section to the bottom of *doc/listeners.rst*. +6. Add tests where appropriate, for example by simply adding a test case called + `class TestCanstoreFileFormat(ReaderWriterTest)` to *test/logformats_test.py*. + That should already handle all of the general testing. + Just follow the way the other tests in there do it. +7. Add imports to *can/__init__py* and *can/io/__init__py* so that the + new classes can be simply imported as *from can import CanstoreReader, CanstoreWriter*. + + + IO Utilities ------------- +~~~~~~~~~~~~ .. automodule:: can.io.generic @@ -75,10 +111,9 @@ IO Utilities -Other Util ----------- +Other Utilities +--------------- .. automodule:: can.util :members: - From dcf87ce3712244bdde7cb4692901950ffcec2215 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 19 Apr 2019 10:23:07 +0200 Subject: [PATCH 0084/1235] Turn BusState into an enum (#533) --- can/bus.py | 16 +++++++++++----- can/interfaces/pcan/pcan.py | 6 +++--- can/interfaces/systec/ucanbus.py | 8 ++++---- can/logger.py | 9 ++++----- examples/receive_all.py | 3 +-- setup.py | 1 + 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/can/bus.py b/can/bus.py index a15c37985..2b36b3c57 100644 --- a/can/bus.py +++ b/can/bus.py @@ -11,12 +11,19 @@ import threading from time import time from collections import namedtuple +from aenum import Enum, auto from .broadcastmanager import ThreadBasedCyclicSendTask LOG = logging.getLogger(__name__) -BusState = namedtuple('BusState', 'ACTIVE, PASSIVE, ERROR') + +class BusState(Enum): + """The state in which a :class:`can.BusABC` can be.""" + + ACTIVE = auto() + PASSIVE = auto() + ERROR = auto() class BusABC(object): @@ -152,7 +159,7 @@ def send(self, msg, timeout=None): for transmit queue to be ready depending on driver implementation. If timeout is exceeded, an exception will be raised. Might not be supported by all interfaces. - None blocks indefinitly. + None blocks indefinitely. :raises can.CanError: if the message could not be sent @@ -369,8 +376,7 @@ def state(self): """ Return the current state of the hardware - :return: ACTIVE, PASSIVE or ERROR - :rtype: NamedTuple + :type: can.BusState """ return BusState.ACTIVE @@ -379,7 +385,7 @@ def state(self, new_state): """ Set the new state of the hardware - :param new_state: BusState.ACTIVE, BusState.PASSIVE or BusState.ERROR + :type: can.BusState """ raise NotImplementedError("Property is not implemented.") diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 6fb0e5185..e989ad6e9 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -97,7 +97,7 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 self.m_objPCANBasic = PCANBasic() self.m_PcanHandle = globals()[channel] - if state is BusState.ACTIVE or BusState.PASSIVE: + if state is BusState.ACTIVE or state is BusState.PASSIVE: self.state = state else: raise ArgumentError("BusState must be Active or Passive") @@ -280,7 +280,7 @@ def state(self, new_state): if new_state is BusState.ACTIVE: self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_OFF) - if new_state is BusState.PASSIVE: + elif new_state is BusState.PASSIVE: # When this mode is set, the CAN controller does not take part on active events (eg. transmit CAN messages) # but stays in a passive mode (CAN monitor), in which it can analyse the traffic on the CAN bus used by a # PCAN channel. See also the Philips Data Sheet "SJA1000 Stand-alone CAN controller". @@ -289,6 +289,6 @@ def state(self, new_state): class PcanError(CanError): """ - TODO: add docs + A generic error on a PCAN bus. """ pass diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index f776c93ae..969db7d53 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -104,7 +104,7 @@ def __init__(self, channel, can_filters=None, **kwargs): raise ValueError("Invalid bitrate {}".format(bitrate)) state = kwargs.get('state', BusState.ACTIVE) - if state is BusState.ACTIVE or BusState.PASSIVE: + if state is BusState.ACTIVE or state is BusState.PASSIVE: self._state = state else: raise ValueError("BusState must be Active or Passive") @@ -247,11 +247,11 @@ def state(self): @state.setter def state(self, new_state): - if self._state != BusState.ERROR and (new_state == BusState.ACTIVE or new_state == BusState.PASSIVE): - # deinitialize CAN channel + if self._state is not BusState.ERROR and (new_state is BusState.ACTIVE or new_state is BusState.PASSIVE): + # close the CAN channel self._ucan.shutdown(self.channel, False) # set mode - if new_state == BusState.ACTIVE: + if new_state is BusState.ACTIVE: self._params["mode"] &= ~Mode.MODE_LISTEN_ONLY else: self._params["mode"] |= Mode.MODE_LISTEN_ONLY diff --git a/can/logger.py b/can/logger.py index 6da89de96..204eb8dfb 100644 --- a/can/logger.py +++ b/can/logger.py @@ -57,10 +57,10 @@ def main(): parser.add_argument('-b', '--bitrate', type=int, help='''Bitrate to use for the CAN bus.''') - group = parser.add_mutually_exclusive_group(required=False) - group.add_argument('--active', help="Start the bus as active, this is applied the default.", + state_group = parser.add_mutually_exclusive_group(required=False) + state_group.add_argument('--active', help="Start the bus as active, this is applied by default.", action='store_true') - group.add_argument('--passive', help="Start the bus as passive.", + state_group.add_argument('--passive', help="Start the bus as passive.", action='store_true') # print help message when no arguments wre given @@ -98,8 +98,7 @@ def main(): if results.active: bus.state = BusState.ACTIVE - - if results.passive: + elif results.passive: bus.state = BusState.PASSIVE print('Connected to {}: {}'.format(bus.__class__.__name__, bus.channel_info)) diff --git a/examples/receive_all.py b/examples/receive_all.py index 90a4c68b6..44a495de7 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -12,8 +12,7 @@ def receive_all(): #bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) #bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) - bus.state = BusState.ACTIVE - #bus.state = BusState.PASSIVE + bus.state = BusState.ACTIVE # or BusState.PASSIVE try: while True: diff --git a/setup.py b/setup.py index f58493d9b..87c9d489c 100644 --- a/setup.py +++ b/setup.py @@ -100,6 +100,7 @@ python_requires=">=2.7", install_requires=[ 'wrapt~=1.10', + 'aenum', 'typing;python_version<"3.5"', 'windows-curses;platform_system=="Windows"', ], From 105e4f9c07d705e75031324c282d3b50b732b135 Mon Sep 17 00:00:00 2001 From: Benny Meisels Date: Thu, 21 Mar 2019 12:33:50 +0200 Subject: [PATCH 0085/1235] implemented FD for Pcan --- can/interfaces/pcan/pcan.py | 201 ++++++++++++++++++++++++++++++------ 1 file changed, 169 insertions(+), 32 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index e989ad6e9..e3d70eadf 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -13,6 +13,7 @@ import can from can import CanError, Message, BusABC from can.bus import BusState +from can.util import len2dlc, dlc2len from .basic import * boottimeEpoch = 0 @@ -65,6 +66,9 @@ 5000 : PCAN_BAUD_5K} +pcan_fd_parameter_list = ['nom_brp', 'nom_tseg1', 'nom_tseg2', 'nom_sjw', 'data_brp', 'data_tseg1', 'data_tseg2', 'data_sjw'] + + class PcanBus(BusABC): def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000, *args, **kwargs): @@ -85,9 +89,80 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 :param int bitrate: Bitrate of channel in bit/s. Default is 500 kbit/s. + Ignored if using CanFD. + + :param bool fd: + Should the Bus be initialized in CAN-FD mode. + + :param int f_clock: + Clock rate in Hz. + Any of the following: + 20000000, 24000000, 30000000, 40000000, 60000000, 80000000. + Ignored if not using CAN-FD. + Pass either f_clock or f_clock_mhz. + + :param int f_clock_mhz: + Clock rate in MHz. + Any of the following: + 20, 24, 30, 40, 60, 80. + Ignored if not using CAN-FD. + Pass either f_clock or f_clock_mhz. + + :param int nom_brp: + Clock prescaler for nominal time quantum. + In the range (1..1024) + Ignored if not using CAN-FD. + + :param int nom_tseg1: + Time segment 1 for nominal bit rate, + that is, the number of quanta from (but not including) + the Sync Segment to the sampling point. + In the range (1..256). + Ignored if not using CAN-FD. + + :param int nom_tseg2: + Time segment 2 for nominal bit rate, + that is, the number of quanta from the sampling + point to the end of the bit. + In the range (1..128). + Ignored if not using CAN-FD. + + :param int nom_sjw: + Synchronization Jump Width for nominal bit rate. + Decides the maximum number of time quanta + that the controller can resynchronize every bit. + In the range (1..128). + Ignored if not using CAN-FD. + + :param int data_brp: + Clock prescaler for fast data time quantum. + In the range (1..1024) + Ignored if not using CAN-FD. + + :param int data_tseg1: + Time segment 1 for fast data bit rate, + that is, the number of quanta from (but not including) + the Sync Segment to the sampling point. + In the range (1..32). + Ignored if not using CAN-FD. + + :param int data_tseg2: + Time segment 2 for fast data bit rate, + that is, the number of quanta from the sampling + point to the end of the bit. + In the range (1..16). + Ignored if not using CAN-FD. + + :param int data_sjw: + Synchronization Jump Width for fast data bit rate. + Decides the maximum number of time quanta + that the controller can resynchronize every bit. + In the range (1..16). + Ignored if not using CAN-FD. """ self.channel_info = channel + self.fd = kwargs.get('fd', False) pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) hwtype = PCAN_TYPE_ISA @@ -102,7 +177,22 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 else: raise ArgumentError("BusState must be Active or Passive") - result = self.m_objPCANBasic.Initialize(self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt) + + if self.fd: + f_clock_val = kwargs.get('f_clock', None) + if f_clock_val is None: + f_clock = "{}={}".format('f_clock_mhz', kwargs.get('f_clock_mhz', None)) + else: + f_clock = "{}={}".format('f_clock', kwargs.get('f_clock', None)) + + fd_parameters_values = [f_clock] + ["{}={}".format(key, kwargs.get(key, None)) for key in pcan_fd_parameter_list if kwargs.get(key, None) is not None] + + self.fd_bitrate = ' ,'.join(fd_parameters_values).encode("ascii") + + + result = self.m_objPCANBasic.InitializeFD(self.m_PcanHandle, self.fd_bitrate) + else: + result = self.m_objPCANBasic.Initialize(self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt) if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) @@ -187,7 +277,10 @@ def _recv_internal(self, timeout): result = None while result is None: - result = self.m_objPCANBasic.Read(self.m_PcanHandle) + if self.fd: + result = self.m_objPCANBasic.ReadFD(self.m_PcanHandle) + else: + result = self.m_objPCANBasic.Read(self.m_PcanHandle) if result[0] == PCAN_ERROR_QRCVEMPTY: if HAS_EVENTS: result = None @@ -210,50 +303,94 @@ def _recv_internal(self, timeout): #log.debug("Received a message") - bIsRTR = (theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value) == PCAN_MESSAGE_RTR.value - bIsExt = (theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value) == PCAN_MESSAGE_EXTENDED.value + is_extended_id = (theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value) == PCAN_MESSAGE_EXTENDED.value + is_remote_frame = (theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value) == PCAN_MESSAGE_RTR.value + is_fd = (theMsg.MSGTYPE & PCAN_MESSAGE_FD.value) == PCAN_MESSAGE_FD.value + bitrate_switch = (theMsg.MSGTYPE & PCAN_MESSAGE_BRS.value) == PCAN_MESSAGE_BRS.value + error_state_indicator = (theMsg.MSGTYPE & PCAN_MESSAGE_ESI.value) == PCAN_MESSAGE_ESI.value + is_error_frame = (theMsg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value) == PCAN_MESSAGE_ERRFRAME.value + + + if self.fd: + dlc = dlc2len(theMsg.DLC) + timestamp = boottimeEpoch + (itsTimeStamp.value / (1000.0 * 1000.0)) + else: + dlc = theMsg.LEN + timestamp = boottimeEpoch + ((itsTimeStamp.micros + 1000 * itsTimeStamp.millis + 0x100000000 * 1000 * itsTimeStamp.millis_overflow) / (1000.0 * 1000.0)) - dlc = theMsg.LEN - timestamp = boottimeEpoch + ((itsTimeStamp.micros + 1000 * itsTimeStamp.millis + 0x100000000 * 1000 * itsTimeStamp.millis_overflow) / (1000.0 * 1000.0)) rx_msg = Message(timestamp=timestamp, arbitration_id=theMsg.ID, - is_extended_id=bIsExt, - is_remote_frame=bIsRTR, + is_extended_id=is_extended_id, + is_remote_frame=is_remote_frame, + is_error_frame=is_error_frame, dlc=dlc, - data=theMsg.DATA[:dlc]) + data=theMsg.DATA[:dlc], + is_fd=is_fd, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator) return rx_msg, False def send(self, msg, timeout=None): - if msg.is_extended_id: - msgType = PCAN_MESSAGE_EXTENDED - else: - msgType = PCAN_MESSAGE_STANDARD - - # create a TPCANMsg message structure - if platform.system() == 'Darwin': - CANMsg = TPCANMsgMac() - else: - CANMsg = TPCANMsg() - - # configure the message. ID, Length of data, message type and data - CANMsg.ID = msg.arbitration_id - CANMsg.LEN = msg.dlc - CANMsg.MSGTYPE = msgType - - # if a remote frame will be sent, data bytes are not important. + msgType = PCAN_MESSAGE_EXTENDED.value if msg.is_extended_id else PCAN_MESSAGE_STANDARD.value if msg.is_remote_frame: - CANMsg.MSGTYPE = msgType.value | PCAN_MESSAGE_RTR.value - else: + msgType |= PCAN_MESSAGE_RTR.value + if msg.is_error_frame: + msgType |= PCAN_MESSAGE_ERRFRAME.value + if msg.is_fd: + msgType |= PCAN_MESSAGE_FD.value + if msg.bitrate_switch: + msgType |= PCAN_MESSAGE_BRS.value + if msg.error_state_indicator: + msgType |= PCAN_MESSAGE_ESI.value + + if self.fd: + # create a TPCANMsg message structure + if platform.system() == 'Darwin': + CANMsg = TPCANMsgFDMac() + else: + CANMsg = TPCANMsgFD() + + # configure the message. ID, Length of data, message type and data + CANMsg.ID = msg.arbitration_id + CANMsg.DLC = len2dlc(msg.dlc) + CANMsg.MSGTYPE = msgType + # copy data - for i in range(CANMsg.LEN): + for i in range(msg.dlc): CANMsg.DATA[i] = msg.data[i] - log.debug("Data: %s", msg.data) - log.debug("Type: %s", type(msg.data)) + log.debug("Data: %s", msg.data) + log.debug("Type: %s", type(msg.data)) + + result = self.m_objPCANBasic.WriteFD(self.m_PcanHandle, CANMsg) + + else: + # create a TPCANMsg message structure + if platform.system() == 'Darwin': + CANMsg = TPCANMsgMac() + else: + CANMsg = TPCANMsg() + + # configure the message. ID, Length of data, message type and data + CANMsg.ID = msg.arbitration_id + CANMsg.LEN = msg.dlc + 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: + # copy data + for i in range(CANMsg.LEN): + CANMsg.DATA[i] = msg.data[i] + + log.debug("Data: %s", msg.data) + log.debug("Type: %s", type(msg.data)) + + result = self.m_objPCANBasic.Write(self.m_PcanHandle, CANMsg) - result = self.m_objPCANBasic.Write(self.m_PcanHandle, CANMsg) if result != PCAN_ERROR_OK: raise PcanError("Failed to send: " + self._get_formatted_error(result)) From e4cf74076accff7f8c4eb3eb0566f848753840f3 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 27 Apr 2019 16:32:56 +1000 Subject: [PATCH 0086/1235] Release notes for release 3.2.0 (#556) --- CHANGELOG.txt | 18 ++++++++++++++++++ can/__init__.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8a38c9319..e4c71508d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,21 @@ +Version 3.2.0 +==== + +Support has been removed for Python 3.4 + +Major features +-------------- + +- FD support added for Pcan in PR #537 + +Other notable changes +--------------------- + +- This release should automatically be published to PyPi by travis. #535 +- BusState is now an enum. #533 +- A guide has been added for new io formats. #548 +- Finish moving from nose to pytest #550 + Version 3.1.1 ==== diff --git a/can/__init__.py b/can/__init__.py index fb48c6dd4..cb4b6d280 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.1.1" +__version__ = "3.2.0-a0" log = logging.getLogger('can') From 1a0442a82b1705ee8f0624fa112b3a3c620fd832 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 27 Apr 2019 10:02:04 +0200 Subject: [PATCH 0087/1235] This fixes the non-standard usage of __test__=False to exclude the base class from execution and uses a simple del statement instead --- test/logformats_test.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 76c9cc426..7ce3ee43e 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -43,9 +43,12 @@ class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase): """Tests a pair of writer and reader by writing all data first and then reading all data and checking if they could be reconstructed correctly. Optionally writes some comments as well. - """ - __test__ = False + .. note:: + This class is prevented from being executed as a test + case itself by a *del* statement in at the end of the file. + (Source: `*Wojciech B.* on StackOverlfow `_) + """ __metaclass__ = ABCMeta @@ -318,8 +321,6 @@ def assertIncludesComments(self, filename): class TestAscFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" - __test__ = True - def _setup_instance(self): super(TestAscFileFormat, self)._setup_instance_helper( can.ASCWriter, can.ASCReader, @@ -332,8 +333,6 @@ def _setup_instance(self): class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader""" - __test__ = True - def _setup_instance(self): super(TestBlfFileFormat, self)._setup_instance_helper( can.BLFWriter, can.BLFReader, @@ -368,8 +367,6 @@ def test_read_known_file(self): class TestCanutilsFileFormat(ReaderWriterTest): """Tests can.CanutilsLogWriter and can.CanutilsLogReader""" - __test__ = True - def _setup_instance(self): super(TestCanutilsFileFormat, self)._setup_instance_helper( can.CanutilsLogWriter, can.CanutilsLogReader, @@ -382,8 +379,6 @@ def _setup_instance(self): class TestCsvFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" - __test__ = True - def _setup_instance(self): super(TestCsvFileFormat, self)._setup_instance_helper( can.CSVWriter, can.CSVReader, @@ -396,8 +391,6 @@ def _setup_instance(self): class TestSqliteDatabaseFormat(ReaderWriterTest): """Tests can.SqliteWriter and can.SqliteReader""" - __test__ = True - def _setup_instance(self): super(TestSqliteDatabaseFormat, self)._setup_instance_helper( can.SqliteWriter, can.SqliteReader, @@ -453,5 +446,9 @@ def test_not_crashes_with_file(self): printer(message) +# this excludes the base class from being executed as a test case itself +del(ReaderWriterTest) + + if __name__ == '__main__': unittest.main() From 596817693a7ea1d595b4dcdeddeb8b855570cebb Mon Sep 17 00:00:00 2001 From: lomonosow Date: Thu, 18 Apr 2019 10:54:27 +0300 Subject: [PATCH 0088/1235] Add availability to set can custom speed in slcan backend --- can/interfaces/slcan.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 7b276a078..aa57026d1 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -50,7 +50,7 @@ class slcanBus(BusABC): LINE_TERMINATOR = b'\r' def __init__(self, channel, ttyBaudrate=115200, bitrate=None, - sleep_after_open=_SLEEP_AFTER_SERIAL_OPEN, + btr=None, sleep_after_open=_SLEEP_AFTER_SERIAL_OPEN, rtscts=False, **kwargs): """ :param str channel: @@ -60,6 +60,8 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, baudrate of underlying serial or usb device :param int bitrate: Bitrate in bit/s + :param str btr: + BTR register value to set custom can speed (overrides bitrate) :param float poll_interval: Poll interval in seconds when reading messages :param float sleep_after_open: @@ -81,6 +83,9 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, time.sleep(sleep_after_open) + if bitrate is not None and btr is not None: + raise ValueError("Bitrate and btr mutually exclusive.") + if bitrate is not None: self.close() if bitrate in self._BITRATES: @@ -88,6 +93,10 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, else: raise ValueError("Invalid bitrate, choose one of " + (', '.join(self._BITRATES)) + '.') + if btr is not None: + self.close() + self.write("s" + btr) + self.open() super(slcanBus, self).__init__(channel, ttyBaudrate=115200, From cdc5254d96072df7739263623f3e920628a7d214 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 22 Apr 2019 22:53:43 +0300 Subject: [PATCH 0089/1235] Update can/interfaces/slcan.py Co-Authored-By: Lomonosow --- can/interfaces/slcan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index aa57026d1..8793e2c22 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -53,6 +53,8 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, btr=None, sleep_after_open=_SLEEP_AFTER_SERIAL_OPEN, rtscts=False, **kwargs): """ + :raise ValueError: if both *bitrate* and *btr* are set + :param str channel: port of underlying serial or usb device (e.g. /dev/ttyUSB0, COM8, ...) Must not be empty. @@ -61,7 +63,7 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, :param int bitrate: Bitrate in bit/s :param str btr: - BTR register value to set custom can speed (overrides bitrate) + BTR register value to set custom can speed :param float poll_interval: Poll interval in seconds when reading messages :param float sleep_after_open: From 0f5a6b8599006bdbed6970357520856e045b2626 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 27 Apr 2019 23:06:43 +0200 Subject: [PATCH 0090/1235] attempt to fix test_scripts --- test/test_scripts.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/test/test_scripts.py b/test/test_scripts.py index da3ba1d6c..74ae71489 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -15,19 +15,17 @@ from .config import * + class CanScriptTest(unittest.TestCase): + __metaclass__ = ABCMeta + @classmethod def setUpClass(cls): # clean up the argument list so the call to the main() functions # in test_does_not_crash() succeeds sys.argv = sys.argv[:1] - #: this is overridden by the subclasses - __test__ = False - - __metaclass__ = ABCMeta - def test_do_commands_exist(self): """This test calls each scripts once and verifies that the help can be read without any other errors, like the script not being @@ -54,8 +52,7 @@ def test_does_not_crash(self): # test main method with self.assertRaises(SystemExit) as cm: module.main() - self.assertEqual(cm.exception.code, errno.EINVAL, - 'Calling main failed:\n{}'.format(command, e.output)) + self.assertEqual(cm.exception.code, errno.EINVAL) @abstractmethod def _commands(self): @@ -73,8 +70,6 @@ def _import(self): class TestLoggerScript(CanScriptTest): - __test__ = True - def _commands(self): commands = [ "python -m can.logger --help", @@ -91,8 +86,6 @@ def _import(self): class TestPlayerScript(CanScriptTest): - __test__ = True - def _commands(self): commands = [ "python -m can.player --help", @@ -110,5 +103,9 @@ def _import(self): # TODO add #390 +# this excludes the base class from being executed as a test case itself +del(CanScriptTest) + + if __name__ == '__main__': unittest.main() From 5550fd1dca0de8eed84b442dcdb3a7e81d3e7a13 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 27 Apr 2019 23:22:15 +0200 Subject: [PATCH 0091/1235] fix use of deprecated test method --- test/test_socketcan_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index beb2b27df..f20557392 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -36,7 +36,7 @@ def test_find_available_interfaces(self): result = list(find_available_interfaces()) self.assertGreaterEqual(len(result), 0) for entry in result: - self.assertRegexpMatches(entry, r"v?can\d+") + self.assertRegex(entry, r"v?can\d+") if IS_CI: self.assertGreaterEqual(len(result), 1) self.assertIn("vcan0", result) From f4fef935fb6dc8eced14d9e54e669c48abc83799 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 27 Apr 2019 23:27:32 +0200 Subject: [PATCH 0092/1235] revert last commit --- test/test_socketcan_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index f20557392..beb2b27df 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -36,7 +36,7 @@ def test_find_available_interfaces(self): result = list(find_available_interfaces()) self.assertGreaterEqual(len(result), 0) for entry in result: - self.assertRegex(entry, r"v?can\d+") + self.assertRegexpMatches(entry, r"v?can\d+") if IS_CI: self.assertGreaterEqual(len(result), 1) self.assertIn("vcan0", result) From 01b9188b8c76217a19323ca85352d7555139e88b Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Mon, 29 Apr 2019 21:01:09 +0200 Subject: [PATCH 0093/1235] Refactoring: Move exceptions to separate file. --- can/__init__.py | 10 ++-------- can/exceptions.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 can/exceptions.py diff --git a/can/__init__.py b/can/__init__.py index cb4b6d280..c93a1b208 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,19 +8,13 @@ import logging -__version__ = "3.2.0-a0" +__version__ = "3.2.0-exception-handling" log = logging.getLogger('can') rc = dict() - -class CanError(IOError): - """Indicates an error with the CAN network. - - """ - pass - +from .exceptions import CanError, CanInitializationError, CanOperationError from .listener import Listener, BufferedReader, RedirectReader try: diff --git a/can/exceptions.py b/can/exceptions.py new file mode 100644 index 000000000..131bd342c --- /dev/null +++ b/can/exceptions.py @@ -0,0 +1,25 @@ +""" +Exception classes. + +Copyright (c) Marcel Kanter +""" + +class CanError(Exception): + """ Base class for all can related exceptions. + """ + pass + + +class CanInitializationError(CanError): + """ Indicates an error related to the initialization. + """ + pass + + +class CanOperationError(CanError): + """ Indicates an error while operation. + For example: + ACK error (e.g. only one bus member) + Stuff, CRC error (e.g. malformed message) + """ + pass \ No newline at end of file From 8697f14b758450ec5b35ef57f300c7abd1ffd714 Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Mon, 29 Apr 2019 21:57:51 +0200 Subject: [PATCH 0094/1235] Added tests for ixxat interface. Started to use the new exception types. --- can/interfaces/ixxat/canlib.py | 1021 ++++++++++++++-------------- can/interfaces/ixxat/exceptions.py | 21 +- test/test_interface_ixxat.py | 39 ++ 3 files changed, 560 insertions(+), 521 deletions(-) create mode 100644 test/test_interface_ixxat.py diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 84c8751c1..04846562c 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -4,12 +4,13 @@ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems Copyright (C) 2016 Giuseppe Corbelli +Copyright (C) 2019 Marcel Kanter TODO: We could implement this interface such that setting other filters - could work when the initial filters were set to zero using the - software fallback. Or could the software filters even be changed - after the connection was opened? We need to document that bahaviour! - See also the NICAN interface. + could work when the initial filters were set to zero using the + software fallback. Or could the software filters even be changed + after the connection was opened? We need to document that bahaviour! + See also the NICAN interface. """ @@ -20,9 +21,9 @@ import logging import sys -from can import CanError, BusABC, Message -from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC) +from can import BusABC, Message +from can import CanError, CanInitializationError +from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT from . import constants, structures @@ -33,10 +34,10 @@ log = logging.getLogger('can.ixxat') try: - # since Python 3.3 - from time import perf_counter as _timer_function + # since Python 3.3 + from time import perf_counter as _timer_function except ImportError: - from time import clock as _timer_function + from time import clock as _timer_function # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -44,534 +45,532 @@ # main ctypes instance _canlib = None if sys.platform == "win32": - try: - _canlib = CLibrary("vcinpl") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) + try: + _canlib = CLibrary("vcinpl") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) elif sys.platform == "cygwin": - try: - _canlib = CLibrary("vcinpl.dll") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) + try: + _canlib = CLibrary("vcinpl.dll") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) else: - # Will not work on other systems, but have it importable anyway for - # tests/sphinx - log.warning("IXXAT VCI library does not work on %s platform", sys.platform) + # Will not work on other systems, but have it importable anyway for + # tests/sphinx + log.warning("IXXAT VCI library does not work on %s platform", sys.platform) def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): - """ Format a VCI error and attach failed function, decoded HRESULT and arguments - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :param arguments: - Arbitrary arguments tuple - :return: - Formatted string - """ - #TODO: make sure we don't generate another exception - return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, HRESULT), - arguments - ) + """ Format a VCI error and attach failed function, decoded HRESULT and arguments + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :param arguments: + Arbitrary arguments tuple + :return: + Formatted string + """ + #TODO: make sure we don't generate another exception + return "{} - arguments were {}".format(__vciFormatError(library_instance, function, HRESULT), arguments) def __vciFormatError(library_instance, function, HRESULT): - """ Format a VCI error and attach failed function and decoded HRESULT - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :return: - Formatted string - """ - buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) - ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) - library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) - return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) + """ Format a VCI error and attach failed function and decoded HRESULT + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :return: + Formatted string + """ + buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) + return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) def __check_status(result, function, arguments): - """ - Check the result of a vcinpl function call and raise appropriate exception - in case of an error. Used as errcheck function when mapping C functions - with ctypes. - :param result: - Function call numeric result - :param callable function: - Called function - :param arguments: - Arbitrary arguments tuple - :raise: - :class:VCITimeout - :class:VCIRxQueueEmptyError - :class:StopIteration - :class:VCIError - """ - if isinstance(result, int): - # Real return value is an unsigned long - result = ctypes.c_ulong(result).value - - if result == constants.VCI_E_TIMEOUT: - raise VCITimeout("Function {} timed out".format(function._name)) - elif result == constants.VCI_E_RXQUEUE_EMPTY: - raise VCIRxQueueEmptyError() - elif result == constants.VCI_E_NO_MORE_ITEMS: - raise StopIteration() - elif result == constants.VCI_E_ACCESSDENIED: - pass # not a real error, might happen if another program has initialized the bus - elif result != constants.VCI_OK: - raise VCIError(vciFormatError(function, result)) - - return result + """ + Check the result of a vcinpl function call and raise appropriate exception + in case of an error. Used as errcheck function when mapping C functions + with ctypes. + :param result: + Function call numeric result + :param callable function: + Called function + :param arguments: + Arbitrary arguments tuple + :raise: + :class:VCITimeout + :class:VCIRxQueueEmptyError + :class:StopIteration + :class:VCIError + """ + if isinstance(result, int): + # Real return value is an unsigned long + result = ctypes.c_ulong(result).value + + if result == constants.VCI_E_TIMEOUT: + raise VCITimeout("Function {} timed out".format(function._name)) + elif result == constants.VCI_E_RXQUEUE_EMPTY: + raise VCIRxQueueEmptyError() + elif result == constants.VCI_E_NO_MORE_ITEMS: + raise StopIteration() + elif result == constants.VCI_E_ACCESSDENIED: + pass # not a real error, might happen if another program has initialized the bus + elif result != constants.VCI_OK: + raise VCIError(vciFormatError(function, result)) + + return result try: - # Map all required symbols and initialize library --------------------------- - #HRESULT VCIAPI vciInitialize ( void ); - _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) - - #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); - _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) - # Hack to have vciFormatError as a free function - vciFormatError = functools.partial(__vciFormatError, _canlib) - - # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); - _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) - - # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); - _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) - # HRESULT vciDeviceClose( HANDLE hDevice ) - _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - - # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); - _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); - _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); - _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - # HRESULT canChannelClose( HANDLE hChannel ) - _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - - #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); - _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); - _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) - #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); - _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); - _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); - _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); - _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); - _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); - _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); - _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); - _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); - _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); - _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) - #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); - _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) - #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); - _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) - #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - _canlib.vciInitialize() + # Map all required symbols and initialize library + #HRESULT VCIAPI vciInitialize ( void ); + _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) + + #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); + _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) + # Hack to have vciFormatError as a free function + vciFormatError = functools.partial(__vciFormatError, _canlib) + + # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); + _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) + + # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); + _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) + # HRESULT vciDeviceClose( HANDLE hDevice ) + _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + + # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); + _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); + _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); + _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + # HRESULT canChannelClose( HANDLE hChannel ) + _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) + #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) + #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) + #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); + _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); + _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) + #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) + + #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); + _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) + #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); + _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) + #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) + #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) + #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); + _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); + _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) + #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); + _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) + #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); + _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); + _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); + _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); + _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) + #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); + _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) + #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); + _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) + #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); + _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) + #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); + _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) + #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); + _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) + #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + _canlib.vciInitialize() except AttributeError: - # In case _canlib == None meaning we're not on win32/no lib found - pass + # In case _canlib == None meaning we're not on win32/no lib found + pass except Exception as e: - log.warning("Could not initialize IXXAT VCI library: %s", e) + log.warning("Could not initialize IXXAT VCI library: %s", e) # --------------------------------------------------------------------------- CAN_INFO_MESSAGES = { - constants.CAN_INFO_START: "CAN started", - constants.CAN_INFO_STOP: "CAN stopped", - constants.CAN_INFO_RESET: "CAN reset", + constants.CAN_INFO_START: "CAN started", + constants.CAN_INFO_STOP: "CAN stopped", + constants.CAN_INFO_RESET: "CAN reset", } CAN_ERROR_MESSAGES = { - constants.CAN_ERROR_STUFF: "CAN bit stuff error", - constants.CAN_ERROR_FORM: "CAN form error", - constants.CAN_ERROR_ACK: "CAN acknowledgment error", - constants.CAN_ERROR_BIT: "CAN bit error", - constants.CAN_ERROR_CRC: "CAN CRC error", - constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", + constants.CAN_ERROR_STUFF: "CAN bit stuff error", + constants.CAN_ERROR_FORM: "CAN form error", + constants.CAN_ERROR_ACK: "CAN acknowledgment error", + constants.CAN_ERROR_BIT: "CAN bit error", + constants.CAN_ERROR_CRC: "CAN CRC error", + constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", } #---------------------------------------------------------------------------- class IXXATBus(BusABC): - """The CAN Bus implemented for the IXXAT interface. - - .. warning:: - - This interface does implement efficient filtering of messages, but - the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` - using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` - does not work. - - """ - - CHANNEL_BITRATES = { - 0: { - 10000: constants.CAN_BT0_10KB, - 20000: constants.CAN_BT0_20KB, - 50000: constants.CAN_BT0_50KB, - 100000: constants.CAN_BT0_100KB, - 125000: constants.CAN_BT0_125KB, - 250000: constants.CAN_BT0_250KB, - 500000: constants.CAN_BT0_500KB, - 800000: constants.CAN_BT0_800KB, - 1000000: constants.CAN_BT0_1000KB - }, - 1: { - 10000: constants.CAN_BT1_10KB, - 20000: constants.CAN_BT1_20KB, - 50000: constants.CAN_BT1_50KB, - 100000: constants.CAN_BT1_100KB, - 125000: constants.CAN_BT1_125KB, - 250000: constants.CAN_BT1_250KB, - 500000: constants.CAN_BT1_500KB, - 800000: constants.CAN_BT1_800KB, - 1000000: constants.CAN_BT1_1000KB - } - } - - def __init__(self, channel, can_filters=None, **kwargs): - """ - :param int channel: - The Channel id to create this bus with. - - :param list can_filters: - See :meth:`can.BusABC.set_filters`. - - :param bool receive_own_messages: - Enable self-reception of sent messages. - - :param int UniqueHardwareId: - UniqueHardwareId to connect (optional, will use the first found if not supplied) - - :param int bitrate: - Channel bitrate in bit/s - """ - if _canlib is None: - raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") - log.info("CAN Filters: %s", can_filters) - log.info("Got configuration of: %s", kwargs) - # Configuration options - bitrate = kwargs.get('bitrate', 500000) - UniqueHardwareId = kwargs.get('UniqueHardwareId', None) - rxFifoSize = kwargs.get('rxFifoSize', 16) - txFifoSize = kwargs.get('txFifoSize', 16) - self._receive_own_messages = kwargs.get('receive_own_messages', False) - # Usually comes as a string from the config file - channel = int(channel) - - if (bitrate not in self.CHANNEL_BITRATES[0]): - raise ValueError("Invalid bitrate {}".format(bitrate)) - - self._device_handle = HANDLE() - self._device_info = structures.VCIDEVICEINFO() - self._control_handle = HANDLE() - self._channel_handle = HANDLE() - self._channel_capabilities = structures.CANCAPABILITIES() - self._message = structures.CANMSG() - self._payload = (ctypes.c_byte * 8)() - - # Search for supplied device - if UniqueHardwareId is None: - log.info("Searching for first available device") - else: - log.info("Searching for unique HW ID %s", UniqueHardwareId) - _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) - while True: - try: - _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) - except StopIteration: - if (UniqueHardwareId is None): - raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") - else: - raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) - else: - if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): - break - else: - log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) - _canlib.vciEnumDeviceClose(self._device_handle) - _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) - log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) - - log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) - _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) - # Signal TX/RX events when at least one frame has been handled - _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) - _canlib.canChannelActivate(self._channel_handle, constants.TRUE) - - log.info("Initializing control %d bitrate %d", channel, bitrate) - _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) - _canlib.canControlInitialize( - self._control_handle, - constants.CAN_OPMODE_STANDARD|constants.CAN_OPMODE_EXTENDED|constants.CAN_OPMODE_ERRFRAME, - self.CHANNEL_BITRATES[0][bitrate], - self.CHANNEL_BITRATES[1][bitrate] - ) - _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) - - # With receive messages, this field contains the relative reception time of - # the message in ticks. The resolution of a tick can be calculated from the fields - # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: - # frequency [1/s] = dwClockFreq / dwTscDivisor - # We explicitly cast to float for Python 2.x users - self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) - - # Setup filters before starting the channel - if can_filters: - log.info("The IXXAT VCI backend is filtering messages") - # Disable every message coming in - for extended in (0, 1): - _canlib.canControlSetAccFilter(self._control_handle, - extended, - constants.CAN_ACC_CODE_NONE, - constants.CAN_ACC_MASK_NONE) - for can_filter in can_filters: - # Whitelist - code = int(can_filter['can_id']) - mask = int(can_filter['can_mask']) - extended = can_filter.get('extended', False) - _canlib.canControlAddFilterIds(self._control_handle, - 1 if extended else 0, - code << 1, - mask << 1) - log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) - - # Start the CAN controller. Messages will be forwarded to the channel - _canlib.canControlStart(self._control_handle, constants.TRUE) - - # For cyclic transmit list. Set when .send_periodic() is first called - self._scheduler = None - self._scheduler_resolution = None - self.channel = channel - - # Usually you get back 3 messages like "CAN initialized" ecc... - # Clear the FIFO by filter them out with low timeout - for i in range(rxFifoSize): - try: - _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - break - - super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) - - def _inWaiting(self): - try: - _canlib.canChannelWaitRxEvent(self._channel_handle, 0) - except VCITimeout: - return 0 - else: - return 1 - - def flush_tx_buffer(self): - """ Flushes the transmit buffer on the IXXAT """ - # TODO #64: no timeout? - _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) - - def _recv_internal(self, timeout): - """ Read a message from IXXAT device. """ - - # TODO: handling CAN error messages? - data_received = False - - if timeout == 0: - # Peek without waiting - try: - _canlib.canChannelPeekMessage(self._channel_handle, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - return None, True - else: - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True - else: - # Wait if no message available - if timeout is None or timeout < 0: - remaining_ms = constants.INFINITE - t0 = None - else: - timeout_ms = int(timeout * 1000) - remaining_ms = timeout_ms - t0 = _timer_function() - - while True: - try: - _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - # Ignore the 2 errors, the timeout is handled manually with the _timer_function() - pass - else: - # See if we got a data or info/error messages - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True - break - - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: - log.info(CAN_INFO_MESSAGES.get(self._message.abData[0], "Unknown CAN info message code {}".format(self._message.abData[0]))) - - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: - log.warning(CAN_ERROR_MESSAGES.get(self._message.abData[0], "Unknown CAN error message code {}".format(self._message.abData[0]))) - - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: - pass - else: - log.warn("Unexpected message info type") - - if t0 is not None: - remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) - if remaining_ms < 0: - break - - if not data_received: - # Timed out / can message type is not DATA - return None, True - - # The _message.dwTime is a 32bit tick value and will overrun, - # so expect to see the value restarting from 0 - rx_msg = Message( - timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s - is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, - is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, - arbitration_id=self._message.dwMsgId, - dlc=self._message.uMsgInfo.Bits.dlc, - data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], - channel=self.channel - ) - - return rx_msg, True - - def send(self, msg, timeout=None): - - # This system is not designed to be very efficient - message = structures.CANMSG() - message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 - message.dwMsgId = msg.arbitration_id - if msg.dlc: - message.uMsgInfo.Bits.dlc = msg.dlc - adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) - ctypes.memmove(message.abData, adapter, len(msg.data)) - - if timeout: - _canlib.canChannelSendMessage( - self._channel_handle, int(timeout * 1000), message) - else: - _canlib.canChannelPostMessage(self._channel_handle, message) - - def _send_periodic_internal(self, msg, period, duration=None): - """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, - self._scheduler) - caps = structures.CANCAPABILITIES() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask(self._scheduler, msg, period, duration, - self._scheduler_resolution) - - def shutdown(self): - if self._scheduler is not None: - _canlib.canSchedulerClose(self._scheduler) - _canlib.canChannelClose(self._channel_handle) - _canlib.canControlStart(self._control_handle, constants.FALSE) - _canlib.canControlClose(self._control_handle) - _canlib.vciDeviceClose(self._device_handle) - - __set_filters_has_been_called = False - def set_filters(self, can_filers=None): - """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. - """ - if self.__set_filters_has_been_called: - log.warn("using filters is not supported like this, see note on IXXATBus") - else: - # allow the constructor to call this without causing a warning - self.__set_filters_has_been_called = True + """The CAN Bus implemented for the IXXAT interface. + + .. warning:: + + This interface does implement efficient filtering of messages, but + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. + + """ + + CHANNEL_BITRATES = { + 0: { + 10000: constants.CAN_BT0_10KB, + 20000: constants.CAN_BT0_20KB, + 50000: constants.CAN_BT0_50KB, + 100000: constants.CAN_BT0_100KB, + 125000: constants.CAN_BT0_125KB, + 250000: constants.CAN_BT0_250KB, + 500000: constants.CAN_BT0_500KB, + 800000: constants.CAN_BT0_800KB, + 1000000: constants.CAN_BT0_1000KB + }, + 1: { + 10000: constants.CAN_BT1_10KB, + 20000: constants.CAN_BT1_20KB, + 50000: constants.CAN_BT1_50KB, + 100000: constants.CAN_BT1_100KB, + 125000: constants.CAN_BT1_125KB, + 250000: constants.CAN_BT1_250KB, + 500000: constants.CAN_BT1_500KB, + 800000: constants.CAN_BT1_800KB, + 1000000: constants.CAN_BT1_1000KB + } + } + + def __init__(self, channel, can_filters=None, **kwargs): + """ + :param int channel: + The Channel id to create this bus with. + + :param list can_filters: + See :meth:`can.BusABC.set_filters`. + + :param bool receive_own_messages: + Enable self-reception of sent messages. + + :param int UniqueHardwareId: + UniqueHardwareId to connect (optional, will use the first found if not supplied) + + :param int bitrate: + Channel bitrate in bit/s + """ + if _canlib is None: + raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") + log.info("CAN Filters: %s", can_filters) + log.info("Got configuration of: %s", kwargs) + # Configuration options + bitrate = kwargs.get('bitrate', 500000) + UniqueHardwareId = kwargs.get('UniqueHardwareId', None) + rxFifoSize = kwargs.get('rxFifoSize', 16) + txFifoSize = kwargs.get('txFifoSize', 16) + self._receive_own_messages = kwargs.get('receive_own_messages', False) + # Usually comes as a string from the config file + channel = int(channel) + + if (bitrate not in self.CHANNEL_BITRATES[0]): + raise ValueError("Invalid bitrate {}".format(bitrate)) + + if rxFifoSize <= 0: + raise ValueError("rxFifoSize must be > 0") + + if txFifoSize <= 0: + raise ValueError("txFifoSize must be > 0") + + if channel < 0: + raise ValueError("channel number must be >= 0") + + self._device_handle = HANDLE() + self._device_info = structures.VCIDEVICEINFO() + self._control_handle = HANDLE() + self._channel_handle = HANDLE() + self._channel_capabilities = structures.CANCAPABILITIES() + self._message = structures.CANMSG() + self._payload = (ctypes.c_byte * 8)() + + # Search for supplied device + if UniqueHardwareId is None: + log.info("Searching for first available device") + else: + log.info("Searching for unique HW ID %s", UniqueHardwareId) + _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) + except StopIteration: + if (UniqueHardwareId is None): + raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") + else: + raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) + else: + if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): + break + else: + log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) + _canlib.vciEnumDeviceClose(self._device_handle) + + try: + _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) + except: + raise(CanInitializationError("Could not open device.")) + + log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) + + log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) + + try: + _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) + # Signal TX/RX events when at least one frame has been handled + except: + raise(CanInitializationError("Could not open and initialize channel.")) + + _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) + _canlib.canChannelActivate(self._channel_handle, constants.TRUE) + + log.info("Initializing control %d bitrate %d", channel, bitrate) + _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) + _canlib.canControlInitialize(self._control_handle, constants.CAN_OPMODE_STANDARD | constants.CAN_OPMODE_EXTENDED | constants.CAN_OPMODE_ERRFRAME, self.CHANNEL_BITRATES[0][bitrate], self.CHANNEL_BITRATES[1][bitrate]) + _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) + + # With receive messages, this field contains the relative reception time of + # the message in ticks. The resolution of a tick can be calculated from the fields + # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: + # frequency [1/s] = dwClockFreq / dwTscDivisor + # We explicitly cast to float for Python 2.x users + self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) + + # Setup filters before starting the channel + if can_filters: + log.info("The IXXAT VCI backend is filtering messages") + # Disable every message coming in + for extended in (0, 1): + _canlib.canControlSetAccFilter(self._control_handle, extended, constants.CAN_ACC_CODE_NONE, constants.CAN_ACC_MASK_NONE) + for can_filter in can_filters: + # Whitelist + code = int(can_filter['can_id']) + mask = int(can_filter['can_mask']) + extended = can_filter.get('extended', False) + _canlib.canControlAddFilterIds(self._control_handle, 1 if extended else 0, code << 1, mask << 1) + log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) + + # Start the CAN controller. Messages will be forwarded to the channel + _canlib.canControlStart(self._control_handle, constants.TRUE) + + # For cyclic transmit list. Set when .send_periodic() is first called + self._scheduler = None + self._scheduler_resolution = None + self.channel = channel + + # Usually you get back 3 messages like "CAN initialized" ecc... + # Clear the FIFO by filter them out with low timeout + for i in range(rxFifoSize): + try: + _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + break + + super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) + + def _inWaiting(self): + try: + _canlib.canChannelWaitRxEvent(self._channel_handle, 0) + except VCITimeout: + return 0 + else: + return 1 + + def flush_tx_buffer(self): + """ Flushes the transmit buffer on the IXXAT """ + # TODO #64: no timeout? + _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) + + def _recv_internal(self, timeout): + """ Read a message from IXXAT device. """ + + # TODO: handling CAN error messages? + data_received = False + + if timeout == 0: + # Peek without waiting + try: + _canlib.canChannelPeekMessage(self._channel_handle, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + return None, True + else: + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + else: + # Wait if no message available + if timeout is None or timeout < 0: + remaining_ms = constants.INFINITE + t0 = None + else: + timeout_ms = int(timeout * 1000) + remaining_ms = timeout_ms + t0 = _timer_function() + + while True: + try: + _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + # Ignore the 2 errors, the timeout is handled manually with the _timer_function() + pass + else: + # See if we got a data or info/error messages + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + break + + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: + log.info(CAN_INFO_MESSAGES.get(self._message.abData[0], "Unknown CAN info message code {}".format(self._message.abData[0]))) + + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: + log.warning(CAN_ERROR_MESSAGES.get(self._message.abData[0], "Unknown CAN error message code {}".format(self._message.abData[0]))) + + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: + pass + else: + log.warn("Unexpected message info type") + + if t0 is not None: + remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) + if remaining_ms < 0: + break + + if not data_received: + # Timed out / can message type is not DATA + return None, True + + # The _message.dwTime is a 32bit tick value and will overrun, + # so expect to see the value restarting from 0 + rx_msg = Message( + timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s + is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, + is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, + arbitration_id=self._message.dwMsgId, + dlc=self._message.uMsgInfo.Bits.dlc, + data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], + channel=self.channel + ) + + return rx_msg, True + + def send(self, msg, timeout=None): + + # This system is not designed to be very efficient + message = structures.CANMSG() + message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 + message.dwMsgId = msg.arbitration_id + if msg.dlc: + message.uMsgInfo.Bits.dlc = msg.dlc + adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) + ctypes.memmove(message.abData, adapter, len(msg.data)) + + if timeout: + _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) + else: + _canlib.canChannelPostMessage(self._channel_handle, message) + + def _send_periodic_internal(self, msg, period, duration=None): + """Send a message using built-in cyclic transmit list functionality.""" + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) + caps = structures.CANCAPABILITIES() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask(self._scheduler, msg, period, duration, self._scheduler_resolution) + + def shutdown(self): + if self._scheduler is not None: + _canlib.canSchedulerClose(self._scheduler) + _canlib.canChannelClose(self._channel_handle) + _canlib.canControlStart(self._control_handle, constants.FALSE) + _canlib.canControlClose(self._control_handle) + _canlib.vciDeviceClose(self._device_handle) + + __set_filters_has_been_called = False + def set_filters(self, can_filers=None): + """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. + """ + if self.__set_filters_has_been_called: + log.warn("using filters is not supported like this, see note on IXXATBus") + else: + # allow the constructor to call this without causing a warning + self.__set_filters_has_been_called = True class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): - """A message in the cyclic transmit list.""" - - def __init__(self, scheduler, msg, period, duration, resolution): - super(CyclicSendTask, self).__init__(msg, period, duration) - self._scheduler = scheduler - self._index = None - self._count = int(duration / period) if duration else 0 - - self._msg = structures.CANCYCLICTXMSG() - self._msg.wCycleTime = int(round(period * resolution)) - self._msg.dwMsgId = msg.arbitration_id - self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - self._msg.uMsgInfo.Bits.dlc = msg.dlc - for i, b in enumerate(msg.data): - self._msg.abData[i] = b - self.start() - - def start(self): - """Start transmitting message (add to list if needed).""" - if self._index is None: - self._index = ctypes.c_uint32() - _canlib.canSchedulerAddMessage(self._scheduler, - self._msg, - self._index) - _canlib.canSchedulerStartMessage(self._scheduler, - self._index, - self._count) - - def pause(self): - """Pause transmitting message (keep it in the list).""" - _canlib.canSchedulerStopMessage(self._scheduler, self._index) - - def stop(self): - """Stop transmitting message (remove from list).""" - # Remove it completely instead of just stopping it to avoid filling up - # the list with permanently stopped messages - _canlib.canSchedulerRemMessage(self._scheduler, self._index) - self._index = None + RestartableCyclicTaskABC): + """A message in the cyclic transmit list.""" + + def __init__(self, scheduler, msg, period, duration, resolution): + super(CyclicSendTask, self).__init__(msg, period, duration) + self._scheduler = scheduler + self._index = None + self._count = int(duration / period) if duration else 0 + + self._msg = structures.CANCYCLICTXMSG() + self._msg.wCycleTime = int(round(period * resolution)) + self._msg.dwMsgId = msg.arbitration_id + self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + self._msg.uMsgInfo.Bits.dlc = msg.dlc + for i, b in enumerate(msg.data): + self._msg.abData[i] = b + self.start() + + def start(self): + """Start transmitting message (add to list if needed).""" + if self._index is None: + self._index = ctypes.c_uint32() + _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) + _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) + + def pause(self): + """Pause transmitting message (keep it in the list).""" + _canlib.canSchedulerStopMessage(self._scheduler, self._index) + + def stop(self): + """Stop transmitting message (remove from list).""" + # Remove it completely instead of just stopping it to avoid filling up + # the list with permanently stopped messages + _canlib.canSchedulerRemMessage(self._scheduler, self._index) + self._index = None diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index ac1700dca..03c3b7167 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -4,28 +4,29 @@ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems Copyright (C) 2016 Giuseppe Corbelli +Copyright (C) 2019 Marcel Kanter """ -from can import CanError +from can import CanError, CanInitializationError __all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] class VCITimeout(CanError): - """ Wraps the VCI_E_TIMEOUT error """ - pass + """ Wraps the VCI_E_TIMEOUT error """ + pass class VCIError(CanError): - """ Try to display errors that occur within the wrapped C library nicely. """ - pass + """ Try to display errors that occur within the wrapped C library nicely. """ + pass class VCIRxQueueEmptyError(VCIError): - """ Wraps the VCI_E_RXQUEUE_EMPTY error """ - def __init__(self): - super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") + """ Wraps the VCI_E_RXQUEUE_EMPTY error """ + def __init__(self): + super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") -class VCIDeviceNotFoundError(CanError): - pass +class VCIDeviceNotFoundError(CanInitializationError): + pass diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py new file mode 100644 index 000000000..76c77052d --- /dev/null +++ b/test/test_interface_ixxat.py @@ -0,0 +1,39 @@ +""" +Unittest for ixxat interface. + +Copyright (C) 2019 Marcel Kanter +""" + +import unittest + +import can + +from can import CanInitializationError + + +class InterfaceIxxatTestCase(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_bus_creation(self): + # channel must be >= 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = -1) + + # rxFifoSize must be > 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = 0, rxFifoSize = 0) + + # txFifoSize must be > 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) + + # non-existent channel -> use arbitrary high value + with self.assertRaises(CanInitializationError): + bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From efe186cf94ad25f91829fa1146a575079ced2a57 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 30 Apr 2019 08:16:30 +0200 Subject: [PATCH 0095/1235] refactor & comment player --- can/io/player.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/can/io/player.py b/can/io/player.py index 80fa585f0..229c157c3 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -8,7 +8,7 @@ from __future__ import absolute_import -import time +from time import time, sleep import logging from .generic import BaseIOHandler @@ -73,8 +73,8 @@ class MessageSync(object): def __init__(self, messages, timestamps=True, gap=0.0001, skip=60): """Creates an new **MessageSync** instance. - :param messages: An iterable of :class:`can.Message` instances. - :param bool timestamps: Use the messages' timestamps. + :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). """ @@ -84,26 +84,25 @@ def __init__(self, messages, timestamps=True, gap=0.0001, skip=60): self.skip = skip def __iter__(self): - log.debug("Iterating over messages at real speed") - - playback_start_time = time.time() + playback_start_time = time() recorded_start_time = None - for m in self.raw_messages: - if recorded_start_time is None: - recorded_start_time = m.timestamp + for message in self.raw_messages: + # Work out the correct wait time if self.timestamps: - # Work out the correct wait time - now = time.time() + if recorded_start_time is None: + recorded_start_time = message.timestamp + + now = time() current_offset = now - playback_start_time - recorded_offset_from_start = m.timestamp - recorded_start_time - remaining_gap = recorded_offset_from_start - current_offset + recorded_offset_from_start = message.timestamp - recorded_start_time + remaining_gap = max(0.0, recorded_offset_from_start - current_offset) sleep_period = max(self.gap, min(self.skip, remaining_gap)) else: sleep_period = self.gap - time.sleep(sleep_period) + sleep(sleep_period) - yield m + yield message From 4a866101936a56ac77310f47be4f59c13cfe44cf Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 30 Apr 2019 08:17:16 +0200 Subject: [PATCH 0096/1235] refactor message equality helper --- test/logformats_test.py | 9 --------- test/message_helper.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 7ce3ee43e..d9551e5d6 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -294,15 +294,6 @@ def _ensure_fsync(self, io_handler): io_handler.file.flush() os.fsync(io_handler.file.fileno()) - def assertMessagesEqual(self, messages_1, messages_2): - """ - Checks the order and content of the individual messages. - """ - self.assertEqual(len(messages_1), len(messages_2)) - - for message_1, message_2 in zip(messages_1, messages_2): - self.assertMessageEqual(message_1, message_2) - def assertIncludesComments(self, filename): """ Ensures that all comments are literally contained in the given file. diff --git a/test/message_helper.py b/test/message_helper.py index 1c139335c..9a4756207 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -47,3 +47,13 @@ def assertMessageEqual(self, message_1, message_2): print(" message 2: {!r}".format(message_2)) self.fail("messages are unequal with allowed timestamp delta {} even when ignoring channels" \ .format(self.allowed_timestamp_delta)) + + def assertMessagesEqual(self, messages_1, messages_2): + """ + Checks the order and content of the individual messages pairwise. + Raises an error if the lengths of the sequences are not equal. + """ + self.assertEqual(len(messages_1), len(messages_2), "the number of messages differs") + + for message_1, message_2 in zip(messages_1, messages_2): + self.assertMessageEqual(message_1, message_2) From 2033be642f8e0cdf894298c9d2e156b8f61c9db8 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 30 Apr 2019 08:18:10 +0200 Subject: [PATCH 0097/1235] add temporary GC disableing to test_cycle_time() --- test/simplecyclic_test.py | 57 ++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index a8896a16f..d47e879a4 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -9,6 +9,7 @@ from time import sleep import unittest +import gc import can @@ -24,34 +25,36 @@ def __init__(self, *args, **kwargs): @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_cycle_time(self): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0,1,2,3,4,5,6,7]) - bus1 = can.interface.Bus(bustype='virtual') - bus2 = can.interface.Bus(bustype='virtual') - task = bus1.send_periodic(msg, 0.01, 1) - self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) - - sleep(2) - size = bus2.queue.qsize() - # About 100 messages should have been transmitted - self.assertTrue(80 <= size <= 120, - '100 +/- 20 messages should have been transmitted. But queue contained {}'.format(size)) - last_msg = bus2.recv() - next_last_msg = bus2.recv() - # Check consecutive messages are spaced properly in time and have - # the same id/data - self.assertMessageEqual(last_msg, next_last_msg) - - - # Check the message id/data sent is the same as message received - # Set timestamp and channel to match recv'd because we don't care - # and they are not initialized by the can.Message constructor. - msg.timestamp = last_msg.timestamp - msg.channel = last_msg.channel - self.assertMessageEqual(msg, last_msg) - - bus1.shutdown() - bus2.shutdown() + msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + with can.interface.Bus(bustype='virtual') as bus1: + with can.interface.Bus(bustype='virtual') as bus2: + + gc.disable() + + task = bus1.send_periodic(msg, 0.01, 1) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + sleep(2) + size = bus2.queue.qsize() + # About 100 messages should have been transmitted + self.assertTrue(80 <= size <= 120, + '100 +/- 20 messages should have been transmitted. But queue contained {}'.format(size)) + last_msg = bus2.recv() + next_last_msg = bus2.recv() + + gc.enable() + + # Check consecutive messages are spaced properly in time and have + # the same id/data + self.assertMessageEqual(last_msg, next_last_msg) + + # Check the message id/data sent is the same as message received + # Set timestamp and channel to match recv'd because we don't care + # and they are not initialized by the can.Message constructor. + msg.timestamp = last_msg.timestamp + msg.channel = last_msg.channel + self.assertMessageEqual(msg, last_msg) def test_removing_bus_tasks(self): bus = can.interface.Bus(bustype='virtual') From cfc1b2c243c3e229dd90fa74154473878d689911 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 30 Apr 2019 08:18:32 +0200 Subject: [PATCH 0098/1235] add test from MessageSync --- test/test_message_sync.py | 106 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 test/test_message_sync.py diff --git a/test/test_message_sync.py b/test/test_message_sync.py new file mode 100644 index 000000000..b5eb66a2e --- /dev/null +++ b/test/test_message_sync.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module tests :class:`can.MessageSync`. +""" + +from __future__ import absolute_import + +from copy import copy +from time import time +import gc + +import unittest +import pytest + +from can import MessageSync, Message + +from .message_helper import ComparingMessagesTestCase +from .data.example_data import TEST_MESSAGES_BASE +TEST_FEWER_MESSAGES = TEST_MESSAGES_BASE[::2] + +class TestMessageFiltering(unittest.TestCase, ComparingMessagesTestCase): + + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + ComparingMessagesTestCase.__init__(self) + + def setup_method(self, _): + gc.disable() + + def teardown_method(self, _): + gc.enable() + + @pytest.mark.timeout(0.2) + def test_general(self): + messages = [ + Message(timestamp=50.0), + Message(timestamp=50.0), + Message(timestamp=50.0 + 0.05), + Message(timestamp=50.0 + 0.05 + 0.08), + Message(timestamp=50.0) # back in time + ] + sync = MessageSync(messages, gap=0.0) + + start = time() + collected = [] + timings = [] + for message in sync: + collected.append(message) + now = time() + timings.append(now - start) + start = now + + self.assertMessagesEqual(messages, collected) + self.assertEqual(len(timings), len(messages), "programming error in test code") + + self.assertTrue(0.0 <= timings[0] < 0.005, str(timings[0])) + self.assertTrue(0.0 <= timings[1] < 0.005, str(timings[1])) + self.assertTrue(0.045 <= timings[2] < 0.055, str(timings[2])) + self.assertTrue(0.075 <= timings[3] < 0.085, str(timings[3])) + self.assertTrue(0.0 <= timings[4] < 0.005, str(timings[4])) + + @pytest.mark.timeout(0.1 * len(TEST_FEWER_MESSAGES)) # very conservative + def test_skip(self): + messages = copy(TEST_FEWER_MESSAGES) + sync = MessageSync(messages, skip=0.005, gap=0.0) + + before = time() + collected = list(sync) + after = time() + took = after - before + + # the handling of the messages itself also take time: ~0.001 s/msg on my laptop + assert 0 < took < len(messages) * (0.005 + 0.003), "took: {}s".format(took) + + self.assertMessagesEqual(messages, collected) + + +@pytest.mark.timeout(0.3) +@pytest.mark.parametrize("timestamp_1,timestamp_2", [ + (0.0, 0.0), + (0.0, 0.01), + (0.01, 0.0), +]) +def test_gap(timestamp_1, timestamp_2): + """This method is alone so it can be parameterized.""" + messages = [ + Message(arbitration_id=0x1, timestamp=timestamp_1), + Message(arbitration_id=0x2, timestamp=timestamp_2) + ] + sync = MessageSync(messages, gap=0.1) + + gc.disable() + before = time() + collected = list(sync) + after = time() + gc.enable() + took = after - before + + assert 0.1 <= took < 0.3 + assert messages == collected + + +if __name__ == '__main__': + unittest.main() From 76a16f82a936d21b2f8b32843974370ea19ac127 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 30 Apr 2019 08:27:31 +0200 Subject: [PATCH 0099/1235] exclude AppVeyor from MessageSync tests --- test/test_message_sync.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_message_sync.py b/test/test_message_sync.py index b5eb66a2e..9bee60ba9 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -16,10 +16,14 @@ from can import MessageSync, Message +from .config import IS_APPVEYOR from .message_helper import ComparingMessagesTestCase from .data.example_data import TEST_MESSAGES_BASE + TEST_FEWER_MESSAGES = TEST_MESSAGES_BASE[::2] + +@unittest.skipIf(IS_APPVEYOR, "this environment's timings are too unpredictable") class TestMessageFiltering(unittest.TestCase, ComparingMessagesTestCase): def __init__(self, *args, **kwargs): @@ -77,6 +81,7 @@ def test_skip(self): self.assertMessagesEqual(messages, collected) +@unittest.skipIf(IS_APPVEYOR, "this environment's timings are too unpredictable") @pytest.mark.timeout(0.3) @pytest.mark.parametrize("timestamp_1,timestamp_2", [ (0.0, 0.0), From 405a89f67b86dc80105b7ad1e601c3b180172c45 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 30 Apr 2019 08:28:38 +0200 Subject: [PATCH 0100/1235] exclude AppVeyor from MessageSync tests --- test/test_message_sync.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_message_sync.py b/test/test_message_sync.py index 9bee60ba9..0a950ce83 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -56,6 +56,7 @@ def test_general(self): timings.append(now - start) start = now + self.assertMessagesEqual(messages, collected) self.assertEqual(len(timings), len(messages), "programming error in test code") From 3894d04f1ce4f422a47e6f1e1dc5eeeaf4e7b20e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 30 Apr 2019 09:05:39 +0200 Subject: [PATCH 0101/1235] make tests/timings easier for CI tests --- test/test_message_sync.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/test/test_message_sync.py b/test/test_message_sync.py index 0a950ce83..6cfbce265 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -16,15 +16,23 @@ from can import MessageSync, Message -from .config import IS_APPVEYOR +from .config import IS_APPVEYOR, IS_CI from .message_helper import ComparingMessagesTestCase from .data.example_data import TEST_MESSAGES_BASE + TEST_FEWER_MESSAGES = TEST_MESSAGES_BASE[::2] +def inc(value): + if IS_CI: + return value * 1.5 + else: + return value + + @unittest.skipIf(IS_APPVEYOR, "this environment's timings are too unpredictable") -class TestMessageFiltering(unittest.TestCase, ComparingMessagesTestCase): +class TestMessageSync(unittest.TestCase, ComparingMessagesTestCase): def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) @@ -56,15 +64,14 @@ def test_general(self): timings.append(now - start) start = now - self.assertMessagesEqual(messages, collected) self.assertEqual(len(timings), len(messages), "programming error in test code") - self.assertTrue(0.0 <= timings[0] < 0.005, str(timings[0])) - self.assertTrue(0.0 <= timings[1] < 0.005, str(timings[1])) - self.assertTrue(0.045 <= timings[2] < 0.055, str(timings[2])) - self.assertTrue(0.075 <= timings[3] < 0.085, str(timings[3])) - self.assertTrue(0.0 <= timings[4] < 0.005, str(timings[4])) + self.assertTrue(0.0 <= timings[0] < inc(0.005), str(timings[0])) + self.assertTrue(0.0 <= timings[1] < inc(0.005), str(timings[1])) + self.assertTrue(0.045 <= timings[2] < inc(0.055), str(timings[2])) + self.assertTrue(0.075 <= timings[3] < inc(0.085), str(timings[3])) + self.assertTrue(0.0 <= timings[4] < inc(0.005), str(timings[4])) @pytest.mark.timeout(0.1 * len(TEST_FEWER_MESSAGES)) # very conservative def test_skip(self): @@ -77,7 +84,7 @@ def test_skip(self): took = after - before # the handling of the messages itself also take time: ~0.001 s/msg on my laptop - assert 0 < took < len(messages) * (0.005 + 0.003), "took: {}s".format(took) + assert 0 < took < inc(len(messages) * (0.005 + 0.003)), "took: {}s".format(took) self.assertMessagesEqual(messages, collected) @@ -104,7 +111,7 @@ def test_gap(timestamp_1, timestamp_2): gc.enable() took = after - before - assert 0.1 <= took < 0.3 + assert 0.1 <= took < inc(0.3) assert messages == collected From b9bea051efd0e12c1766cbc5a6ec5287f68abf3f Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 09:57:04 +0200 Subject: [PATCH 0102/1235] Introduced CanBackEndError for exceptions related to the backend. --- can/__init__.py | 2 +- can/exceptions.py | 14 +++++++++++--- can/interfaces/ixxat/canlib.py | 7 +++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index c93a1b208..d377652d0 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -14,7 +14,7 @@ rc = dict() -from .exceptions import CanError, CanInitializationError, CanOperationError +from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError from .listener import Listener, BufferedReader, RedirectReader try: diff --git a/can/exceptions.py b/can/exceptions.py index 131bd342c..d43a27648 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -10,16 +10,24 @@ class CanError(Exception): pass +class CanBackEndError(CanError): + """ Indicates an error related to the backend (e.g. driver/OS/library) + Examples: + - A call to a library function results in an unexpected return value + """ + pass + + class CanInitializationError(CanError): """ Indicates an error related to the initialization. + Examples for situations when this exception may occur: + - Try to open a non-existent device and/or channel + - Try to use a invalid setting, which is ok by value, but not ok for the interface """ pass class CanOperationError(CanError): """ Indicates an error while operation. - For example: - ACK error (e.g. only one bus member) - Stuff, CRC error (e.g. malformed message) """ pass \ No newline at end of file diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 04846562c..7fae57f97 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -22,7 +22,7 @@ import sys from can import BusABC, Message -from can import CanError, CanInitializationError +from can import CanError, CanBackEndError, CanInitializationError, CanOperationError from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT @@ -452,17 +452,16 @@ def _recv_internal(self, timeout): if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: data_received = True break - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: log.info(CAN_INFO_MESSAGES.get(self._message.abData[0], "Unknown CAN info message code {}".format(self._message.abData[0]))) - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: log.warning(CAN_ERROR_MESSAGES.get(self._message.abData[0], "Unknown CAN error message code {}".format(self._message.abData[0]))) - + # TODO report error with is_error_frame is set to true elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: pass else: log.warn("Unexpected message info type") + raise(CanBackEndError()) if t0 is not None: remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) From 938e440c2c63dfad7f71bc255d14087c0767ab85 Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 11:35:49 +0200 Subject: [PATCH 0103/1235] Raise an CanOperationError when an exception occurs in the send method. The method returns True on success and False on timeout, if the user wanted an timeout. --- can/interfaces/ixxat/canlib.py | 30 +++++++++++++++++++++++------- test/test_interface_ixxat.py | 34 +++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 7fae57f97..52637b12d 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -115,6 +115,8 @@ def __check_status(result, function, arguments): # Real return value is an unsigned long result = ctypes.c_ulong(result).value + #print(hex(result), function) + if result == constants.VCI_E_TIMEOUT: raise VCITimeout("Function {} timed out".format(function._name)) elif result == constants.VCI_E_RXQUEUE_EMPTY: @@ -318,7 +320,6 @@ def __init__(self, channel, can_filters=None, **kwargs): self._channel_handle = HANDLE() self._channel_capabilities = structures.CANCAPABILITIES() self._message = structures.CANMSG() - self._payload = (ctypes.c_byte * 8)() # Search for supplied device if UniqueHardwareId is None: @@ -456,7 +457,6 @@ def _recv_internal(self, timeout): log.info(CAN_INFO_MESSAGES.get(self._message.abData[0], "Unknown CAN info message code {}".format(self._message.abData[0]))) elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: log.warning(CAN_ERROR_MESSAGES.get(self._message.abData[0], "Unknown CAN error message code {}".format(self._message.abData[0]))) - # TODO report error with is_error_frame is set to true elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: pass else: @@ -487,7 +487,12 @@ def _recv_internal(self, timeout): return rx_msg, True def send(self, msg, timeout=None): - + """ + Sends a message on the bus. The interface may buffer the message. + returns True on success or when timeout is None + returns False on timeout (when timeout is not None) + raises CanOperationError + """ # This system is not designed to be very efficient message = structures.CANMSG() message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA @@ -500,10 +505,21 @@ def send(self, msg, timeout=None): adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) ctypes.memmove(message.abData, adapter, len(msg.data)) - if timeout: - _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) - else: - _canlib.canChannelPostMessage(self._channel_handle, message) + try: + if timeout: + _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) + else: + _canlib.canChannelPostMessage(self._channel_handle, message) + except VCITimeout: + # if the user wanted an timeout, the timeout in the library is probably no error + if timeout: + return False + else: + raise(CanOperationError()) + except: + raise(CanOperationError()) + + return True def _send_periodic_internal(self, msg, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 76c77052d..fcaf58536 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -5,13 +5,14 @@ """ import unittest - import can -from can import CanInitializationError - +from can import CanError, CanBackEndError, CanInitializationError, CanOperationError -class InterfaceIxxatTestCase(unittest.TestCase): +class SoftwareTestCase(unittest.TestCase): + """ + Test cases that test the software only and do not rely on an existing/connected hardware. + """ def setUp(self): pass @@ -30,10 +31,33 @@ def test_bus_creation(self): # txFifoSize must be > 0 with self.assertRaises(ValueError): bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) - + + +class HardwareTestCase(unittest.TestCase): + """ + Test cases that rely on an existing/connected hardware. + """ + def setUp(self): + try: + bus = can.Bus(interface = 'ixxat', channel = 0) + except: + raise(unittest.SkipTest()) + + def tearDown(self): + pass + + def test_bus_creation(self): # non-existent channel -> use arbitrary high value with self.assertRaises(CanInitializationError): bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) + + def test_send_after_shutdown(self): + bus = can.Bus(interface = 'ixxat', channel = 0) + msg = can.Message(arbitration_id = 0x3FF, dlc = 0) + bus.shutdown() + with self.assertRaises(CanOperationError): + bus.send(msg) + if __name__ == '__main__': unittest.main() \ No newline at end of file From 07326ef63f49e5b7b7a1b6f59ebed3d502e34930 Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 11:48:19 +0200 Subject: [PATCH 0104/1235] Change intentions to 4 spaces to match remote. --- can/interfaces/ixxat/canlib.py | 1042 ++++++++++++++-------------- can/interfaces/ixxat/exceptions.py | 16 +- test/test_interface_ixxat.py | 90 +-- 3 files changed, 574 insertions(+), 574 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 52637b12d..bfa518830 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -7,10 +7,10 @@ Copyright (C) 2019 Marcel Kanter TODO: We could implement this interface such that setting other filters - could work when the initial filters were set to zero using the - software fallback. Or could the software filters even be changed - after the connection was opened? We need to document that bahaviour! - See also the NICAN interface. + could work when the initial filters were set to zero using the + software fallback. Or could the software filters even be changed + after the connection was opened? We need to document that bahaviour! + See also the NICAN interface. """ @@ -34,10 +34,10 @@ log = logging.getLogger('can.ixxat') try: - # since Python 3.3 - from time import perf_counter as _timer_function + # since Python 3.3 + from time import perf_counter as _timer_function except ImportError: - from time import clock as _timer_function + from time import clock as _timer_function # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -45,547 +45,547 @@ # main ctypes instance _canlib = None if sys.platform == "win32": - try: - _canlib = CLibrary("vcinpl") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) + try: + _canlib = CLibrary("vcinpl") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) elif sys.platform == "cygwin": - try: - _canlib = CLibrary("vcinpl.dll") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) + try: + _canlib = CLibrary("vcinpl.dll") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) else: - # Will not work on other systems, but have it importable anyway for - # tests/sphinx - log.warning("IXXAT VCI library does not work on %s platform", sys.platform) + # Will not work on other systems, but have it importable anyway for + # tests/sphinx + log.warning("IXXAT VCI library does not work on %s platform", sys.platform) def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): - """ Format a VCI error and attach failed function, decoded HRESULT and arguments - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :param arguments: - Arbitrary arguments tuple - :return: - Formatted string - """ - #TODO: make sure we don't generate another exception - return "{} - arguments were {}".format(__vciFormatError(library_instance, function, HRESULT), arguments) + """ Format a VCI error and attach failed function, decoded HRESULT and arguments + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :param arguments: + Arbitrary arguments tuple + :return: + Formatted string + """ + #TODO: make sure we don't generate another exception + return "{} - arguments were {}".format(__vciFormatError(library_instance, function, HRESULT), arguments) def __vciFormatError(library_instance, function, HRESULT): - """ Format a VCI error and attach failed function and decoded HRESULT - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :return: - Formatted string - """ - buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) - ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) - library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) - return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) + """ Format a VCI error and attach failed function and decoded HRESULT + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :return: + Formatted string + """ + buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) + return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) def __check_status(result, function, arguments): - """ - Check the result of a vcinpl function call and raise appropriate exception - in case of an error. Used as errcheck function when mapping C functions - with ctypes. - :param result: - Function call numeric result - :param callable function: - Called function - :param arguments: - Arbitrary arguments tuple - :raise: - :class:VCITimeout - :class:VCIRxQueueEmptyError - :class:StopIteration - :class:VCIError - """ - if isinstance(result, int): - # Real return value is an unsigned long - result = ctypes.c_ulong(result).value - - #print(hex(result), function) - - if result == constants.VCI_E_TIMEOUT: - raise VCITimeout("Function {} timed out".format(function._name)) - elif result == constants.VCI_E_RXQUEUE_EMPTY: - raise VCIRxQueueEmptyError() - elif result == constants.VCI_E_NO_MORE_ITEMS: - raise StopIteration() - elif result == constants.VCI_E_ACCESSDENIED: - pass # not a real error, might happen if another program has initialized the bus - elif result != constants.VCI_OK: - raise VCIError(vciFormatError(function, result)) - - return result + """ + Check the result of a vcinpl function call and raise appropriate exception + in case of an error. Used as errcheck function when mapping C functions + with ctypes. + :param result: + Function call numeric result + :param callable function: + Called function + :param arguments: + Arbitrary arguments tuple + :raise: + :class:VCITimeout + :class:VCIRxQueueEmptyError + :class:StopIteration + :class:VCIError + """ + if isinstance(result, int): + # Real return value is an unsigned long + result = ctypes.c_ulong(result).value + + #print(hex(result), function) + + if result == constants.VCI_E_TIMEOUT: + raise VCITimeout("Function {} timed out".format(function._name)) + elif result == constants.VCI_E_RXQUEUE_EMPTY: + raise VCIRxQueueEmptyError() + elif result == constants.VCI_E_NO_MORE_ITEMS: + raise StopIteration() + elif result == constants.VCI_E_ACCESSDENIED: + pass # not a real error, might happen if another program has initialized the bus + elif result != constants.VCI_OK: + raise VCIError(vciFormatError(function, result)) + + return result try: - # Map all required symbols and initialize library - #HRESULT VCIAPI vciInitialize ( void ); - _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) - - #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); - _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) - # Hack to have vciFormatError as a free function - vciFormatError = functools.partial(__vciFormatError, _canlib) - - # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); - _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) - - # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); - _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) - # HRESULT vciDeviceClose( HANDLE hDevice ) - _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - - # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); - _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); - _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); - _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - # HRESULT canChannelClose( HANDLE hChannel ) - _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - - #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); - _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); - _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) - #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); - _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); - _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); - _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); - _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); - _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); - _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); - _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); - _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); - _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); - _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) - #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); - _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) - #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); - _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) - #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - _canlib.vciInitialize() + # Map all required symbols and initialize library + #HRESULT VCIAPI vciInitialize ( void ); + _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) + + #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); + _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) + # Hack to have vciFormatError as a free function + vciFormatError = functools.partial(__vciFormatError, _canlib) + + # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); + _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) + + # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); + _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) + # HRESULT vciDeviceClose( HANDLE hDevice ) + _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + + # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); + _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); + _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); + _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + # HRESULT canChannelClose( HANDLE hChannel ) + _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) + #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) + #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) + #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); + _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); + _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) + #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); + _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) + + #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); + _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) + #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); + _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) + #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) + #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) + #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); + _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); + _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) + #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); + _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) + #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); + _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); + _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); + _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); + _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) + #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); + _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) + #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); + _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) + #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); + _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) + #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); + _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) + #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); + _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) + #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + _canlib.vciInitialize() except AttributeError: - # In case _canlib == None meaning we're not on win32/no lib found - pass + # In case _canlib == None meaning we're not on win32/no lib found + pass except Exception as e: - log.warning("Could not initialize IXXAT VCI library: %s", e) + log.warning("Could not initialize IXXAT VCI library: %s", e) # --------------------------------------------------------------------------- CAN_INFO_MESSAGES = { - constants.CAN_INFO_START: "CAN started", - constants.CAN_INFO_STOP: "CAN stopped", - constants.CAN_INFO_RESET: "CAN reset", + constants.CAN_INFO_START: "CAN started", + constants.CAN_INFO_STOP: "CAN stopped", + constants.CAN_INFO_RESET: "CAN reset", } CAN_ERROR_MESSAGES = { - constants.CAN_ERROR_STUFF: "CAN bit stuff error", - constants.CAN_ERROR_FORM: "CAN form error", - constants.CAN_ERROR_ACK: "CAN acknowledgment error", - constants.CAN_ERROR_BIT: "CAN bit error", - constants.CAN_ERROR_CRC: "CAN CRC error", - constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", + constants.CAN_ERROR_STUFF: "CAN bit stuff error", + constants.CAN_ERROR_FORM: "CAN form error", + constants.CAN_ERROR_ACK: "CAN acknowledgment error", + constants.CAN_ERROR_BIT: "CAN bit error", + constants.CAN_ERROR_CRC: "CAN CRC error", + constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", } #---------------------------------------------------------------------------- class IXXATBus(BusABC): - """The CAN Bus implemented for the IXXAT interface. - - .. warning:: - - This interface does implement efficient filtering of messages, but - the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` - using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` - does not work. - - """ - - CHANNEL_BITRATES = { - 0: { - 10000: constants.CAN_BT0_10KB, - 20000: constants.CAN_BT0_20KB, - 50000: constants.CAN_BT0_50KB, - 100000: constants.CAN_BT0_100KB, - 125000: constants.CAN_BT0_125KB, - 250000: constants.CAN_BT0_250KB, - 500000: constants.CAN_BT0_500KB, - 800000: constants.CAN_BT0_800KB, - 1000000: constants.CAN_BT0_1000KB - }, - 1: { - 10000: constants.CAN_BT1_10KB, - 20000: constants.CAN_BT1_20KB, - 50000: constants.CAN_BT1_50KB, - 100000: constants.CAN_BT1_100KB, - 125000: constants.CAN_BT1_125KB, - 250000: constants.CAN_BT1_250KB, - 500000: constants.CAN_BT1_500KB, - 800000: constants.CAN_BT1_800KB, - 1000000: constants.CAN_BT1_1000KB - } - } - - def __init__(self, channel, can_filters=None, **kwargs): - """ - :param int channel: - The Channel id to create this bus with. - - :param list can_filters: - See :meth:`can.BusABC.set_filters`. - - :param bool receive_own_messages: - Enable self-reception of sent messages. - - :param int UniqueHardwareId: - UniqueHardwareId to connect (optional, will use the first found if not supplied) - - :param int bitrate: - Channel bitrate in bit/s - """ - if _canlib is None: - raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") - log.info("CAN Filters: %s", can_filters) - log.info("Got configuration of: %s", kwargs) - # Configuration options - bitrate = kwargs.get('bitrate', 500000) - UniqueHardwareId = kwargs.get('UniqueHardwareId', None) - rxFifoSize = kwargs.get('rxFifoSize', 16) - txFifoSize = kwargs.get('txFifoSize', 16) - self._receive_own_messages = kwargs.get('receive_own_messages', False) - # Usually comes as a string from the config file - channel = int(channel) - - if (bitrate not in self.CHANNEL_BITRATES[0]): - raise ValueError("Invalid bitrate {}".format(bitrate)) - - if rxFifoSize <= 0: - raise ValueError("rxFifoSize must be > 0") - - if txFifoSize <= 0: - raise ValueError("txFifoSize must be > 0") - - if channel < 0: - raise ValueError("channel number must be >= 0") - - self._device_handle = HANDLE() - self._device_info = structures.VCIDEVICEINFO() - self._control_handle = HANDLE() - self._channel_handle = HANDLE() - self._channel_capabilities = structures.CANCAPABILITIES() - self._message = structures.CANMSG() - - # Search for supplied device - if UniqueHardwareId is None: - log.info("Searching for first available device") - else: - log.info("Searching for unique HW ID %s", UniqueHardwareId) - _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) - while True: - try: - _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) - except StopIteration: - if (UniqueHardwareId is None): - raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") - else: - raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) - else: - if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): - break - else: - log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) - _canlib.vciEnumDeviceClose(self._device_handle) - - try: - _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) - except: - raise(CanInitializationError("Could not open device.")) - - log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) - - log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) - - try: - _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) - # Signal TX/RX events when at least one frame has been handled - except: - raise(CanInitializationError("Could not open and initialize channel.")) - - _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) - _canlib.canChannelActivate(self._channel_handle, constants.TRUE) - - log.info("Initializing control %d bitrate %d", channel, bitrate) - _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) - _canlib.canControlInitialize(self._control_handle, constants.CAN_OPMODE_STANDARD | constants.CAN_OPMODE_EXTENDED | constants.CAN_OPMODE_ERRFRAME, self.CHANNEL_BITRATES[0][bitrate], self.CHANNEL_BITRATES[1][bitrate]) - _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) - - # With receive messages, this field contains the relative reception time of - # the message in ticks. The resolution of a tick can be calculated from the fields - # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: - # frequency [1/s] = dwClockFreq / dwTscDivisor - # We explicitly cast to float for Python 2.x users - self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) - - # Setup filters before starting the channel - if can_filters: - log.info("The IXXAT VCI backend is filtering messages") - # Disable every message coming in - for extended in (0, 1): - _canlib.canControlSetAccFilter(self._control_handle, extended, constants.CAN_ACC_CODE_NONE, constants.CAN_ACC_MASK_NONE) - for can_filter in can_filters: - # Whitelist - code = int(can_filter['can_id']) - mask = int(can_filter['can_mask']) - extended = can_filter.get('extended', False) - _canlib.canControlAddFilterIds(self._control_handle, 1 if extended else 0, code << 1, mask << 1) - log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) - - # Start the CAN controller. Messages will be forwarded to the channel - _canlib.canControlStart(self._control_handle, constants.TRUE) - - # For cyclic transmit list. Set when .send_periodic() is first called - self._scheduler = None - self._scheduler_resolution = None - self.channel = channel - - # Usually you get back 3 messages like "CAN initialized" ecc... - # Clear the FIFO by filter them out with low timeout - for i in range(rxFifoSize): - try: - _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - break - - super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) - - def _inWaiting(self): - try: - _canlib.canChannelWaitRxEvent(self._channel_handle, 0) - except VCITimeout: - return 0 - else: - return 1 - - def flush_tx_buffer(self): - """ Flushes the transmit buffer on the IXXAT """ - # TODO #64: no timeout? - _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) - - def _recv_internal(self, timeout): - """ Read a message from IXXAT device. """ - - # TODO: handling CAN error messages? - data_received = False - - if timeout == 0: - # Peek without waiting - try: - _canlib.canChannelPeekMessage(self._channel_handle, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - return None, True - else: - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True - else: - # Wait if no message available - if timeout is None or timeout < 0: - remaining_ms = constants.INFINITE - t0 = None - else: - timeout_ms = int(timeout * 1000) - remaining_ms = timeout_ms - t0 = _timer_function() - - while True: - try: - _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) - except (VCITimeout, VCIRxQueueEmptyError): - # Ignore the 2 errors, the timeout is handled manually with the _timer_function() - pass - else: - # See if we got a data or info/error messages - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True - break - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: - log.info(CAN_INFO_MESSAGES.get(self._message.abData[0], "Unknown CAN info message code {}".format(self._message.abData[0]))) - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: - log.warning(CAN_ERROR_MESSAGES.get(self._message.abData[0], "Unknown CAN error message code {}".format(self._message.abData[0]))) - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: - pass - else: - log.warn("Unexpected message info type") - raise(CanBackEndError()) - - if t0 is not None: - remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) - if remaining_ms < 0: - break - - if not data_received: - # Timed out / can message type is not DATA - return None, True - - # The _message.dwTime is a 32bit tick value and will overrun, - # so expect to see the value restarting from 0 - rx_msg = Message( - timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s - is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, - is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, - arbitration_id=self._message.dwMsgId, - dlc=self._message.uMsgInfo.Bits.dlc, - data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], - channel=self.channel - ) - - return rx_msg, True - - def send(self, msg, timeout=None): - """ - Sends a message on the bus. The interface may buffer the message. - returns True on success or when timeout is None - returns False on timeout (when timeout is not None) - raises CanOperationError - """ - # This system is not designed to be very efficient - message = structures.CANMSG() - message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 - message.dwMsgId = msg.arbitration_id - if msg.dlc: - message.uMsgInfo.Bits.dlc = msg.dlc - adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) - ctypes.memmove(message.abData, adapter, len(msg.data)) - - try: - if timeout: - _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) - else: - _canlib.canChannelPostMessage(self._channel_handle, message) - except VCITimeout: - # if the user wanted an timeout, the timeout in the library is probably no error - if timeout: - return False - else: - raise(CanOperationError()) - except: - raise(CanOperationError()) - - return True - - def _send_periodic_internal(self, msg, period, duration=None): - """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) - caps = structures.CANCAPABILITIES() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask(self._scheduler, msg, period, duration, self._scheduler_resolution) - - def shutdown(self): - if self._scheduler is not None: - _canlib.canSchedulerClose(self._scheduler) - _canlib.canChannelClose(self._channel_handle) - _canlib.canControlStart(self._control_handle, constants.FALSE) - _canlib.canControlClose(self._control_handle) - _canlib.vciDeviceClose(self._device_handle) - - __set_filters_has_been_called = False - def set_filters(self, can_filers=None): - """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. - """ - if self.__set_filters_has_been_called: - log.warn("using filters is not supported like this, see note on IXXATBus") - else: - # allow the constructor to call this without causing a warning - self.__set_filters_has_been_called = True + """The CAN Bus implemented for the IXXAT interface. + + .. warning:: + + This interface does implement efficient filtering of messages, but + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. + + """ + + CHANNEL_BITRATES = { + 0: { + 10000: constants.CAN_BT0_10KB, + 20000: constants.CAN_BT0_20KB, + 50000: constants.CAN_BT0_50KB, + 100000: constants.CAN_BT0_100KB, + 125000: constants.CAN_BT0_125KB, + 250000: constants.CAN_BT0_250KB, + 500000: constants.CAN_BT0_500KB, + 800000: constants.CAN_BT0_800KB, + 1000000: constants.CAN_BT0_1000KB + }, + 1: { + 10000: constants.CAN_BT1_10KB, + 20000: constants.CAN_BT1_20KB, + 50000: constants.CAN_BT1_50KB, + 100000: constants.CAN_BT1_100KB, + 125000: constants.CAN_BT1_125KB, + 250000: constants.CAN_BT1_250KB, + 500000: constants.CAN_BT1_500KB, + 800000: constants.CAN_BT1_800KB, + 1000000: constants.CAN_BT1_1000KB + } + } + + def __init__(self, channel, can_filters=None, **kwargs): + """ + :param int channel: + The Channel id to create this bus with. + + :param list can_filters: + See :meth:`can.BusABC.set_filters`. + + :param bool receive_own_messages: + Enable self-reception of sent messages. + + :param int UniqueHardwareId: + UniqueHardwareId to connect (optional, will use the first found if not supplied) + + :param int bitrate: + Channel bitrate in bit/s + """ + if _canlib is None: + raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") + log.info("CAN Filters: %s", can_filters) + log.info("Got configuration of: %s", kwargs) + # Configuration options + bitrate = kwargs.get('bitrate', 500000) + UniqueHardwareId = kwargs.get('UniqueHardwareId', None) + rxFifoSize = kwargs.get('rxFifoSize', 16) + txFifoSize = kwargs.get('txFifoSize', 16) + self._receive_own_messages = kwargs.get('receive_own_messages', False) + # Usually comes as a string from the config file + channel = int(channel) + + if (bitrate not in self.CHANNEL_BITRATES[0]): + raise ValueError("Invalid bitrate {}".format(bitrate)) + + if rxFifoSize <= 0: + raise ValueError("rxFifoSize must be > 0") + + if txFifoSize <= 0: + raise ValueError("txFifoSize must be > 0") + + if channel < 0: + raise ValueError("channel number must be >= 0") + + self._device_handle = HANDLE() + self._device_info = structures.VCIDEVICEINFO() + self._control_handle = HANDLE() + self._channel_handle = HANDLE() + self._channel_capabilities = structures.CANCAPABILITIES() + self._message = structures.CANMSG() + + # Search for supplied device + if UniqueHardwareId is None: + log.info("Searching for first available device") + else: + log.info("Searching for unique HW ID %s", UniqueHardwareId) + _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) + except StopIteration: + if (UniqueHardwareId is None): + raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") + else: + raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) + else: + if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): + break + else: + log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) + _canlib.vciEnumDeviceClose(self._device_handle) + + try: + _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) + except: + raise(CanInitializationError("Could not open device.")) + + log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) + + log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) + + try: + _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) + # Signal TX/RX events when at least one frame has been handled + except: + raise(CanInitializationError("Could not open and initialize channel.")) + + _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) + _canlib.canChannelActivate(self._channel_handle, constants.TRUE) + + log.info("Initializing control %d bitrate %d", channel, bitrate) + _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) + _canlib.canControlInitialize(self._control_handle, constants.CAN_OPMODE_STANDARD | constants.CAN_OPMODE_EXTENDED | constants.CAN_OPMODE_ERRFRAME, self.CHANNEL_BITRATES[0][bitrate], self.CHANNEL_BITRATES[1][bitrate]) + _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) + + # With receive messages, this field contains the relative reception time of + # the message in ticks. The resolution of a tick can be calculated from the fields + # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: + # frequency [1/s] = dwClockFreq / dwTscDivisor + # We explicitly cast to float for Python 2.x users + self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) + + # Setup filters before starting the channel + if can_filters: + log.info("The IXXAT VCI backend is filtering messages") + # Disable every message coming in + for extended in (0, 1): + _canlib.canControlSetAccFilter(self._control_handle, extended, constants.CAN_ACC_CODE_NONE, constants.CAN_ACC_MASK_NONE) + for can_filter in can_filters: + # Whitelist + code = int(can_filter['can_id']) + mask = int(can_filter['can_mask']) + extended = can_filter.get('extended', False) + _canlib.canControlAddFilterIds(self._control_handle, 1 if extended else 0, code << 1, mask << 1) + log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) + + # Start the CAN controller. Messages will be forwarded to the channel + _canlib.canControlStart(self._control_handle, constants.TRUE) + + # For cyclic transmit list. Set when .send_periodic() is first called + self._scheduler = None + self._scheduler_resolution = None + self.channel = channel + + # Usually you get back 3 messages like "CAN initialized" ecc... + # Clear the FIFO by filter them out with low timeout + for i in range(rxFifoSize): + try: + _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + break + + super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) + + def _inWaiting(self): + try: + _canlib.canChannelWaitRxEvent(self._channel_handle, 0) + except VCITimeout: + return 0 + else: + return 1 + + def flush_tx_buffer(self): + """ Flushes the transmit buffer on the IXXAT """ + # TODO #64: no timeout? + _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) + + def _recv_internal(self, timeout): + """ Read a message from IXXAT device. """ + + # TODO: handling CAN error messages? + data_received = False + + if timeout == 0: + # Peek without waiting + try: + _canlib.canChannelPeekMessage(self._channel_handle, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + return None, True + else: + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + else: + # Wait if no message available + if timeout is None or timeout < 0: + remaining_ms = constants.INFINITE + t0 = None + else: + timeout_ms = int(timeout * 1000) + remaining_ms = timeout_ms + t0 = _timer_function() + + while True: + try: + _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) + except (VCITimeout, VCIRxQueueEmptyError): + # Ignore the 2 errors, the timeout is handled manually with the _timer_function() + pass + else: + # See if we got a data or info/error messages + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + break + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: + log.info(CAN_INFO_MESSAGES.get(self._message.abData[0], "Unknown CAN info message code {}".format(self._message.abData[0]))) + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: + log.warning(CAN_ERROR_MESSAGES.get(self._message.abData[0], "Unknown CAN error message code {}".format(self._message.abData[0]))) + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: + pass + else: + log.warn("Unexpected message info type") + raise(CanBackEndError()) + + if t0 is not None: + remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) + if remaining_ms < 0: + break + + if not data_received: + # Timed out / can message type is not DATA + return None, True + + # The _message.dwTime is a 32bit tick value and will overrun, + # so expect to see the value restarting from 0 + rx_msg = Message( + timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s + is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, + is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, + arbitration_id=self._message.dwMsgId, + dlc=self._message.uMsgInfo.Bits.dlc, + data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], + channel=self.channel + ) + + return rx_msg, True + + def send(self, msg, timeout=None): + """ + Sends a message on the bus. The interface may buffer the message. + returns True on success or when timeout is None + returns False on timeout (when timeout is not None) + raises CanOperationError + """ + # This system is not designed to be very efficient + message = structures.CANMSG() + message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 + message.dwMsgId = msg.arbitration_id + if msg.dlc: + message.uMsgInfo.Bits.dlc = msg.dlc + adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) + ctypes.memmove(message.abData, adapter, len(msg.data)) + + try: + if timeout: + _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) + else: + _canlib.canChannelPostMessage(self._channel_handle, message) + except VCITimeout: + # if the user wanted an timeout, the timeout in the library is probably no error + if timeout: + return False + else: + raise(CanOperationError()) + except: + raise(CanOperationError()) + + return True + + def _send_periodic_internal(self, msg, period, duration=None): + """Send a message using built-in cyclic transmit list functionality.""" + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) + caps = structures.CANCAPABILITIES() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask(self._scheduler, msg, period, duration, self._scheduler_resolution) + + def shutdown(self): + if self._scheduler is not None: + _canlib.canSchedulerClose(self._scheduler) + _canlib.canChannelClose(self._channel_handle) + _canlib.canControlStart(self._control_handle, constants.FALSE) + _canlib.canControlClose(self._control_handle) + _canlib.vciDeviceClose(self._device_handle) + + __set_filters_has_been_called = False + def set_filters(self, can_filers=None): + """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. + """ + if self.__set_filters_has_been_called: + log.warn("using filters is not supported like this, see note on IXXATBus") + else: + # allow the constructor to call this without causing a warning + self.__set_filters_has_been_called = True class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): - """A message in the cyclic transmit list.""" - - def __init__(self, scheduler, msg, period, duration, resolution): - super(CyclicSendTask, self).__init__(msg, period, duration) - self._scheduler = scheduler - self._index = None - self._count = int(duration / period) if duration else 0 - - self._msg = structures.CANCYCLICTXMSG() - self._msg.wCycleTime = int(round(period * resolution)) - self._msg.dwMsgId = msg.arbitration_id - self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - self._msg.uMsgInfo.Bits.dlc = msg.dlc - for i, b in enumerate(msg.data): - self._msg.abData[i] = b - self.start() - - def start(self): - """Start transmitting message (add to list if needed).""" - if self._index is None: - self._index = ctypes.c_uint32() - _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) - _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) - - def pause(self): - """Pause transmitting message (keep it in the list).""" - _canlib.canSchedulerStopMessage(self._scheduler, self._index) - - def stop(self): - """Stop transmitting message (remove from list).""" - # Remove it completely instead of just stopping it to avoid filling up - # the list with permanently stopped messages - _canlib.canSchedulerRemMessage(self._scheduler, self._index) - self._index = None + RestartableCyclicTaskABC): + """A message in the cyclic transmit list.""" + + def __init__(self, scheduler, msg, period, duration, resolution): + super(CyclicSendTask, self).__init__(msg, period, duration) + self._scheduler = scheduler + self._index = None + self._count = int(duration / period) if duration else 0 + + self._msg = structures.CANCYCLICTXMSG() + self._msg.wCycleTime = int(round(period * resolution)) + self._msg.dwMsgId = msg.arbitration_id + self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + self._msg.uMsgInfo.Bits.dlc = msg.dlc + for i, b in enumerate(msg.data): + self._msg.abData[i] = b + self.start() + + def start(self): + """Start transmitting message (add to list if needed).""" + if self._index is None: + self._index = ctypes.c_uint32() + _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) + _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) + + def pause(self): + """Pause transmitting message (keep it in the list).""" + _canlib.canSchedulerStopMessage(self._scheduler, self._index) + + def stop(self): + """Stop transmitting message (remove from list).""" + # Remove it completely instead of just stopping it to avoid filling up + # the list with permanently stopped messages + _canlib.canSchedulerRemMessage(self._scheduler, self._index) + self._index = None diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 03c3b7167..c9a83dd4a 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -13,20 +13,20 @@ class VCITimeout(CanError): - """ Wraps the VCI_E_TIMEOUT error """ - pass + """ Wraps the VCI_E_TIMEOUT error """ + pass class VCIError(CanError): - """ Try to display errors that occur within the wrapped C library nicely. """ - pass + """ Try to display errors that occur within the wrapped C library nicely. """ + pass class VCIRxQueueEmptyError(VCIError): - """ Wraps the VCI_E_RXQUEUE_EMPTY error """ - def __init__(self): - super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") + """ Wraps the VCI_E_RXQUEUE_EMPTY error """ + def __init__(self): + super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") class VCIDeviceNotFoundError(CanInitializationError): - pass + pass diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index fcaf58536..2299b2559 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -10,54 +10,54 @@ from can import CanError, CanBackEndError, CanInitializationError, CanOperationError class SoftwareTestCase(unittest.TestCase): - """ - Test cases that test the software only and do not rely on an existing/connected hardware. - """ - def setUp(self): - pass - - def tearDown(self): - pass - - def test_bus_creation(self): - # channel must be >= 0 - with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = -1) - - # rxFifoSize must be > 0 - with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = 0, rxFifoSize = 0) - - # txFifoSize must be > 0 - with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) + """ + Test cases that test the software only and do not rely on an existing/connected hardware. + """ + def setUp(self): + pass + + def tearDown(self): + pass + + def test_bus_creation(self): + # channel must be >= 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = -1) + + # rxFifoSize must be > 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = 0, rxFifoSize = 0) + + # txFifoSize must be > 0 + with self.assertRaises(ValueError): + bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) class HardwareTestCase(unittest.TestCase): - """ - Test cases that rely on an existing/connected hardware. - """ - def setUp(self): - try: - bus = can.Bus(interface = 'ixxat', channel = 0) - except: - raise(unittest.SkipTest()) - - def tearDown(self): - pass - - def test_bus_creation(self): - # non-existent channel -> use arbitrary high value - with self.assertRaises(CanInitializationError): - bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) - - def test_send_after_shutdown(self): - bus = can.Bus(interface = 'ixxat', channel = 0) - msg = can.Message(arbitration_id = 0x3FF, dlc = 0) - bus.shutdown() - with self.assertRaises(CanOperationError): - bus.send(msg) + """ + Test cases that rely on an existing/connected hardware. + """ + def setUp(self): + try: + bus = can.Bus(interface = 'ixxat', channel = 0) + except: + raise(unittest.SkipTest()) + + def tearDown(self): + pass + + def test_bus_creation(self): + # non-existent channel -> use arbitrary high value + with self.assertRaises(CanInitializationError): + bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) + + def test_send_after_shutdown(self): + bus = can.Bus(interface = 'ixxat', channel = 0) + msg = can.Message(arbitration_id = 0x3FF, dlc = 0) + bus.shutdown() + with self.assertRaises(CanOperationError): + bus.send(msg) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From d5be879f5e64a9b269884b71cedae0491091c09f Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 21:17:11 +0200 Subject: [PATCH 0105/1235] Skip the test if there is an ImportError --- test/test_interface_ixxat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 2299b2559..15931856a 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -14,7 +14,10 @@ class SoftwareTestCase(unittest.TestCase): Test cases that test the software only and do not rely on an existing/connected hardware. """ def setUp(self): - pass + try: + bus = can.Bus(interface = 'ixxat', channel = 0) + except ImportError: + raise(unittest.SkipTest()) def tearDown(self): pass @@ -40,7 +43,7 @@ class HardwareTestCase(unittest.TestCase): def setUp(self): try: bus = can.Bus(interface = 'ixxat', channel = 0) - except: + except ImportError: raise(unittest.SkipTest()) def tearDown(self): From 519fc93de322673175320944643535addd3cb09a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 30 Apr 2019 21:24:40 +0200 Subject: [PATCH 0106/1235] make tests better suited for CI tests (by excluding them on some platforms) --- test/test_message_sync.py | 58 ++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/test/test_message_sync.py b/test/test_message_sync.py index 6cfbce265..dafa41036 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -16,7 +16,7 @@ from can import MessageSync, Message -from .config import IS_APPVEYOR, IS_CI +from .config import IS_CI, IS_APPVEYOR, IS_TRAVIS, IS_OSX from .message_helper import ComparingMessagesTestCase from .data.example_data import TEST_MESSAGES_BASE @@ -31,7 +31,8 @@ def inc(value): return value -@unittest.skipIf(IS_APPVEYOR, "this environment's timings are too unpredictable") +@unittest.skipIf(IS_APPVEYOR or (IS_TRAVIS and IS_OSX), + "this environment's timings are too unpredictable") class TestMessageSync(unittest.TestCase, ComparingMessagesTestCase): def __init__(self, *args, **kwargs): @@ -44,7 +45,7 @@ def setup_method(self, _): def teardown_method(self, _): gc.enable() - @pytest.mark.timeout(0.2) + @pytest.mark.timeout(inc(0.2)) def test_general(self): messages = [ Message(timestamp=50.0), @@ -73,7 +74,7 @@ def test_general(self): self.assertTrue(0.075 <= timings[3] < inc(0.085), str(timings[3])) self.assertTrue(0.0 <= timings[4] < inc(0.005), str(timings[4])) - @pytest.mark.timeout(0.1 * len(TEST_FEWER_MESSAGES)) # very conservative + @pytest.mark.timeout(inc(0.1) * len(TEST_FEWER_MESSAGES)) # very conservative def test_skip(self): messages = copy(TEST_FEWER_MESSAGES) sync = MessageSync(messages, skip=0.005, gap=0.0) @@ -89,30 +90,31 @@ def test_skip(self): self.assertMessagesEqual(messages, collected) -@unittest.skipIf(IS_APPVEYOR, "this environment's timings are too unpredictable") -@pytest.mark.timeout(0.3) -@pytest.mark.parametrize("timestamp_1,timestamp_2", [ - (0.0, 0.0), - (0.0, 0.01), - (0.01, 0.0), -]) -def test_gap(timestamp_1, timestamp_2): - """This method is alone so it can be parameterized.""" - messages = [ - Message(arbitration_id=0x1, timestamp=timestamp_1), - Message(arbitration_id=0x2, timestamp=timestamp_2) - ] - sync = MessageSync(messages, gap=0.1) - - gc.disable() - before = time() - collected = list(sync) - after = time() - gc.enable() - took = after - before - - assert 0.1 <= took < inc(0.3) - assert messages == collected +if not IS_APPVEYOR: # this environment's timings are too unpredictable + + @pytest.mark.timeout(inc(0.3)) + @pytest.mark.parametrize("timestamp_1,timestamp_2", [ + (0.0, 0.0), + (0.0, 0.01), + (0.01, 0.0), + ]) + def test_gap(timestamp_1, timestamp_2): + """This method is alone so it can be parameterized.""" + messages = [ + Message(arbitration_id=0x1, timestamp=timestamp_1), + Message(arbitration_id=0x2, timestamp=timestamp_2) + ] + sync = MessageSync(messages, gap=0.1) + + gc.disable() + before = time() + collected = list(sync) + after = time() + gc.enable() + took = after - before + + assert 0.1 <= took < inc(0.3) + assert messages == collected if __name__ == '__main__': From e9c709639878a6794936dd3a23d173d6cedd85b2 Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Tue, 30 Apr 2019 21:25:45 +0200 Subject: [PATCH 0107/1235] Merged in suggested changes from pull request. --- can/interfaces/ixxat/canlib.py | 10 +++++----- test/test_interface_ixxat.py | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index bfa518830..61dae6871 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -23,7 +23,7 @@ from can import BusABC, Message from can import CanError, CanBackEndError, CanInitializationError, CanOperationError -from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) +from can.broadcastmanager import LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT from . import constants, structures @@ -345,7 +345,7 @@ def __init__(self, channel, can_filters=None, **kwargs): try: _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) except: - raise(CanInitializationError("Could not open device.")) + raise CanInitializationError("Could not open device.") log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) @@ -355,7 +355,7 @@ def __init__(self, channel, can_filters=None, **kwargs): _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) # Signal TX/RX events when at least one frame has been handled except: - raise(CanInitializationError("Could not open and initialize channel.")) + raise CanInitializationError("Could not open and initialize channel.") _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) _canlib.canChannelActivate(self._channel_handle, constants.TRUE) @@ -515,9 +515,9 @@ def send(self, msg, timeout=None): if timeout: return False else: - raise(CanOperationError()) + raise CanOperationError("Timeout in library call.") except: - raise(CanOperationError()) + raise CanOperationError("Send failed.") return True diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 15931856a..a3ab494fb 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -16,6 +16,7 @@ class SoftwareTestCase(unittest.TestCase): def setUp(self): try: bus = can.Bus(interface = 'ixxat', channel = 0) + bus.shutdown() except ImportError: raise(unittest.SkipTest()) @@ -43,6 +44,7 @@ class HardwareTestCase(unittest.TestCase): def setUp(self): try: bus = can.Bus(interface = 'ixxat', channel = 0) + bus.shutdown() except ImportError: raise(unittest.SkipTest()) From 6bcb7367635be29a77a11ae789cd1ff5cbbbe293 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 29 Apr 2019 10:45:00 +0200 Subject: [PATCH 0108/1235] Add need for case to LogReader to the add new IO docs --- doc/internal-api.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/internal-api.rst b/doc/internal-api.rst index 54978e0ce..c43db3394 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -90,14 +90,15 @@ Ideally add both reading and writing support for the new file format, although t Besides from a constructor, only ``__iter__(self)`` needs to be implemented. 3. Implement a writer ``CanstoreWriter`` (which often extends :class:`can.io.generic.BaseIOHandler` and :class:`can.Listener`, but does not have to). Besides from a constructor, only ``on_message_received(self, msg)`` needs to be implemented. -4. Document the two new classes (and possibly additional helpers) with docstrings and comments. +4. Add a case to ``can.io.player.LogReader``'s ``__new__()``. +5. Document the two new classes (and possibly additional helpers) with docstrings and comments. Please mention features and limitations of the implementation. -5. Add a short section to the bottom of *doc/listeners.rst*. -6. Add tests where appropriate, for example by simply adding a test case called +6. Add a short section to the bottom of *doc/listeners.rst*. +7. Add tests where appropriate, for example by simply adding a test case called `class TestCanstoreFileFormat(ReaderWriterTest)` to *test/logformats_test.py*. That should already handle all of the general testing. Just follow the way the other tests in there do it. -7. Add imports to *can/__init__py* and *can/io/__init__py* so that the +8. Add imports to *can/__init__py* and *can/io/__init__py* so that the new classes can be simply imported as *from can import CanstoreReader, CanstoreWriter*. From 21d4a4514028260786a8464fd8d4bd90431026cf Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 4 Feb 2019 17:03:59 +0100 Subject: [PATCH 0109/1235] check new linux environment --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0a063684f..84ee9fb30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ python: # CPython; versions pre-2.7 and 3.0-3.4 have reached EOL - "2.7" - "3.6" - - "3.7-dev" # TODO: change to "3.7" once it is supported by travis-ci + - "3.7" - "nightly" # PyPy: - "pypy" # Python 2.7 @@ -25,7 +25,7 @@ os: # - windows # Windows is not supported at all by Travis CI as of Feb. 2018 # Linux setup -dist: trusty +dist: xenial sudo: required matrix: From beedd79aa1d0a7b36467b177ed707167a74de73f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 18:28:31 +0200 Subject: [PATCH 0110/1235] separate different jobs --- .travis.yml | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 84ee9fb30..332400636 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - # CPython; versions pre-2.7 and 3.0-3.4 have reached EOL + # CPython; versions pre-2.7 and 3.0-3.5 have reached EOL - "2.7" - "3.6" - "3.7" @@ -29,8 +29,16 @@ dist: xenial sudo: required matrix: - # see "os: ..." above include: + # building the docs + - python: "3.7" + env: BUILD_ONLY_DOCS=TRUE + # testing socketcan on Trusty + - os: linux + dist: trusty + python: "3.6" + env: TEST_SOCKETCAN=TRUE + # testing on macOS; see "os: ..." above - os: osx osx_image: xcode8.3 python: "3.6-dev" @@ -42,22 +50,28 @@ matrix: python: "nightly" allow_failures: - # allow all nighly builds to fail, since these python versions might be unstable - - python: "nightly" # we do not allow dev builds to fail, since these builds are considered stable enough + # we allow all nighly builds to fail, since these python versions might be unstable + - python: "nightly" install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi - - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then travis_retry pip install -r doc/doc-requirements.txt; fi + - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi + - if [[ "$BUILD_ONLY_DOCS" ]]; then travis_retry pip install -r doc/doc-requirements.txt; fi - travis_retry pip install .[test] script: - - pytest - - codecov - # Build Docs with Sphinx - # -a Write all files - # -n nitpicky - - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then python -m sphinx -an doc build; fi + - | + if [[ "$BUILD_ONLY_DOCS" ]]; then + # Build the docs with Sphinx + # -a Write all files + # -n nitpicky + python -m sphinx -an doc build; + else + # Run the tests + python setup.py test + # Upload the coverage to codecov.io + codecov; + fi # Have travis deploy tagged commits to PyPi deploy: From f754a79f0bcb69d87bd04e64af0cc77865eb1097 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 18:47:19 +0200 Subject: [PATCH 0111/1235] fix test config --- test/config.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/config.py b/test/config.py index 3c37bcbe6..940ba7cf0 100644 --- a/test/config.py +++ b/test/config.py @@ -12,22 +12,25 @@ from os import environ as environment +def env(name): # type: bool + return environment.get(name, '').lower() in ("yes", "true", "t", "1") + + # ############################## Continuos integration # see here for the environment variables that are set on the CI servers: # - https://docs.travis-ci.com/user/environment-variables/ # - https://www.appveyor.com/docs/environment-variables/ -IS_TRAVIS = environment.get('TRAVIS', '').lower() == 'true' -IS_APPVEYOR = environment.get('APPVEYOR', '').lower() == 'true' +IS_TRAVIS = env('TRAVIS') +IS_APPVEYOR = env('APPVEYOR') -IS_CI = IS_TRAVIS or IS_APPVEYOR or \ - environment.get('CI', '').lower() == 'true' or \ - environment.get('CONTINUOUS_INTEGRATION', '').lower() == 'true' +IS_CI = IS_TRAVIS or IS_APPVEYOR or env('CI') or env('CONTINUOUS_INTEGRATION') if IS_APPVEYOR and IS_TRAVIS: raise EnvironmentError("IS_APPVEYOR and IS_TRAVIS cannot be both True at the same time") + # ############################## Platforms _sys = platform.system().lower() @@ -42,11 +45,10 @@ "can be True at the same time " + '(platform.system() == "{}")'.format(platform.system()) ) -elif not IS_WINDOWS and not IS_LINUX and not IS_OSX: - raise EnvironmentError("one of IS_WINDOWS, IS_LINUX, IS_OSX has to be True") + # ############################## What tests to run TEST_CAN_FD = True -TEST_INTERFACE_SOCKETCAN = IS_CI and IS_LINUX +TEST_INTERFACE_SOCKETCAN = IS_LINUX and env('TEST_SOCKETCAN') From 26d84e9bf33878aefacab379f2c78a25d637005e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 18:54:09 +0200 Subject: [PATCH 0112/1235] fix return codes in main script --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 332400636..14207e299 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,12 +65,12 @@ script: # Build the docs with Sphinx # -a Write all files # -n nitpicky - python -m sphinx -an doc build; + python -m sphinx -an doc build else # Run the tests python setup.py test # Upload the coverage to codecov.io - codecov; + if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]]; then codecov fi fi # Have travis deploy tagged commits to PyPi From fb1056a41213225c6deb5ef7ee4f49ec76e41185 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 19:03:28 +0200 Subject: [PATCH 0113/1235] fix syntax error --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 14207e299..36a9b73c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,7 @@ script: # Run the tests python setup.py test # Upload the coverage to codecov.io - if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]]; then codecov fi + if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]]; then codecov; fi fi # Have travis deploy tagged commits to PyPi From 32b8620051a13623874f3058944e4fccea07ab6b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 19:10:20 +0200 Subject: [PATCH 0114/1235] fix socketcan skipping if not available --- test/test_detect_available_configs.py | 6 +++--- test/test_socketcan_helpers.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py index 153f91e3a..ca2d82c15 100644 --- a/test/test_detect_available_configs.py +++ b/test/test_detect_available_configs.py @@ -15,7 +15,7 @@ from can import detect_available_configs -from .config import IS_LINUX, IS_CI +from .config import IS_LINUX, IS_CI, TEST_INTERFACE_SOCKETCAN class TestDetectAvailableConfigs(unittest.TestCase): @@ -45,13 +45,13 @@ def test_content_socketcan(self): for config in configs: self.assertEqual(config['interface'], 'socketcan') - @unittest.skipUnless(IS_LINUX and IS_CI, "socketcan is only available on Linux") + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "socketcan is not tested") def test_socketcan_on_ci_server(self): configs = detect_available_configs(interfaces='socketcan') self.assertGreaterEqual(len(configs), 1) self.assertIn('vcan0', [config['channel'] for config in configs]) - # see TestSocketCanHelpers.test_find_available_interfaces() + # see TestSocketCanHelpers.test_find_available_interfaces() too if __name__ == '__main__': diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index beb2b27df..f1462549a 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -37,7 +37,7 @@ def test_find_available_interfaces(self): self.assertGreaterEqual(len(result), 0) for entry in result: self.assertRegexpMatches(entry, r"v?can\d+") - if IS_CI: + if TEST_INTERFACE_SOCKETCAN: self.assertGreaterEqual(len(result), 1) self.assertIn("vcan0", result) From 8700cce4fd8d8c956b3d57843da8883e3854feb5 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 19:14:49 +0200 Subject: [PATCH 0115/1235] fix the error code semantics --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 36a9b73c7..2da6f25d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,7 +68,7 @@ script: python -m sphinx -an doc build else # Run the tests - python setup.py test + python setup.py test && # (preserve the error code semantics) # Upload the coverage to codecov.io if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]]; then codecov; fi fi From b64350efbe9e342e0f27530bac6bde3d6a5efa6a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 19:32:18 +0200 Subject: [PATCH 0116/1235] FIX: Warning: 'classifiers' should be a list, got type 'tuple' --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 87c9d489c..c600b7215 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ url="https://github.com/hardbyte/python-can", description="Controller Area Network interface module for Python", long_description=long_description, - classifiers=( + classifiers=[ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", "Programming Language :: Python :: 2.7", @@ -74,7 +74,7 @@ "Topic :: System :: Networking", "Topic :: System :: Hardware :: Hardware Drivers", "Topic :: Utilities" - ), + ], # Code version=version, From 8ab8d3390dc47d2d17b5f006b512778fd0921d8f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 19:45:06 +0200 Subject: [PATCH 0117/1235] add 3.8-dev and cleanups --- .travis.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2da6f25d7..0d2ea2e7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,11 @@ python: - "2.7" - "3.6" - "3.7" - - "nightly" + - 3.8-dev + - nightly # PyPy: - - "pypy" # Python 2.7 - - "pypy3.5" # Python 3.5 + - pypy # Python 2.7 + - pypy3.5 # Python 3.5 os: - linux # Linux is officially supported and we test the library under @@ -33,7 +34,7 @@ matrix: # building the docs - python: "3.7" env: BUILD_ONLY_DOCS=TRUE - # testing socketcan on Trusty + # testing socketcan on Trusty & Python 3.6, since it is not available on Xenial - os: linux dist: trusty python: "3.6" @@ -41,18 +42,20 @@ matrix: # testing on macOS; see "os: ..." above - os: osx osx_image: xcode8.3 - python: "3.6-dev" + python: 3.6-dev - os: osx osx_image: xcode8.3 - python: "3.7-dev" + python: 3.7-dev - os: osx osx_image: xcode8.3 - python: "nightly" + python: nightly allow_failures: - # we do not allow dev builds to fail, since these builds are considered stable enough - # we allow all nighly builds to fail, since these python versions might be unstable - - python: "nightly" + # we allow all dev & nighly builds to fail, since these python versions might + # still be very unstable + - python: + - 3.8-dev + - nightly install: - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi From c8367cd9f390a06e6d591344752ea759eecb8073 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 1 May 2019 19:47:37 +0200 Subject: [PATCH 0118/1235] fix allow_failures --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0d2ea2e7a..6305ddad8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,9 +53,8 @@ matrix: allow_failures: # we allow all dev & nighly builds to fail, since these python versions might # still be very unstable - - python: - - 3.8-dev - - nightly + - python: 3.8-dev + - python: nightly install: - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi From e8e4522829a29d8d6a08821fa6442ff870c639c9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 2 May 2019 11:47:15 +0200 Subject: [PATCH 0119/1235] address review --- .travis.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6305ddad8..38a4b02e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,6 @@ os: # Linux setup dist: xenial -sudo: required matrix: include: @@ -38,6 +37,7 @@ matrix: - os: linux dist: trusty python: "3.6" + sudo: required env: TEST_SOCKETCAN=TRUE # testing on macOS; see "os: ..." above - os: osx @@ -70,9 +70,13 @@ script: python -m sphinx -an doc build else # Run the tests - python setup.py test && # (preserve the error code semantics) + python setup.py test + # preserve the error code + RETURN_CODE=$? # Upload the coverage to codecov.io - if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]]; then codecov; fi + codecov + # set error code + (exit $RETURN_CODE); fi # Have travis deploy tagged commits to PyPi From 62de93716c328b3d5d9c3455933f981e3704b5ee Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 3 May 2019 04:23:25 +0200 Subject: [PATCH 0120/1235] Fix broken execution of tests on some Windows environments (#558) Removes the non-standard usage of __test__ = False to exclude the base class from execution and uses a del statement instead. --- test/logformats_test.py | 21 +++++++++------------ test/test_scripts.py | 19 ++++++++----------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 76c9cc426..7ce3ee43e 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -43,9 +43,12 @@ class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase): """Tests a pair of writer and reader by writing all data first and then reading all data and checking if they could be reconstructed correctly. Optionally writes some comments as well. - """ - __test__ = False + .. note:: + This class is prevented from being executed as a test + case itself by a *del* statement in at the end of the file. + (Source: `*Wojciech B.* on StackOverlfow `_) + """ __metaclass__ = ABCMeta @@ -318,8 +321,6 @@ def assertIncludesComments(self, filename): class TestAscFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" - __test__ = True - def _setup_instance(self): super(TestAscFileFormat, self)._setup_instance_helper( can.ASCWriter, can.ASCReader, @@ -332,8 +333,6 @@ def _setup_instance(self): class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader""" - __test__ = True - def _setup_instance(self): super(TestBlfFileFormat, self)._setup_instance_helper( can.BLFWriter, can.BLFReader, @@ -368,8 +367,6 @@ def test_read_known_file(self): class TestCanutilsFileFormat(ReaderWriterTest): """Tests can.CanutilsLogWriter and can.CanutilsLogReader""" - __test__ = True - def _setup_instance(self): super(TestCanutilsFileFormat, self)._setup_instance_helper( can.CanutilsLogWriter, can.CanutilsLogReader, @@ -382,8 +379,6 @@ def _setup_instance(self): class TestCsvFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" - __test__ = True - def _setup_instance(self): super(TestCsvFileFormat, self)._setup_instance_helper( can.CSVWriter, can.CSVReader, @@ -396,8 +391,6 @@ def _setup_instance(self): class TestSqliteDatabaseFormat(ReaderWriterTest): """Tests can.SqliteWriter and can.SqliteReader""" - __test__ = True - def _setup_instance(self): super(TestSqliteDatabaseFormat, self)._setup_instance_helper( can.SqliteWriter, can.SqliteReader, @@ -453,5 +446,9 @@ def test_not_crashes_with_file(self): printer(message) +# this excludes the base class from being executed as a test case itself +del(ReaderWriterTest) + + if __name__ == '__main__': unittest.main() diff --git a/test/test_scripts.py b/test/test_scripts.py index da3ba1d6c..74ae71489 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -15,19 +15,17 @@ from .config import * + class CanScriptTest(unittest.TestCase): + __metaclass__ = ABCMeta + @classmethod def setUpClass(cls): # clean up the argument list so the call to the main() functions # in test_does_not_crash() succeeds sys.argv = sys.argv[:1] - #: this is overridden by the subclasses - __test__ = False - - __metaclass__ = ABCMeta - def test_do_commands_exist(self): """This test calls each scripts once and verifies that the help can be read without any other errors, like the script not being @@ -54,8 +52,7 @@ def test_does_not_crash(self): # test main method with self.assertRaises(SystemExit) as cm: module.main() - self.assertEqual(cm.exception.code, errno.EINVAL, - 'Calling main failed:\n{}'.format(command, e.output)) + self.assertEqual(cm.exception.code, errno.EINVAL) @abstractmethod def _commands(self): @@ -73,8 +70,6 @@ def _import(self): class TestLoggerScript(CanScriptTest): - __test__ = True - def _commands(self): commands = [ "python -m can.logger --help", @@ -91,8 +86,6 @@ def _import(self): class TestPlayerScript(CanScriptTest): - __test__ = True - def _commands(self): commands = [ "python -m can.player --help", @@ -110,5 +103,9 @@ def _import(self): # TODO add #390 +# this excludes the base class from being executed as a test case itself +del(CanScriptTest) + + if __name__ == '__main__': unittest.main() From 3dd1184b0668f3479f57a01049d7a4c42b819610 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 3 May 2019 12:49:37 +1000 Subject: [PATCH 0121/1235] Use correct appveyor Python version (#566) Use the environment variable from the env matrix --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3b52143e7..b36a94213 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,7 +16,7 @@ environment: install: # Prepend Python installation and scripts (e.g. pytest) to PATH - - set PATH=%PYTHON_INSTALL%;%PYTHON_INSTALL%\\Scripts;%PATH% + - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% # We need to install the python-can library itself including the dependencies - "python -m pip install .[test,neovi]" From 3ff22b2227b904933dbdb031d45d6deefa944d9a Mon Sep 17 00:00:00 2001 From: shedfly Date: Fri, 3 May 2019 03:10:19 -0700 Subject: [PATCH 0122/1235] adding seeedstudio usb-can-analyzer interface --- can/interfaces/__init__.py | 3 +- can/interfaces/usb_can_analyzer/__init__.py | 6 + can/interfaces/usb_can_analyzer/notes | 167 +++++++++++ .../usb_can_analyzer/usb_can_analyzer.py | 276 ++++++++++++++++++ setup.py | 3 +- 5 files changed, 453 insertions(+), 2 deletions(-) create mode 100644 can/interfaces/usb_can_analyzer/__init__.py create mode 100644 can/interfaces/usb_can_analyzer/notes create mode 100644 can/interfaces/usb_can_analyzer/usb_can_analyzer.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index ec79e51d6..864233b0f 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -23,7 +23,8 @@ 'vector': ('can.interfaces.vector', 'VectorBus'), 'slcan': ('can.interfaces.slcan', 'slcanBus'), 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus'), - 'systec': ('can.interfaces.systec', 'UcanBus') + 'systec': ('can.interfaces.systec', 'UcanBus'), + 'usb_can_analyzer': ('can.interfaces.usb_can_analyzer.usb_can_analyzer', 'CanAnalyzer') } BACKENDS.update({ diff --git a/can/interfaces/usb_can_analyzer/__init__.py b/can/interfaces/usb_can_analyzer/__init__.py new file mode 100644 index 000000000..ec07c4d68 --- /dev/null +++ b/can/interfaces/usb_can_analyzer/__init__.py @@ -0,0 +1,6 @@ +# coding: utf-8 + +""" +""" + +from can.interfaces.usb_can_analyzer.usb_can_analyzer import CanAnalyzer as Bus diff --git a/can/interfaces/usb_can_analyzer/notes b/can/interfaces/usb_can_analyzer/notes new file mode 100644 index 000000000..829c85173 --- /dev/null +++ b/can/interfaces/usb_can_analyzer/notes @@ -0,0 +1,167 @@ +USB-CAN Analyzer - USB to CAN Bus Serial Protocol Definition + +Posted by Wilfried Voss on September 19, 2018 - copperhilltech.com + +CAN Bus To USB Mini Converter + +The USB-CAN Analyzer, in combination with the corresponding Windows software, represents a very economical solution to run an effective CAN Bus Analyzer. It allows you to develop, test, manage, and maintain your own CAN Bus network, as well as receiving, sending, logging, and analyzing CAN Bus data. + +For the following, we need to point out that the device is manufactured in China, and documentation is sparse. And while this is a very affordable solution, there is naturally room for some improvement. In general, there is no application interface, or, to be more precise, the communication between the USB-CAN device and the host system, in this case a PC running Windows, is not documented. Consequently, there is currently no software support for Linux (e.g. SocketCAN), and it would be useful if users could write their own applications, regardless of the operating system they use. + +As a first step in this direction, we invested some efforts to analyze and document the USB communication protocol, which makes it easy developing an application interface. + +There has been a document, USB (Serial port) to CAN protocol defines, that was distributed online, claiming a documentation of the "Send and receive data packet format." However, after analyzing the protocol, we believe that the data frame architecture described therein represents the original intention. + +We believe that the architecture as described caused problems when it came to higher baud rates, specifically at 1 Mbps in combination with high busloads. Consequently, the firmware developers decided to optimize the data frame size. As a result, they cut the frame size from a static 20 bytes to a dynamic 6 to 16 bytes, depending on message ID length (11 or 29 bits) and number of data bytes. + +The following describes the serial protocol as we analyzed it, with a few unknowns remaining. However, the documentation includes all vital information for creating your own CAN Bus application. + +CAN Bus Data Frame + +The CAN Bus data frame format applies to both, send and receive. + +Both message ID lengths, 11-bit standard and 29-bit extended, use the first two bytes in the serial data frame. Depending on the message ID length, the remaining bytes will be filled differently. + +The data frame does not utilize a checksum check, most probably due to keeping the number of transmitted bytes to a minimum and cutting down on processing time. + +Byte Description + +0 0xAA = Packet Start + +1 CAN Bus Data Frame Information + + Bit 7 Always 1 + + Bit 6 Always 1 + + Bit 5 0=STD; 1=EXT + + Bit 4 0=Data; 1=Remote + + Bit 3…0 Data Length Code (DLC); 0…8 + +Standard CAN data frame continues: + +Byte Description + +2 Message ID LSB + +3 Message ID MSB + +x Data, length depending on DLC + +y 0x55 = Packet End + +Extended CAN data frame continues: + +Byte Description + +2 Message ID LSB + +3 Message ID 2ND + +4 Message ID 3RD + +5 Message ID MSB + +x Data, length depending on DLC + +y 0x55 = Packet End + +CAN Bus Data Frame Size + +Standard 11-bit 6 bytes with zero data bytes + + 14 bytes with eight data bytes + +Extended 29-bit 8 bytes with zero data bytes + + 16 bytes with eight data bytes + +CAN Bus Initialization Frame + +The CAN Bus initialization frame has a constant length of 20 bytes. + +Byte(s) Description + +0 0xAA = Frame Start Byte 1 + +1 0x55 = Frame Start Byte 2 + +2 0x12 = Initialization Message ID + +3 CAN Baud Rate (See table below) + +4 0x01=STD; 0x02=EXT; Applies only to transmitting + +5 – 8 Filter ID; LSB first, MSB last + +9 – 12 Mask ID; LSB first, MSB last + +13 Operation Mode (See table below) + +14 0x01; Purpose Unknown + +15 – 18 All 0x00 + +19 Checksum + +Note: The checksum is processed between bytes 2 and 18. + +CAN Bus baud rate + +0x01 1000k + +0x02 800k + +0x03 500k + +0x04 400k + +0x05 250k + +0x06 200k + +0x07 125k + +0x08 100k + +0x09 50k + +0x0A 20k + +0x0B 10k + +0x0C 5k + +Operation Mode + +0x00 Normal + +0x01 Loopback + +0x02 Silent (Listen-Only) + +0x03 Loopback + Silent + +CAN Controller Status Frame + +The format of the status request and status report frames are identical, where the request frame sends all zeroes between the frame header (frame start tokens plus frame ID) and the checksum, i.e. bytes 3 through 18 are 0x00. + +Byte(s) Description + +0 0xAA = Frame Start Byte 1 + +1 0x55 = Frame Start Byte 2 + +2 0x04 = Status Message ID + +3 Receive Error Counter + +4 Transmit Error Counter + +5 – 18 Unknown Purpose + In case of status report, they may be filled with data + +19 Checksum + diff --git a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py new file mode 100644 index 000000000..9fee2fc4d --- /dev/null +++ b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py @@ -0,0 +1,276 @@ +# coding: utf-8 + +""" +To Support the Seeed USB-Can analyzer interface. The device will appear +as a serial port, for example "/dev/ttyS1" or "/dev/ttyUSB0" on Linux +machines or "COM1" on Windows. +https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html +SKU 114991193 +See protoocl: +https://copperhilltech.com/blog/usbcan-analyzer-usb-to-can-bus-serial-protocol-definition/ + +this file uses Crc8Darc checksums. +""" + + +from __future__ import absolute_import, division + +import logging +import struct +from crccheck.crc import Crc8Darc +from time import sleep, time +from can import BusABC, Message + +logger = logging.getLogger('can.CanAnalyzer') + +try: + import serial +except ImportError: + logger.warning("You won't be able to use the serial can backend without " + "the serial module installed!") + serial = None + + +class CanAnalyzer(BusABC): + """ + Enable basic can communication over a serial device. + + .. note:: See :meth:`can.interfaces.serial.CanAnalyzer._recv_internal` + for some special semantics. + + """ + BITRATE = { + 1000000: 0x01, + 800000: 0x02, + 500000: 0x03, + 400000: 0x04, + 250000: 0x05, + 200000: 0x06, + 125000: 0x07, + 100000: 0x08, + 50000: 0x09, + 20000: 0x0A, + 10000: 0x0B, + 5000: 0x0C + } + + FRAMETYPE = { + "STD":0x01, + "EXT":0x02 + } + + OPERATIONMODE = { + "normal":0x00, + "loopback":0x01, + "silent":0x02, + "loopback_and_silent":0x03 + } + + def __init__(self, channel, baudrate=2000000, timeout=0.1, rtscts=False, + frame_type='STD', operation_mode='normal', bit_rate=500000, + *args, **kwargs): + """ + :param str channel: + The serial device to open. For example "/dev/ttyS1" or + "/dev/ttyUSB0" on Linux or "COM1" on Windows systems. + + :param int baudrate: + Baud rate of the serial device in bit/s (default 115200). + + .. warning:: + Some serial port implementations don't care about the baudrate. + + :param float timeout: + Timeout for the serial device in seconds (default 0.1). + + :param bool rtscts: + turn hardware handshake (RTS/CTS) on and off + + """ + self.bit_rate = bit_rate + self.frame_type = frame_type + self.op_mode = operation_mode + self.filter_id = bytearray([0x00, 0x00, 0x00, 0x00]) + self.mask_id = bytearray([0x00, 0x00, 0x00, 0x00]) + if not channel: + raise ValueError("Must specify a serial port.") + + self.channel_info = "Serial interface: " + channel + self.ser = serial.Serial( + channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts) + + super(CanAnalyzer, self).__init__(channel=channel, *args, **kwargs) + self.init_frame + + def shutdown(self): + """ + Close the serial interface. + """ + self.ser.close() + + def init_frame(self, timeout=None): + + byte_msg = bytearray() + byte_msg.append(0xAA) # Frame Start Byte 1 + byte_msg.append(0x55) # Frame Start Byte 2 + + byte_msg.append(0x12) # Initialization Message ID + + byte_msg.append(CanAnalyzer.BITRATE[self.bit_rate]) # CAN Baud Rate + byte_msg.append(CanAnalyzer.FRAMETYPE[self.frame_type]) + + for i in range(0, 4): + byte_msg.append(self.filter_id[i]) + + for i in range(0, 4): + byte_msg.append(self.mask_id[i]) + + byte_msg.append(CanAnalyzer.OPERATIONMODE[self.op_mode]) + + byte_msg.append(0x01) + + for i in range(0, 4): + byte_msg.append(0x00) + + print "byte_str = " + str(byte_msg[2:]) + + crc = Crc8Darc.calc(byte_msg[2:]) + crc_byte = struct.pack('B', crc) + + print "crc_byte: " + str(crc_byte) + " length: " + str(len(crc_byte)) + + byte_msg.append(crc_byte) + self.ser.write(byte_msg) + + def flush_buffer(self): + self.ser.flushInput() + + def status_frame(self, timeout=None): + byte_msg = bytearray() + byte_msg.append(0xAA) # Frame Start Byte 1 + byte_msg.append(0x55) # Frame Start Byte 2 + byte_msg.append(0x04) # Status Message ID + byte_msg.append(0x00) # In response packet - Rx error count + byte_msg.append(0x00) # In response packet - Tx error count + + for i in range(0, 14): + byte_msg.append(0x00) + + print "byte_str = " + str(byte_msg[2:]) + + crc = Crc8Darc.calc(byte_msg[2:]) + crc_byte = struct.pack('B', crc) + + print "crc_byte: " + str(crc_byte) + " length: " + str(len(crc_byte)) + + byte_msg.append(crc_byte) + self.ser.write(byte_msg) + + + def send(self, msg, timeout=None): + """ + Send a message over the serial device. + + :param can.Message msg: + Message to send. + + .. note:: Flags like ``extended_id``, ``is_remote_frame`` and + ``is_error_frame`` will be ignored. + + .. note:: If the timestamp is a float value it will be converted + to an integer. + + :param timeout: + This parameter will be ignored. The timeout value of the channel is + used instead. + + """ + try: + timestamp = struct.pack(' Date: Fri, 3 May 2019 10:19:23 -0400 Subject: [PATCH 0123/1235] Adding CAN FD 64 frame support to blf reader --- can/io/blf.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/can/io/blf.py b/can/io/blf.py index 059da5ec9..d162fdebc 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -66,6 +66,13 @@ class BLFParseError(Exception): # valid data bytes, data CAN_FD_MSG_STRUCT = struct.Struct(" Date: Mon, 6 May 2019 06:59:03 +0000 Subject: [PATCH 0124/1235] replacing print with log, moving crccheck to extras_require --- .../usb_can_analyzer/usb_can_analyzer.py | 39 +++++++++---------- setup.py | 6 +-- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py index 9fee2fc4d..9a2ee0baa 100644 --- a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py +++ b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py @@ -12,12 +12,10 @@ this file uses Crc8Darc checksums. """ - from __future__ import absolute_import, division import logging import struct -from crccheck.crc import Crc8Darc from time import sleep, time from can import BusABC, Message @@ -30,6 +28,11 @@ "the serial module installed!") serial = None +try: + from crccheck.crc import Crc8Darc +except ImportError: + logger.warning("The interface requires the install option crccheck.") + class CanAnalyzer(BusABC): """ @@ -53,7 +56,7 @@ class CanAnalyzer(BusABC): 10000: 0x0B, 5000: 0x0C } - + FRAMETYPE = { "STD":0x01, "EXT":0x02 @@ -100,7 +103,7 @@ def __init__(self, channel, baudrate=2000000, timeout=0.1, rtscts=False, channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts) super(CanAnalyzer, self).__init__(channel=channel, *args, **kwargs) - self.init_frame + self.init_frame() def shutdown(self): """ @@ -119,27 +122,23 @@ def init_frame(self, timeout=None): byte_msg.append(CanAnalyzer.BITRATE[self.bit_rate]) # CAN Baud Rate byte_msg.append(CanAnalyzer.FRAMETYPE[self.frame_type]) - for i in range(0, 4): - byte_msg.append(self.filter_id[i]) + byte_msg.extend(self.filter_id) - for i in range(0, 4): - byte_msg.append(self.mask_id[i]) + byte_msg.extend(self.mask_id) byte_msg.append(CanAnalyzer.OPERATIONMODE[self.op_mode]) - + byte_msg.append(0x01) for i in range(0, 4): byte_msg.append(0x00) - print "byte_str = " + str(byte_msg[2:]) - crc = Crc8Darc.calc(byte_msg[2:]) crc_byte = struct.pack('B', crc) - print "crc_byte: " + str(crc_byte) + " length: " + str(len(crc_byte)) - byte_msg.append(crc_byte) + + logger.debug("init_frm:\t" + str(byte_msg)) self.ser.write(byte_msg) def flush_buffer(self): @@ -156,16 +155,13 @@ def status_frame(self, timeout=None): for i in range(0, 14): byte_msg.append(0x00) - print "byte_str = " + str(byte_msg[2:]) - crc = Crc8Darc.calc(byte_msg[2:]) crc_byte = struct.pack('B', crc) - print "crc_byte: " + str(crc_byte) + " length: " + str(len(crc_byte)) - byte_msg.append(crc_byte) - self.ser.write(byte_msg) + logger.debug("status_frm:\t" + str(byte_msg)) + self.ser.write(byte_msg) def send(self, msg, timeout=None): """ @@ -203,6 +199,8 @@ def send(self, msg, timeout=None): for i in range(0, msg.dlc): byte_msg.append(msg.data[i]) byte_msg.append(0xBB) + + logger.debug("Sending:\t" + byte_msg) self.ser.write(byte_msg) def _recv_internal(self, timeout): @@ -229,7 +227,7 @@ def _recv_internal(self, timeout): # ser.read can return an empty string # or raise a SerialException rx_byte_1 = self.ser.read() - + except serial.SerialException: return None, False @@ -260,7 +258,7 @@ def _recv_internal(self, timeout): arbitration_id=arb_id, extended_id=is_extended, data=data) - + logger.debug("recv_msg:\t" + str(msg)) return msg, False else: @@ -268,7 +266,6 @@ def _recv_internal(self, timeout): return None, None - def fileno(self): if hasattr(self.ser, 'fileno'): return self.ser.fileno() diff --git a/setup.py b/setup.py index dcef0acb3..d4c6b7838 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,8 @@ # Dependencies extras_require = { 'serial': ['pyserial~=3.0'], - 'neovi': ['python-ics>=2.12'] + 'neovi': ['python-ics>=2.12'], + 'usb-can-analyzer': ['crccheck>=0.6'] } tests_require = [ @@ -36,8 +37,7 @@ 'codecov~=2.0', 'future', 'six', - 'hypothesis', - 'crccheck' + 'hypothesis' ] + extras_require['serial'] extras_require['test'] = tests_require From ea5d15cecf4476d3dbb232af26276557038efa53 Mon Sep 17 00:00:00 2001 From: shedfly Date: Mon, 6 May 2019 10:43:38 +0000 Subject: [PATCH 0125/1235] adding 'is_remote' to message, add binascii to debug logs --- .../usb_can_analyzer/usb_can_analyzer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py index 9a2ee0baa..6f37a9066 100644 --- a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py +++ b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py @@ -16,6 +16,7 @@ import logging import struct +import binascii from time import sleep, time from can import BusABC, Message @@ -138,7 +139,7 @@ def init_frame(self, timeout=None): byte_msg.append(crc_byte) - logger.debug("init_frm:\t" + str(byte_msg)) + logger.debug("init_frm:\t" + binascii.hexlify(byte_msg)) self.ser.write(byte_msg) def flush_buffer(self): @@ -160,7 +161,7 @@ def status_frame(self, timeout=None): byte_msg.append(crc_byte) - logger.debug("status_frm:\t" + str(byte_msg)) + logger.debug("status_frm:\t" + binascii.hexlify(byte_msg)) self.ser.write(byte_msg) def send(self, msg, timeout=None): @@ -200,7 +201,7 @@ def send(self, msg, timeout=None): byte_msg.append(msg.data[i]) byte_msg.append(0xBB) - logger.debug("Sending:\t" + byte_msg) + logger.debug("Sending:\t" + binascii.hexlify(byte_msg)) self.ser.write(byte_msg) def _recv_internal(self, timeout): @@ -240,8 +241,7 @@ def _recv_internal(self, timeout): else: length = int(rx_byte_2 & 0x0F) is_extended = bool(rx_byte_2 & 0x20) - is_data = bool(rx_byte_2 & 10) - + is_remote = bool(rx_byte_2 & 0x10) if is_extended: s_3_4_5_6 = bytearray(self.ser.read(4)) arb_id = (struct.unpack(' Date: Mon, 6 May 2019 12:57:35 +0000 Subject: [PATCH 0126/1235] more general name 'crccheck' for install option, also update Message named args --- can/interfaces/usb_can_analyzer/usb_can_analyzer.py | 3 ++- setup.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py index 6f37a9066..b1fd95ffa 100644 --- a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py +++ b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py @@ -255,8 +255,9 @@ def _recv_internal(self, timeout): if end_packet == 0x55: msg = Message(timestamp=time_stamp, arbitration_id=arb_id, - extended_id=is_extended, + is_extended_id=is_extended, is_remote_frame=is_remote, + dlc=length, data=data) logger.debug("recv message: " + str(msg)) return msg, False diff --git a/setup.py b/setup.py index d4c6b7838..a4ae7b405 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ extras_require = { 'serial': ['pyserial~=3.0'], 'neovi': ['python-ics>=2.12'], - 'usb-can-analyzer': ['crccheck>=0.6'] + 'crccheck': ['crccheck>=0.6'] } tests_require = [ From fad98eee7b3dd69679aa357a9d1de9983ae90701 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 7 May 2019 16:41:52 +0200 Subject: [PATCH 0127/1235] better comments --- test/simplecyclic_test.py | 2 ++ test/test_message_sync.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index d47e879a4..a10871648 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -30,6 +30,7 @@ def test_cycle_time(self): with can.interface.Bus(bustype='virtual') as bus1: with can.interface.Bus(bustype='virtual') as bus2: + # disabling the garbage collector makes the time readings more reliable gc.disable() task = bus1.send_periodic(msg, 0.01, 1) @@ -43,6 +44,7 @@ def test_cycle_time(self): last_msg = bus2.recv() next_last_msg = bus2.recv() + # we need to reenable the garbage collector again gc.enable() # Check consecutive messages are spaced properly in time and have diff --git a/test/test_message_sync.py b/test/test_message_sync.py index dafa41036..ec21a0660 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -25,6 +25,7 @@ def inc(value): + """Makes the test boundaries give some more space when run on the CI server.""" if IS_CI: return value * 1.5 else: @@ -40,9 +41,11 @@ def __init__(self, *args, **kwargs): ComparingMessagesTestCase.__init__(self) def setup_method(self, _): + # disabling the garbage collector makes the time readings more reliable gc.disable() def teardown_method(self, _): + # we need to reenable the garbage collector again gc.enable() @pytest.mark.timeout(inc(0.2)) @@ -84,7 +87,8 @@ def test_skip(self): after = time() took = after - before - # the handling of the messages itself also take time: ~0.001 s/msg on my laptop + # the handling of the messages itself also takes some time: + # ~0.001 s/message on a ThinkPad T560 laptop (Ubuntu 18.04, i5-6200U) assert 0 < took < inc(len(messages) * (0.005 + 0.003)), "took: {}s".format(took) self.assertMessagesEqual(messages, collected) From 200554fa22b4c420fee21d76a95cfe40a6d3344b Mon Sep 17 00:00:00 2001 From: Karl Date: Wed, 8 May 2019 09:37:56 -0700 Subject: [PATCH 0128/1235] Various spelling corrections in the generated docs Various trivial spelling changes throughout the generated docs: * 'farly' -> 'fairly' * 'throughly' -> 'thoroughly' * 'actally' -> 'actually' * 'tunred' -> 'turned' * 'seperator' -> 'separator' * 'busses' -> 'buses' --- README.rst | 2 +- can/interfaces/virtual.py | 2 +- can/io/csv.py | 2 +- can/io/printer.py | 2 +- can/io/sqlite.py | 6 +++--- can/notifier.py | 4 ++-- doc/interfaces/ixxat.rst | 2 +- doc/interfaces/nican.rst | 2 +- test/back2back_test.py | 6 +++--- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 12b917355..360d420b2 100644 --- a/README.rst +++ b/README.rst @@ -51,7 +51,7 @@ Features - many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), CSV, SQLite and Canutils log - efficient in-kernel or in-hardware filtering of messages on supported interfaces - bus configuration reading from file or environment variables -- CLI tools for working with CAN busses (see the `docs `__) +- CLI tools for working with CAN buses (see the `docs `__) - more diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 163579387..6f24c73f2 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -121,7 +121,7 @@ def _detect_available_configs(): .. note:: This method will run into problems if thousands of - autodetected busses are used at once. + autodetected buses are used at once. """ with channels_lock: diff --git a/can/io/csv.py b/can/io/csv.py index 37ca0e5a7..92f841f8f 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -78,7 +78,7 @@ class CSVReader(BaseIOHandler): format as described there. Assumes that there is a header and thus skips the first line. - Any line seperator is accepted. + Any line separator is accepted. """ def __init__(self, file): diff --git a/can/io/printer.py b/can/io/printer.py index cb9c4581d..6cc01f69b 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -17,7 +17,7 @@ class Printer(BaseIOHandler, Listener): """ The Printer class is a subclass of :class:`~can.Listener` which simply prints - any messages it receives to the terminal (stdout). A message is tunred into a + any messages it receives to the terminal (stdout). A message is turned into a string using :meth:`~can.Message.__str__`. :attr bool write_to_file: `True` iff this instance prints to a file instead of diff --git a/can/io/sqlite.py b/can/io/sqlite.py index a12023a6f..21cd2aafc 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -20,7 +20,7 @@ log = logging.getLogger('can.io.sqlite') -if sys.version_info.major < 3: +if sys.version_info.major < 3: # legacy fallback for Python 2 memoryview = buffer @@ -58,7 +58,7 @@ def __iter__(self): for frame_data in self._cursor.execute("SELECT * FROM {}".format(self.table_name)): yield SqliteReader._assemble_message(frame_data) - @staticmethod + @staticmethod def _assemble_message(frame_data): timestamp, can_id, is_extended, is_remote, is_error, dlc, data = frame_data return Message( @@ -103,7 +103,7 @@ class SqliteWriter(BaseIOHandler, BufferedReader): :meth:`~can.SqliteWriter.stop()` may take a while. :attr str table_name: the name of the database table used for storing the messages - :attr int num_frames: the number of frames actally writtem to the database, this + :attr int num_frames: the number of frames actually written to the database, this excludes messages that are still buffered :attr float last_write: the last time a message war actually written to the database, as given by ``time.time()`` diff --git a/can/notifier.py b/can/notifier.py index 7c5f2820d..737ec978e 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -20,7 +20,7 @@ class Notifier(object): def __init__(self, bus, listeners, timeout=1.0, loop=None): """Manages the distribution of :class:`can.Message` instances to listeners. - Supports multiple busses and listeners. + Supports multiple buses and listeners. .. Note:: @@ -127,7 +127,7 @@ def _on_error(self, exc): listener.on_error(exc) def add_listener(self, listener): - """Add new Listener to the notification list. + """Add new Listener to the notification list. If it is already present, it will be called two times each time a message arrives. diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index ff52776b8..9ab79ffcf 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -46,7 +46,7 @@ module, while the following parameters are optional and are interpreted by IXXAT Internals --------- -The IXXAT :class:`~can.BusABC` object is a farly straightforward interface +The IXXAT :class:`~can.BusABC` object is a fairly straightforward interface to the IXXAT VCI library. It can open a specific device ID or use the first one found. diff --git a/doc/interfaces/nican.rst b/doc/interfaces/nican.rst index ec4e82cb6..b2214371f 100644 --- a/doc/interfaces/nican.rst +++ b/doc/interfaces/nican.rst @@ -12,7 +12,7 @@ This interface adds support for CAN controllers by `National Instruments`_. .. warning:: - CAN filtering has not been tested throughly and may not work as expected. + CAN filtering has not been tested thoroughly and may not work as expected. Bus diff --git a/test/back2back_test.py b/test/back2back_test.py index 800ffce9e..4062d462a 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -2,7 +2,7 @@ # coding: utf-8 """ -This module tests two virtual busses attached to each other. +This module tests two virtual buses attached to each other. """ from __future__ import absolute_import, print_function @@ -10,7 +10,7 @@ import sys import unittest from time import sleep -from multiprocessing.dummy import Pool as ThreadPool +from multiprocessing.dummy import Pool as ThreadPool import pytest import random @@ -90,7 +90,7 @@ def test_timestamp(self): recv_msg2 = self.bus1.recv(self.TIMEOUT) delta_time = recv_msg2.timestamp - recv_msg1.timestamp self.assertTrue(1.75 <= delta_time <= 2.25, - 'Time difference should have been 2s +/- 250ms.' + 'Time difference should have been 2s +/- 250ms.' 'But measured {}'.format(delta_time)) def test_standard_message(self): From a9b7659f538861606d9b26debcc4b6460c87ef57 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 8 May 2019 21:16:05 +0200 Subject: [PATCH 0129/1235] make coverage collection slightly faster --- .appveyor.yml | 2 +- .travis.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index b36a94213..f65386f3e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -28,4 +28,4 @@ test_script: - "pytest" # uplad coverage reports - - "codecov" + - "codecov -X gcov" diff --git a/.travis.yml b/.travis.yml index 38a4b02e6..fd5679532 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,7 +74,7 @@ script: # preserve the error code RETURN_CODE=$? # Upload the coverage to codecov.io - codecov + codecov -X gcov # set error code (exit $RETURN_CODE); fi From 71407f99d6643e3d5044d7aa13bb3bffa16be009 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 8 May 2019 21:23:37 +0200 Subject: [PATCH 0130/1235] Fix warning in usb2can This PR removes [this `DeprecationWarning`](https://travis-ci.org/hardbyte/python-can/jobs/529939236#L473): > invalid escape sequence \c Is this correct to to? It now is like [this example](https://docs.microsoft.com/en-us/windows/desktop/wmisdk/swbemlocator-connectserver#examples). --- can/interfaces/usb2can/serial_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index b47396876..fbfe60a6e 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -39,7 +39,7 @@ def find_serial_devices(serial_matcher="ED"): :rtype: List[str] """ objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") - objSWbemServices = objWMIService.ConnectServer(".", "root\cimv2") + objSWbemServices = objWMIService.ConnectServer(".", "root\\cimv2") items = objSWbemServices.ExecQuery("SELECT * FROM Win32_USBControllerDevice") ids = (item.Dependent.strip('"')[-8:] for item in items) return [e for e in ids if e.startswith(serial_matcher)] From 96df597a17fdcea896eb5dfdd6b2f2332a6b2ded Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 8 May 2019 21:30:05 +0200 Subject: [PATCH 0131/1235] Fix deprecated constructor call This would fail in version 4.0 of this library and already prints a warning. --- can/interfaces/systec/ucanbus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 969db7d53..9731398bd 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -140,7 +140,7 @@ def _recv_internal(self, timeout): msg = Message(timestamp=float(message[0].time) / 1000.0, is_remote_frame=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_RTR), - extended_id=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_EXT), + is_extended_id=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_EXT), arbitration_id=message[0].id, dlc=len(message[0].data), data=message[0].data) From 93ad7238633bf3f36373e094a89c325b7cc1c401 Mon Sep 17 00:00:00 2001 From: shedfly Date: Fri, 10 May 2019 12:24:49 +0000 Subject: [PATCH 0132/1235] implementing 'send' --- .../usb_can_analyzer/usb_can_analyzer.py | 48 ++++++++----------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py index b1fd95ffa..73def806d 100644 --- a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py +++ b/can/interfaces/usb_can_analyzer/usb_can_analyzer.py @@ -171,35 +171,32 @@ def send(self, msg, timeout=None): :param can.Message msg: Message to send. - .. note:: Flags like ``extended_id``, ``is_remote_frame`` and - ``is_error_frame`` will be ignored. - - .. note:: If the timestamp is a float value it will be converted - to an integer. - :param timeout: This parameter will be ignored. The timeout value of the channel is used instead. - """ - try: - timestamp = struct.pack(' Date: Fri, 10 May 2019 12:31:34 +0000 Subject: [PATCH 0133/1235] changing extras_require install option name to match interface name and adding pyserial --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a4ae7b405..388b96747 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ extras_require = { 'serial': ['pyserial~=3.0'], 'neovi': ['python-ics>=2.12'], - 'crccheck': ['crccheck>=0.6'] + 'usb_can_analyzer': ['crccheck>=0.6','pyserial>=3.0'] } tests_require = [ From beb4d36c3a12aeb7d735faaa34adc1df055c1c95 Mon Sep 17 00:00:00 2001 From: karl ding Date: Sat, 11 May 2019 15:10:35 -0700 Subject: [PATCH 0134/1235] Fix stuct packing in build_bcm_header on 32-bit (#573) * Fix stuct packing in build_bcm_header on 32-bit Previously build_bcm_header relied upon adding a "0q" at the end of the struct format string in order to force a 8-byte wide alignment on the struct, in order to interop with the structure defined in the Linux headers: struct bcm_msg_head { __u32 opcode; __u32 flags; __u32 count; struct bcm_timeval ival1, ival2; canid_t can_id; __u32 nframes; struct can_frame frames[0]; }; The Python code assumed that alignof(long long) == 8 bytes in order to accomplish this. However, on certain 32-bit platforms, this assumption is not true, and alignof(long long) == 4. As the struct module does not provide logic to derive alignments of its types, this changes the packing logic to use ctypes. This exposes the alignment of the struct members, allowing us to determine whether padding bytes are necessary. Fixes #470 * Call BCM factory upon module initialization In order to prevent the BCM factory method from being called each time a BCM header struct is created, we create the class when the module gets created. * Add SocketCAN interface tests for BCM factory Add a SocketCAN interface test file with tests for validating the BcmMsgHead class created by bcm_header_factory on various platforms. * Clean up comments referencing packing via struct Clean up the old comment in build_bcm_header referencing the old packing code that called into Python's struct library. This now uses ctypes, so and the class is created via the bcm_header_factory factory function, so we move the comment there instead. --- can/interfaces/socketcan/socketcan.py | 122 +++++++++++--- test/test_socketcan.py | 234 ++++++++++++++++++++++++++ 2 files changed, 335 insertions(+), 21 deletions(-) create mode 100644 test/test_socketcan.py diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 566ddfcb5..633c87b22 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -67,6 +67,84 @@ def get_addr(sock, channel): return struct.pack("HiLL", AF_CAN, idx, 0, 0) +# Setup BCM struct +def bcm_header_factory(fields, alignment=8): + curr_stride = 0 + results = [] + pad_index = 0 + for field in fields: + field_alignment = ctypes.alignment(field[1]) + field_size = ctypes.sizeof(field[1]) + + # If the current stride index isn't a multiple of the alignment + # requirements of this field, then we must add padding bytes until we + # are aligned + while curr_stride % field_alignment != 0: + results.append(("pad_{}".format(pad_index), ctypes.c_uint8)) + pad_index += 1 + curr_stride += 1 + + # Now can it fit? + # Example: If this is 8 bytes and the type requires 4 bytes alignment + # then we can only fit when we're starting at 0. Otherwise, we will + # split across 2 strides. + # + # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | + results.append(field) + curr_stride += field_size + + # Add trailing padding to align to a multiple of the largest scalar member + # in the structure + while curr_stride % alignment != 0: + results.append(("pad_{}".format(pad_index), ctypes.c_uint8)) + pad_index += 1 + curr_stride += 1 + + return type("BcmMsgHead", (ctypes.Structure,), {"_fields_": results}) + +# The fields definition is taken from the C struct definitions in +# +# +# struct bcm_timeval { +# long tv_sec; +# long tv_usec; +# }; +# +# /** +# * struct bcm_msg_head - head of messages to/from the broadcast manager +# * @opcode: opcode, see enum below. +# * @flags: special flags, see below. +# * @count: number of frames to send before changing interval. +# * @ival1: interval for the first @count frames. +# * @ival2: interval for the following frames. +# * @can_id: CAN ID of frames to be sent or received. +# * @nframes: number of frames appended to the message head. +# * @frames: array of CAN frames. +# */ +# struct bcm_msg_head { +# __u32 opcode; +# __u32 flags; +# __u32 count; +# struct bcm_timeval ival1, ival2; +# canid_t can_id; +# __u32 nframes; +# struct can_frame frames[0]; +# }; +BcmMsgHead = bcm_header_factory( + fields=[ + ("opcode", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("count", ctypes.c_uint32), + ("ival1_tv_sec", ctypes.c_long), + ("ival1_tv_usec", ctypes.c_long), + ("ival2_tv_sec", ctypes.c_long), + ("ival2_tv_usec", ctypes.c_long), + ("can_id", ctypes.c_uint32), + ("nframes", ctypes.c_uint32), + ] +) + + # struct module defines a binary packing format: # https://docs.python.org/3/library/struct.html#struct-format-strings # The 32bit can id is directly followed by the 8bit data link count @@ -118,27 +196,29 @@ def build_can_frame(msg): return CAN_FRAME_HEADER_STRUCT.pack(can_id, msg.dlc, flags) + data -def build_bcm_header(opcode, flags, count, ival1_seconds, ival1_usec, ival2_seconds, ival2_usec, can_id, nframes): - # == Must use native not standard types for packing == - # struct bcm_msg_head { - # __u32 opcode; -> I - # __u32 flags; -> I - # __u32 count; -> I - # struct timeval ival1, ival2; -> llll ... - # canid_t can_id; -> I - # __u32 nframes; -> I - bcm_cmd_msg_fmt = "@3I4l2I0q" - - return struct.pack(bcm_cmd_msg_fmt, - opcode, - flags, - count, - ival1_seconds, - ival1_usec, - ival2_seconds, - ival2_usec, - can_id, - nframes) +def build_bcm_header( + opcode, + flags, + count, + ival1_seconds, + ival1_usec, + ival2_seconds, + ival2_usec, + can_id, + nframes, +): + result = BcmMsgHead( + opcode=opcode, + flags=flags, + count=count, + ival1_tv_sec=ival1_seconds, + ival1_tv_usec=ival1_usec, + ival2_tv_sec=ival2_seconds, + ival2_tv_usec=ival2_usec, + can_id=can_id, + nframes=nframes, + ) + return ctypes.string_at(ctypes.addressof(result), ctypes.sizeof(result)) def build_bcm_tx_delete_header(can_id, flags): diff --git a/test/test_socketcan.py b/test/test_socketcan.py new file mode 100644 index 000000000..f010a6372 --- /dev/null +++ b/test/test_socketcan.py @@ -0,0 +1,234 @@ +""" +Test functions in `can.interfaces.socketcan.socketcan`. +""" +import unittest + +try: + from unittest.mock import Mock + from unittest.mock import patch + from unittest.mock import call +except ImportError: + from mock import Mock + from mock import patch + from mock import call + +import ctypes + +from can.interfaces.socketcan.socketcan import bcm_header_factory + + +class SocketCANTest(unittest.TestCase): + def setUp(self): + self._ctypes_sizeof = ctypes.sizeof + self._ctypes_alignment = ctypes.alignment + + @patch("ctypes.sizeof") + @patch("ctypes.alignment") + def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_4( + self, ctypes_sizeof, ctypes_alignment + ): + """This tests a 32-bit platform (ex. Debian Stretch on i386), where: + + * sizeof(long) == 4 + * sizeof(long long) == 8 + * alignof(long) == 4 + * alignof(long long) == 4 + """ + + def side_effect_ctypes_sizeof(value): + type_to_size = { + ctypes.c_longlong: 8, + ctypes.c_long: 4, + ctypes.c_uint8: 1, + ctypes.c_uint16: 2, + ctypes.c_uint32: 4, + ctypes.c_uint64: 8, + } + return type_to_size[value] + + def side_effect_ctypes_alignment(value): + type_to_alignment = { + ctypes.c_longlong: 4, + ctypes.c_long: 4, + ctypes.c_uint8: 1, + ctypes.c_uint16: 2, + ctypes.c_uint32: 4, + ctypes.c_uint64: 4, + } + return type_to_alignment[value] + + ctypes_sizeof.side_effect = side_effect_ctypes_sizeof + ctypes_alignment.side_effect = side_effect_ctypes_alignment + + fields = [ + ("opcode", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("count", ctypes.c_uint32), + ("ival1_tv_sec", ctypes.c_long), + ("ival1_tv_usec", ctypes.c_long), + ("ival2_tv_sec", ctypes.c_long), + ("ival2_tv_usec", ctypes.c_long), + ("can_id", ctypes.c_uint32), + ("nframes", ctypes.c_uint32), + ] + BcmMsgHead = bcm_header_factory(fields) + + expected_fields = [ + ("opcode", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("count", ctypes.c_uint32), + ("ival1_tv_sec", ctypes.c_long), + ("ival1_tv_usec", ctypes.c_long), + ("ival2_tv_sec", ctypes.c_long), + ("ival2_tv_usec", ctypes.c_long), + ("can_id", ctypes.c_uint32), + ("nframes", ctypes.c_uint32), + # We expect 4 bytes of padding + ("pad_0", ctypes.c_uint8), + ("pad_1", ctypes.c_uint8), + ("pad_2", ctypes.c_uint8), + ("pad_3", ctypes.c_uint8), + ] + self.assertEqual(expected_fields, BcmMsgHead._fields_) + + @patch("ctypes.sizeof") + @patch("ctypes.alignment") + def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_8( + self, ctypes_sizeof, ctypes_alignment + ): + """This tests a 32-bit platform (ex. Raspbian Stretch on armv7l), where: + + * sizeof(long) == 4 + * sizeof(long long) == 8 + * alignof(long) == 4 + * alignof(long long) == 8 + """ + + def side_effect_ctypes_sizeof(value): + type_to_size = { + ctypes.c_longlong: 8, + ctypes.c_long: 4, + ctypes.c_uint8: 1, + ctypes.c_uint16: 2, + ctypes.c_uint32: 4, + ctypes.c_uint64: 8, + } + return type_to_size[value] + + def side_effect_ctypes_alignment(value): + type_to_alignment = { + ctypes.c_longlong: 8, + ctypes.c_long: 4, + ctypes.c_uint8: 1, + ctypes.c_uint16: 2, + ctypes.c_uint32: 4, + ctypes.c_uint64: 8, + } + return type_to_alignment[value] + + ctypes_sizeof.side_effect = side_effect_ctypes_sizeof + ctypes_alignment.side_effect = side_effect_ctypes_alignment + + fields = [ + ("opcode", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("count", ctypes.c_uint32), + ("ival1_tv_sec", ctypes.c_long), + ("ival1_tv_usec", ctypes.c_long), + ("ival2_tv_sec", ctypes.c_long), + ("ival2_tv_usec", ctypes.c_long), + ("can_id", ctypes.c_uint32), + ("nframes", ctypes.c_uint32), + ] + BcmMsgHead = bcm_header_factory(fields) + + expected_fields = [ + ("opcode", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("count", ctypes.c_uint32), + ("ival1_tv_sec", ctypes.c_long), + ("ival1_tv_usec", ctypes.c_long), + ("ival2_tv_sec", ctypes.c_long), + ("ival2_tv_usec", ctypes.c_long), + ("can_id", ctypes.c_uint32), + ("nframes", ctypes.c_uint32), + # We expect 4 bytes of padding + ("pad_0", ctypes.c_uint8), + ("pad_1", ctypes.c_uint8), + ("pad_2", ctypes.c_uint8), + ("pad_3", ctypes.c_uint8), + ] + self.assertEqual(expected_fields, BcmMsgHead._fields_) + + @patch("ctypes.sizeof") + @patch("ctypes.alignment") + def test_bcm_header_factory_64_bit_sizeof_long_4_alignof_long_4( + self, ctypes_sizeof, ctypes_alignment + ): + """This tests a 64-bit platform (ex. Ubuntu 18.04 on x86_64), where: + + * sizeof(long) == 8 + * sizeof(long long) == 8 + * alignof(long) == 8 + * alignof(long long) == 8 + """ + + def side_effect_ctypes_sizeof(value): + type_to_size = { + ctypes.c_longlong: 8, + ctypes.c_long: 8, + ctypes.c_uint8: 1, + ctypes.c_uint16: 2, + ctypes.c_uint32: 4, + ctypes.c_uint64: 8, + } + return type_to_size[value] + + def side_effect_ctypes_alignment(value): + type_to_alignment = { + ctypes.c_longlong: 8, + ctypes.c_long: 8, + ctypes.c_uint8: 1, + ctypes.c_uint16: 2, + ctypes.c_uint32: 4, + ctypes.c_uint64: 8, + } + return type_to_alignment[value] + + ctypes_sizeof.side_effect = side_effect_ctypes_sizeof + ctypes_alignment.side_effect = side_effect_ctypes_alignment + + fields = [ + ("opcode", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("count", ctypes.c_uint32), + ("ival1_tv_sec", ctypes.c_long), + ("ival1_tv_usec", ctypes.c_long), + ("ival2_tv_sec", ctypes.c_long), + ("ival2_tv_usec", ctypes.c_long), + ("can_id", ctypes.c_uint32), + ("nframes", ctypes.c_uint32), + ] + BcmMsgHead = bcm_header_factory(fields) + + expected_fields = [ + ("opcode", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("count", ctypes.c_uint32), + # We expect 4 bytes of padding + ("pad_0", ctypes.c_uint8), + ("pad_1", ctypes.c_uint8), + ("pad_2", ctypes.c_uint8), + ("pad_3", ctypes.c_uint8), + ("ival1_tv_sec", ctypes.c_long), + ("ival1_tv_usec", ctypes.c_long), + ("ival2_tv_sec", ctypes.c_long), + ("ival2_tv_usec", ctypes.c_long), + ("can_id", ctypes.c_uint32), + ("nframes", ctypes.c_uint32), + ] + self.assertEqual(expected_fields, BcmMsgHead._fields_) + + +if __name__ == "__main__": + unittest.main() From 8c63278f50c5f9a139b68c8a70991dd64dd66a2b Mon Sep 17 00:00:00 2001 From: karl ding Date: Sat, 11 May 2019 18:19:14 -0700 Subject: [PATCH 0135/1235] Remove unused SocketCAN constants (#575) These constants are no longer used in the code and can be cleaned up Fixes #574 --- can/interfaces/socketcan/constants.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index 3228d4f4e..b56eaae64 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -4,8 +4,6 @@ Defines shared CAN constants. """ -canMSG_EXT = 0x0004 - CAN_ERR_FLAG = 0x20000000 CAN_RTR_FLAG = 0x40000000 CAN_EFF_FLAG = 0x80000000 @@ -14,10 +12,6 @@ CAN_BCM_TX_SETUP = 1 CAN_BCM_TX_DELETE = 2 -CAN_BCM_TX_EXPIRED = 9 - -CAN_BCM_RX_TIMEOUT = 11 - # BCM flags SETTIMER = 0x0001 STARTTIMER = 0x0002 @@ -57,32 +51,11 @@ SIOCGSTAMP = 0x8906 EXTFLG = 0x0004 -SKT_ERRFLG = 0x0001 -SKT_RTRFLG = 0x0002 - CANFD_BRS = 0x01 CANFD_ESI = 0x02 CANFD_MTU = 72 -PYCAN_ERRFLG = 0x0020 -PYCAN_STDFLG = 0x0002 -PYCAN_RTRFLG = 0x0001 - -ID_TYPE_EXTENDED = True -ID_TYPE_STANDARD = False - -ID_TYPE_29_BIT = ID_TYPE_EXTENDED -ID_TYPE_11_BIT = ID_TYPE_STANDARD - -REMOTE_FRAME = True -DATA_FRAME = False -WAKEUP_MSG = True -ERROR_FRAME = True - -DRIVER_MODE_SILENT = False -DRIVER_MODE_NORMAL = (not DRIVER_MODE_SILENT) - STD_ACCEPTANCE_MASK_ALL_BITS = (2**11 - 1) MAX_11_BIT_ID = STD_ACCEPTANCE_MASK_ALL_BITS From 5d40159aeba7e31926ac103dc85d07eb4a51e88b Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Sun, 12 May 2019 08:37:57 +0200 Subject: [PATCH 0136/1235] Introduced CanTimeoutError. Win32 and cygwin will both load "vcimpl.dll". Added comments. --- can/exceptions.py | 14 +++++---- can/interfaces/ixxat/canlib.py | 46 ++++++++++-------------------- can/interfaces/ixxat/exceptions.py | 2 +- test/test_interface_ixxat.py | 18 ++++++------ 4 files changed, 33 insertions(+), 47 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index d43a27648..a91fc7a1b 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -1,7 +1,5 @@ """ Exception classes. - -Copyright (c) Marcel Kanter """ class CanError(Exception): @@ -22,12 +20,18 @@ class CanInitializationError(CanError): """ Indicates an error related to the initialization. Examples for situations when this exception may occur: - Try to open a non-existent device and/or channel - - Try to use a invalid setting, which is ok by value, but not ok for the interface + - Try to use an invalid setting, which is ok by value, but not ok for the interface """ pass class CanOperationError(CanError): - """ Indicates an error while operation. + """ Indicates an error while in operation. + """ + pass + + +class CanTimeoutError(CanError): + """ Indicates a timeout of an operation. """ - pass \ No newline at end of file + pass diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 61dae6871..47d88a85b 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,11 +1,6 @@ -# coding: utf-8 - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems -Copyright (C) 2016 Giuseppe Corbelli -Copyright (C) 2019 Marcel Kanter - TODO: We could implement this interface such that setting other filters could work when the initial filters were set to zero using the software fallback. Or could the software filters even be changed @@ -44,12 +39,8 @@ # main ctypes instance _canlib = None -if sys.platform == "win32": - try: - _canlib = CLibrary("vcinpl") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) -elif sys.platform == "cygwin": +# TODO: Use ECI driver for linux +if sys.platform == "win32" or sys.platform == "cygwin": try: _canlib = CLibrary("vcinpl.dll") except Exception as e: @@ -489,9 +480,14 @@ def _recv_internal(self, timeout): def send(self, msg, timeout=None): """ Sends a message on the bus. The interface may buffer the message. - returns True on success or when timeout is None - returns False on timeout (when timeout is not None) - raises CanOperationError + + :param can.Message msg: + The message to send. + :param float timeout: + Timeout after some time. + :raise: + :class:CanTimeoutError + :class:CanOperationError """ # This system is not designed to be very efficient message = structures.CANMSG() @@ -505,21 +501,10 @@ def send(self, msg, timeout=None): adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) ctypes.memmove(message.abData, adapter, len(msg.data)) - try: - if timeout: - _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) - else: - _canlib.canChannelPostMessage(self._channel_handle, message) - except VCITimeout: - # if the user wanted an timeout, the timeout in the library is probably no error - if timeout: - return False - else: - raise CanOperationError("Timeout in library call.") - except: - raise CanOperationError("Send failed.") - - return True + if timeout: + _canlib.canChannelSendMessage(self._channel_handle, int(timeout * 1000), message) + else: + _canlib.canChannelPostMessage(self._channel_handle, message) def _send_periodic_internal(self, msg, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" @@ -551,8 +536,7 @@ def set_filters(self, can_filers=None): self.__set_filters_has_been_called = True -class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): +class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" def __init__(self, scheduler, msg, period, duration, resolution): diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index c9a83dd4a..17d51df40 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -12,7 +12,7 @@ __all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] -class VCITimeout(CanError): +class VCITimeout(CanTimeoutError): """ Wraps the VCI_E_TIMEOUT error """ pass diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index a3ab494fb..e57eab368 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -1,7 +1,5 @@ """ Unittest for ixxat interface. - -Copyright (C) 2019 Marcel Kanter """ import unittest @@ -15,7 +13,7 @@ class SoftwareTestCase(unittest.TestCase): """ def setUp(self): try: - bus = can.Bus(interface = 'ixxat', channel = 0) + bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: raise(unittest.SkipTest()) @@ -26,15 +24,15 @@ def tearDown(self): def test_bus_creation(self): # channel must be >= 0 with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = -1) + bus = can.Bus(interface="ixxat", channel=-1) # rxFifoSize must be > 0 with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = 0, rxFifoSize = 0) + bus = can.Bus(interface="ixxat", channel=0, rxFifoSize=0) # txFifoSize must be > 0 with self.assertRaises(ValueError): - bus = can.Bus(interface = 'ixxat', channel = 0, txFifoSize = 0) + bus = can.Bus(interface="ixxat", channel=0, txFifoSize=0) class HardwareTestCase(unittest.TestCase): @@ -43,7 +41,7 @@ class HardwareTestCase(unittest.TestCase): """ def setUp(self): try: - bus = can.Bus(interface = 'ixxat', channel = 0) + bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: raise(unittest.SkipTest()) @@ -54,11 +52,11 @@ def tearDown(self): def test_bus_creation(self): # non-existent channel -> use arbitrary high value with self.assertRaises(CanInitializationError): - bus = can.Bus(interface = 'ixxat', channel = 0xFFFFFFFFF) + bus = can.Bus(interface="ixxat", channel=0xFFFF) def test_send_after_shutdown(self): - bus = can.Bus(interface = 'ixxat', channel = 0) - msg = can.Message(arbitration_id = 0x3FF, dlc = 0) + bus = can.Bus(interface="ixxat", channel=0) + msg = can.Message(arbitration_id=0x3FF, dlc=0) bus.shutdown() with self.assertRaises(CanOperationError): bus.send(msg) From f46dc60bf0847e2e6471c58a3e3ba3147b4df3ef Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 12 May 2019 12:30:09 +1000 Subject: [PATCH 0137/1235] Update changelog for release3.2 --- CHANGELOG.txt | 74 +++++++++++++++++++++++++++++++++++++------------ doc/history.rst | 2 +- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index e4c71508d..c7f90b080 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,20 +1,60 @@ Version 3.2.0 ==== -Support has been removed for Python 3.4 Major features -------------- -- FD support added for Pcan in PR #537 +* FD support added for Pcan by @bmeisels with input from + @markupsp, @christiansandberg & @felixdivo in PR #537 Other notable changes --------------------- -- This release should automatically be published to PyPi by travis. #535 -- BusState is now an enum. #533 -- A guide has been added for new io formats. #548 -- Finish moving from nose to pytest #550 +* #532 Support has been removed for Python 3.4 +* #533 BusState is now an enum. +* #535 This release should automatically be published to PyPi by travis. +* #548 A guide has been added for new io formats. +* #550 Finish moving from nose to pytest. +* #558 Fix installation on Windows. +* #561 Tests for MessageSync added. + +General fixes, cleanup and docs changes can be found on the GitHub milestone +https://github.com/hardbyte/python-can/milestone/7?closed=1 + +Pulls: #522, #526, #527, #536, #540, #546, #547, #548, #533, #559, #569, #571, #572, #575 + +Backend Specific Changes +------------------------ + +pcan +~~~~ + +* FD + +slcan +~~~~ + +* ability to set custom can speed instead of using predefined speed values. #553 + +socketcan +~~~~ + +* Bug fix to properly support 32bit systems. #573 + +usb2can +~~~~ + +* slightly better error handling +* multiple serial devices can be found +* support for the `_detect_available_configs()` API + +Pulls #511, #535 + +vector +~~~~ + +* handle `app_name`. #525 Version 3.1.1 ==== @@ -31,20 +71,20 @@ Two new interfaces this release: Other notable changes --------------------- -- #477 The kvaser interface now supports bus statistics via a custom bus method. -- #434 neovi now supports receiving own messages -- #490 Adding option to override the neovi library name -- #488 Allow simultaneous access to IXXAT cards -- #447 Improvements to serial interface: +* #477 The kvaser interface now supports bus statistics via a custom bus method. +* #434 neovi now supports receiving own messages +* #490 Adding option to override the neovi library name +* #488 Allow simultaneous access to IXXAT cards +* #447 Improvements to serial interface: * to allow receiving partial messages * to fix issue with DLC of remote frames * addition of unit tests -- #497 Small API changes to `Message` and added unit tests -- #471 Fix CAN FD issue in kvaser interface -- #462 Fix `Notifier` issue with asyncio -- #481 Fix PCAN support on OSX -- #455 Fix to `Message` initializer -- Small bugfixes and improvements +* #497 Small API changes to `Message` and added unit tests +* #471 Fix CAN FD issue in kvaser interface +* #462 Fix `Notifier` issue with asyncio +* #481 Fix PCAN support on OSX +* #455 Fix to `Message` initializer +* Small bugfixes and improvements Version 3.1.0 ==== diff --git a/doc/history.rst b/doc/history.rst index 54193aff2..caed67baa 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -55,7 +55,7 @@ Python natively supports the CAN protocol from version 3.3 on, if running on Lin Python version Feature Link ============== ============================================================== ==== 3.3 Initial SocketCAN support `Docs `__ -3.4 Broadcast Banagement (BCM) commands are natively supported `Docs `__ +3.4 Broadcast Management (BCM) commands are natively supported `Docs `__ 3.5 CAN FD support `Docs `__ 3.7 Support for CAN ISO-TP `Docs `__ ============== ============================================================== ==== From f08777034495416650045b9ce87edfa9768924b7 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 12 May 2019 12:40:21 +1000 Subject: [PATCH 0138/1235] Update version to alpha2 for release3.2 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index cb4b6d280..099963a3e 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.2.0-a0" +__version__ = "3.2.0-a1" log = logging.getLogger('can') From c42b687d09b3a491e0c6b4072ed2398bf9002429 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 12 May 2019 19:15:03 +1000 Subject: [PATCH 0139/1235] Use travis stages instead of matrix (#577) --- .travis.yml | 129 ++++++++++++++++++++++++++---------------------- can/__init__.py | 2 +- 2 files changed, 70 insertions(+), 61 deletions(-) diff --git a/.travis.yml b/.travis.yml index fd5679532..b45e17d95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ language: python +# Linux setup +dist: xenial + +cache: + directories: + - "$HOME/.cache/pip" + python: # CPython; versions pre-2.7 and 3.0-3.5 have reached EOL - "2.7" @@ -10,81 +17,83 @@ python: # PyPy: - pypy # Python 2.7 - pypy3.5 # Python 3.5 + - pypy3 -os: - - linux # Linux is officially supported and we test the library under - # many different Python verions (see "python: ..." above) +env: -# - osx # OSX + Python is not officially supported by Travis CI as of Feb. 2018 - # nevertheless, "nightly" and some "*-dev" versions seem to work, so we - # include them explicitly below (see "matrix: include: ..." below). - # They only seem to work with the xcode8.3 image, and not the newer ones. - # Thus we will leave this in, until it breaks one day, at which point we - # will probably reomve testing on OSX if it is not supported then. - # See #385 on Github. -# - windows # Windows is not supported at all by Travis CI as of Feb. 2018 +install: + - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi + - travis_retry pip install .[test] -# Linux setup -dist: xenial +script: + - | + # Run the tests + python setup.py test + # preserve the error code + RETURN_CODE=$? + # Upload the coverage to codecov.io + codecov -X gcov + # set error code + (exit $RETURN_CODE); + + +jobs: + allow_failures: + # we allow all dev & nightly builds to fail, since these python versions might + # still be very unstable + - python: 3.8-dev + - python: nightly -matrix: include: - # building the docs - - python: "3.7" - env: BUILD_ONLY_DOCS=TRUE + # Note no matrix support when using stages. + # Stages with the same name get run in parallel. + # Jobs within a stage can also be named. + + # Unit Testing Stage + # testing socketcan on Trusty & Python 3.6, since it is not available on Xenial - - os: linux + - stage: test + name: Socketcan + os: linux dist: trusty python: "3.6" sudo: required env: TEST_SOCKETCAN=TRUE - # testing on macOS; see "os: ..." above - - os: osx + + # testing on OSX + - stage: test + os: osx osx_image: xcode8.3 python: 3.6-dev - - os: osx + - stage: test + os: osx osx_image: xcode8.3 python: 3.7-dev - - os: osx + - stage: test + os: osx osx_image: xcode8.3 python: nightly - allow_failures: - # we allow all dev & nighly builds to fail, since these python versions might - # still be very unstable - - python: 3.8-dev - - python: nightly - -install: - - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - - if [[ "$BUILD_ONLY_DOCS" ]]; then travis_retry pip install -r doc/doc-requirements.txt; fi - - travis_retry pip install .[test] - -script: - - | - if [[ "$BUILD_ONLY_DOCS" ]]; then - # Build the docs with Sphinx - # -a Write all files - # -n nitpicky - python -m sphinx -an doc build - else - # Run the tests - python setup.py test - # preserve the error code - RETURN_CODE=$? - # Upload the coverage to codecov.io - codecov -X gcov - # set error code - (exit $RETURN_CODE); - fi - -# Have travis deploy tagged commits to PyPi -deploy: - provider: pypi - user: hardbyte - password: - secure: oQ9XpEkcilkZgKp+rKvPb2J1GrZe2ZvtOq/IjzCpiA8NeWixl/ai3BkPrLbd8t1wNIFoGwx7IQ7zxWL79aPYeG6XrljEomv3g45NR6dkQewUH+dQFlnT75Rm96Ycxvme0w1+71vM4PqxIuzyXUrF2n7JjC0XCCxHdTuYmPGbxVO1fOsE5R5b9inAbpEUtJuWz5AIrDEZ0OgoQpLSC8fLwbymTThX3JZ5GBLpRScVvLazjIYfRkZxvCqQ4mp1UNTdoMzekxsvxOOcEW6+j3fQO+Q/8uvMksKP0RgT8HE69oeYOeVic4Q4wGqORw+ur4A56NvBqVKtizVLCzzEG9ZfoSDy7ryvGWGZykkh8HX0PFQAEykC3iYihHK8ZFz5bEqRMegTmuRYZwPsel61wVd5posxnQkGm0syIoJNKuuRc5sUK+E3GviYcT8NntdR+4WBrvpQAYa1ZHpVrfnQXyaDmGzOjwCRGPoIDJweEqGVmLycEC5aT8rX3/W9tie9iPnjmFJh4CwNMxDgVQRo80m6Gtlf/DQpA3mH39IvWGqd5fHdTPxYPs32EQSCsaYLJV5pM8xBNv6M2S/KriGnGZU0xT7MEr46da0LstKsK/U8O0yamjyugMvQoC3zQcKLrDzWFSBsT7/vG+AuV5SK8yzfEHugo7jkPQQ+NTw29xzk4dY= - on: - tags: true - skip_cleanup: true + - stage: documentation + name: "Sphinx Build" + python: "3.7" + before_install: + - travis_retry pip install -r doc/doc-requirements.txt + script: + # Build the docs with Sphinx + # -a Write all files + # -n nitpicky + - python -m sphinx -an doc build + - stage: deploy + name: "PyPi Deployment" + python: "3.7" + deploy: + provider: pypi + user: hardbyte + password: + secure: oQ9XpEkcilkZgKp+rKvPb2J1GrZe2ZvtOq/IjzCpiA8NeWixl/ai3BkPrLbd8t1wNIFoGwx7IQ7zxWL79aPYeG6XrljEomv3g45NR6dkQewUH+dQFlnT75Rm96Ycxvme0w1+71vM4PqxIuzyXUrF2n7JjC0XCCxHdTuYmPGbxVO1fOsE5R5b9inAbpEUtJuWz5AIrDEZ0OgoQpLSC8fLwbymTThX3JZ5GBLpRScVvLazjIYfRkZxvCqQ4mp1UNTdoMzekxsvxOOcEW6+j3fQO+Q/8uvMksKP0RgT8HE69oeYOeVic4Q4wGqORw+ur4A56NvBqVKtizVLCzzEG9ZfoSDy7ryvGWGZykkh8HX0PFQAEykC3iYihHK8ZFz5bEqRMegTmuRYZwPsel61wVd5posxnQkGm0syIoJNKuuRc5sUK+E3GviYcT8NntdR+4WBrvpQAYa1ZHpVrfnQXyaDmGzOjwCRGPoIDJweEqGVmLycEC5aT8rX3/W9tie9iPnjmFJh4CwNMxDgVQRo80m6Gtlf/DQpA3mH39IvWGqd5fHdTPxYPs32EQSCsaYLJV5pM8xBNv6M2S/KriGnGZU0xT7MEr46da0LstKsK/U8O0yamjyugMvQoC3zQcKLrDzWFSBsT7/vG+AuV5SK8yzfEHugo7jkPQQ+NTw29xzk4dY= + on: + # Have travis deploy tagged commits to PyPi + tags: true + skip_cleanup: true diff --git a/can/__init__.py b/can/__init__.py index 099963a3e..acf440645 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.2.0-a1" +__version__ = "3.2.0b0" log = logging.getLogger('can') From 6e80f080261ccd319cafab7c2aa47d103492c54a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 13 May 2019 13:24:28 +1000 Subject: [PATCH 0140/1235] Set version to "3.2.0" --- CHANGELOG.txt | 3 ++- can/__init__.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c7f90b080..ce66d8e4d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -6,7 +6,7 @@ Major features -------------- * FD support added for Pcan by @bmeisels with input from - @markupsp, @christiansandberg & @felixdivo in PR #537 + @markupspi, @christiansandberg & @felixdivo in PR #537 Other notable changes --------------------- @@ -14,6 +14,7 @@ Other notable changes * #532 Support has been removed for Python 3.4 * #533 BusState is now an enum. * #535 This release should automatically be published to PyPi by travis. +* #577 Travis-ci now uses stages. * #548 A guide has been added for new io formats. * #550 Finish moving from nose to pytest. * #558 Fix installation on Windows. diff --git a/can/__init__.py b/can/__init__.py index acf440645..a612363ae 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.2.0b0" +__version__ = "3.2.0" log = logging.getLogger('can') From 17048b98ffa511944f9340246e9938392bf667aa Mon Sep 17 00:00:00 2001 From: karl ding Date: Tue, 14 May 2019 13:54:25 -0700 Subject: [PATCH 0141/1235] Update installation instructions (#583) Change the old reference to the Mercurial repository hosted on BitBucket to the Git repository. In addition, update the pywin32 extension URL, as the project is now managed on GitHub. --- doc/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/installation.rst b/doc/installation.rst index add0f5cec..147b27b74 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -50,7 +50,7 @@ available, the times are returned as number of seconds from system startup. To install the uptime library, run ``pip install uptime``. This library can take advantage of the `Python for Windows Extensions -`__ library if installed. +`__ library if installed. It will be used to get notified of new messages instead of the CPU intensive polling that will otherwise have be used. @@ -83,7 +83,7 @@ Installing python-can in development mode ----------------------------------------- A "development" install of this package allows you to make changes locally -or pull updates from the Mercurial repository and use them without having to +or pull updates from the Git repository and use them without having to reinstall. Download or clone the source repository then: :: From 6bbafc1c2f00a5e64b884f75efc4e5cb5ab5b586 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 15 May 2019 14:14:30 +1000 Subject: [PATCH 0142/1235] Add note about supported python versions --- CHANGELOG.txt | 4 +++- README.rst | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ce66d8e4d..59d15b52a 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -7,11 +7,13 @@ Major features * FD support added for Pcan by @bmeisels with input from @markupspi, @christiansandberg & @felixdivo in PR #537 +* This is the last version of python-can which will support Python 2.7 + and Python 3.5. Support has been removed for Python 3.4 in this + release in PR #532 Other notable changes --------------------- -* #532 Support has been removed for Python 3.4 * #533 BusState is now an enum. * #535 This release should automatically be published to PyPi by travis. * #577 Travis-ci now uses stages. diff --git a/README.rst b/README.rst index 360d420b2..affcde831 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,16 @@ Python developers; providing common abstractions to different hardware devices, and a suite of utilities for sending and receiving messages on a can bus. -The library supports Python 2.7, Python 3.5+ as well as PyPy 2 & 3 and runs on Mac, Linux and Windows. +The library supports Python 2.7, Python 3.5+ as well as PyPy 2 & 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 (expected) 3.6+ +================== =========== Features From 7268f49f4ca349464e29bc680e9e42c66e313455 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 16 May 2019 19:06:20 +1000 Subject: [PATCH 0143/1235] fix typo in changlog --- CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 59d15b52a..2f50dca8d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -6,7 +6,7 @@ Major features -------------- * FD support added for Pcan by @bmeisels with input from - @markupspi, @christiansandberg & @felixdivo in PR #537 + @markuspi, @christiansandberg & @felixdivo in PR #537 * This is the last version of python-can which will support Python 2.7 and Python 3.5. Support has been removed for Python 3.4 in this release in PR #532 From 22a8726c4125a6454c3dece61f9f21b6b5851742 Mon Sep 17 00:00:00 2001 From: Karl Date: Wed, 15 May 2019 17:10:58 -0700 Subject: [PATCH 0144/1235] Clean up bits generator TODO in PCAN interface Remove the TODO to document how the bits generator works in the PCAN interface by adding documentation and renaming the variables to make things more clear. --- can/interfaces/pcan/pcan.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index e3d70eadf..864308bab 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -216,11 +216,17 @@ def _get_formatted_error(self, error): """ def bits(n): - """TODO: document""" + """ + Iterate over all the set bits in `n`, returning the masked bits at + the set indices + """ while n: - b = n & (~n+1) - yield b - n ^= b + # Create a mask to mask the lowest set bit in n + mask = (~n + 1) + masked_value = n & mask + yield masked_value + # Toggle the lowest set bit + n ^= masked_value stsReturn = self.m_objPCANBasic.GetErrorText(error, 0) if stsReturn[0] != PCAN_ERROR_OK: From ccc13801ca250d524adda68c37cf49b58bea8b92 Mon Sep 17 00:00:00 2001 From: Karl Date: Thu, 16 May 2019 13:36:18 -0700 Subject: [PATCH 0145/1235] Add additional coverage to SocketCAN interface Add additional test cases to cover code exercising the Linux Broadcast Manager as part of the SocketCAN interface. --- test/test_socketcan.py | 160 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 3 deletions(-) diff --git a/test/test_socketcan.py b/test/test_socketcan.py index f010a6372..828a67307 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -14,7 +14,21 @@ import ctypes -from can.interfaces.socketcan.socketcan import bcm_header_factory +from can.interfaces.socketcan.socketcan import ( + bcm_header_factory, + build_bcm_header, + build_bcm_tx_delete_header, + build_bcm_transmit_header, + build_bcm_update_header, + BcmMsgHead, +) +from can.interfaces.socketcan.constants import ( + CAN_BCM_TX_DELETE, + CAN_BCM_TX_SETUP, + SETTIMER, + STARTTIMER, + TX_COUNTEVT, +) class SocketCANTest(unittest.TestCase): @@ -93,7 +107,7 @@ def side_effect_ctypes_alignment(value): @patch("ctypes.sizeof") @patch("ctypes.alignment") - def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_8( + def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_long_8( self, ctypes_sizeof, ctypes_alignment ): """This tests a 32-bit platform (ex. Raspbian Stretch on armv7l), where: @@ -162,7 +176,7 @@ def side_effect_ctypes_alignment(value): @patch("ctypes.sizeof") @patch("ctypes.alignment") - def test_bcm_header_factory_64_bit_sizeof_long_4_alignof_long_4( + def test_bcm_header_factory_64_bit_sizeof_long_8_alignof_long_8( self, ctypes_sizeof, ctypes_alignment ): """This tests a 64-bit platform (ex. Ubuntu 18.04 on x86_64), where: @@ -229,6 +243,146 @@ def side_effect_ctypes_alignment(value): ] self.assertEqual(expected_fields, BcmMsgHead._fields_) + @unittest.skipIf( + not ( + ctypes.sizeof(ctypes.c_long) == 4 and ctypes.alignment(ctypes.c_long) == 4 + ), + "Should only run on platforms where sizeof(long) == 4 and alignof(long) == 4", + ) + def test_build_bcm_header_sizeof_long_4_alignof_long_4(self): + expected_result = b"" + expected_result += b"\x02\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x01\x04\x00\x00" + expected_result += b"\x01\x00\x00\x00\x00\x00\x00\x00" + + self.assertEqual( + expected_result, + build_bcm_header( + opcode=CAN_BCM_TX_DELETE, + flags=0, + count=0, + ival1_seconds=0, + ival1_usec=0, + ival2_seconds=0, + ival2_usec=0, + can_id=0x401, + nframes=1, + ), + ) + + @unittest.skipIf( + not ( + ctypes.sizeof(ctypes.c_long) == 8 and ctypes.alignment(ctypes.c_long) == 8 + ), + "Should only run on platforms where sizeof(long) == 8 and alignof(long) == 8", + ) + def test_build_bcm_header_sizeof_long_8_alignof_long_8(self): + expected_result = b"" + expected_result += b"\x02\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" + expected_result += b"\x01\x04\x00\x00\x01\x00\x00\x00" + + self.assertEqual( + expected_result, + build_bcm_header( + opcode=CAN_BCM_TX_DELETE, + flags=0, + count=0, + ival1_seconds=0, + ival1_usec=0, + ival2_seconds=0, + ival2_usec=0, + can_id=0x401, + nframes=1, + ), + ) + + def test_build_bcm_tx_delete_header(self): + can_id = 0x401 + flags = 0 + bcm_buffer = build_bcm_tx_delete_header(can_id=can_id, flags=flags) + result = BcmMsgHead.from_buffer_copy(bcm_buffer) + + self.assertEqual(CAN_BCM_TX_DELETE, result.opcode) + self.assertEqual(flags, result.flags) + self.assertEqual(0, result.count) + self.assertEqual(0, result.ival1_tv_sec) + self.assertEqual(0, result.ival1_tv_usec) + self.assertEqual(0, result.ival2_tv_sec) + self.assertEqual(0, result.ival2_tv_usec) + self.assertEqual(can_id, result.can_id) + self.assertEqual(1, result.nframes) + + def test_build_bcm_transmit_header_initial_period_0(self): + can_id = 0x401 + flags = 0 + count = 42 + bcm_buffer = build_bcm_transmit_header( + can_id=can_id, + count=count, + initial_period=0, + subsequent_period=2, + msg_flags=flags, + ) + result = BcmMsgHead.from_buffer_copy(bcm_buffer) + + self.assertEqual(CAN_BCM_TX_SETUP, result.opcode) + # SETTIMER and STARTTIMER should be added to the initial flags + self.assertEqual(flags | SETTIMER | STARTTIMER, result.flags) + self.assertEqual(count, result.count) + self.assertEqual(0, result.ival1_tv_sec) + self.assertEqual(0, result.ival1_tv_usec) + self.assertEqual(2, result.ival2_tv_sec) + self.assertEqual(0, result.ival2_tv_usec) + self.assertEqual(can_id, result.can_id) + self.assertEqual(1, result.nframes) + + def test_build_bcm_transmit_header_initial_period_1_24(self): + can_id = 0x401 + flags = 0 + count = 42 + bcm_buffer = build_bcm_transmit_header( + can_id=can_id, + count=count, + initial_period=1.24, + subsequent_period=2, + msg_flags=flags, + ) + result = BcmMsgHead.from_buffer_copy(bcm_buffer) + + self.assertEqual(CAN_BCM_TX_SETUP, result.opcode) + # SETTIMER, STARTTIMER, TX_COUNTEVT should be added to the initial flags + self.assertEqual(flags | SETTIMER | STARTTIMER | TX_COUNTEVT, result.flags) + self.assertEqual(count, result.count) + self.assertEqual(1, result.ival1_tv_sec) + self.assertEqual(240000, result.ival1_tv_usec) + self.assertEqual(2, result.ival2_tv_sec) + self.assertEqual(0, result.ival2_tv_usec) + self.assertEqual(can_id, result.can_id) + self.assertEqual(1, result.nframes) + + def test_build_bcm_update_header(self): + can_id = 0x401 + flags = 0 + bcm_buffer = build_bcm_update_header(can_id=can_id, msg_flags=flags) + result = BcmMsgHead.from_buffer_copy(bcm_buffer) + + self.assertEqual(CAN_BCM_TX_SETUP, result.opcode) + self.assertEqual(flags, result.flags) + self.assertEqual(0, result.count) + self.assertEqual(0, result.ival1_tv_sec) + self.assertEqual(0, result.ival1_tv_usec) + self.assertEqual(0, result.ival2_tv_sec) + self.assertEqual(0, result.ival2_tv_usec) + self.assertEqual(can_id, result.can_id) + self.assertEqual(1, result.nframes) + if __name__ == "__main__": unittest.main() From 5103e3592bf17a40caa3a1f2af0e88d72f9ca616 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 19:00:41 +0200 Subject: [PATCH 0146/1235] modernize test cases --- test/back2back_test.py | 2 -- test/listener_test.py | 4 +--- test/logformats_test.py | 14 ++------------ test/message_helper.py | 4 +--- test/network_test.py | 8 ++------ test/notifier_test.py | 7 +------ test/serial_test.py | 4 +--- test/simplecyclic_test.py | 2 -- test/test_detect_available_configs.py | 6 +----- test/test_kvaser.py | 5 +---- test/test_load_file_config.py | 1 + test/test_message_filtering.py | 2 -- test/test_message_sync.py | 2 -- test/test_scripts.py | 6 +----- test/test_slcan.py | 1 + test/test_socketcan.py | 11 +++-------- test/test_socketcan_helpers.py | 4 +--- test/test_systec.py | 5 +---- test/test_viewer.py | 15 ++------------- 19 files changed, 20 insertions(+), 83 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 4062d462a..e54ad93e4 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -5,8 +5,6 @@ This module tests two virtual buses attached to each other. """ -from __future__ import absolute_import, print_function - import sys import unittest from time import sleep diff --git a/test/listener_test.py b/test/listener_test.py index c25a6fb56..840f687fb 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -4,8 +4,6 @@ """ """ -from __future__ import absolute_import, print_function - from time import sleep import unittest import random @@ -117,7 +115,7 @@ def test_filetype_to_instance(extension, klass): test_filetype_to_instance(".log", can.CanutilsLogReader) # test file extensions that are not supported - with self.assertRaisesRegexp(NotImplementedError, ".xyz_42"): + with self.assertRaisesRegex(NotImplementedError, ".xyz_42"): test_filetype_to_instance(".xyz_42", can.Printer) def testLoggerTypeResolution(self): diff --git a/test/logformats_test.py b/test/logformats_test.py index d9551e5d6..809639e6d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -14,20 +14,12 @@ TODO: implement CAN FD support testing """ -from __future__ import print_function, absolute_import, division - import logging import unittest import tempfile import os from abc import abstractmethod, ABCMeta - -try: - # Python 3 - from itertools import zip_longest -except ImportError: - # Python 2 - from itertools import izip_longest as zip_longest +from itertools import zip_longest import can @@ -39,7 +31,7 @@ logging.basicConfig(level=logging.DEBUG) -class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase): +class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase, metaclass=ABCMeta): """Tests a pair of writer and reader by writing all data first and then reading all data and checking if they could be reconstructed correctly. Optionally writes some comments as well. @@ -50,8 +42,6 @@ class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase): (Source: `*Wojciech B.* on StackOverlfow `_) """ - __metaclass__ = ABCMeta - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) self._setup_instance() diff --git a/test/message_helper.py b/test/message_helper.py index 9a4756207..8fb94f48b 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -5,12 +5,10 @@ This module contains a helper for writing test cases that need to compare messages. """ -from __future__ import absolute_import, print_function - from copy import copy -class ComparingMessagesTestCase(object): +class ComparingMessagesTestCase: """ Must be extended by a class also extending a unittest.TestCase. diff --git a/test/network_test.py b/test/network_test.py index f4163329d..1af102ec3 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -1,17 +1,13 @@ #!/usr/bin/env python # coding: utf-8 -from __future__ import print_function import unittest import threading -try: - import queue -except ImportError: - import Queue as queue +import queue import random - import logging + logging.getLogger(__file__).setLevel(logging.WARNING) # make a random bool: diff --git a/test/notifier_test.py b/test/notifier_test.py index 3ab257cf7..71ac7a944 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -3,10 +3,7 @@ import unittest import time -try: - import asyncio -except ImportError: - asyncio = None +import asyncio import can @@ -45,7 +42,6 @@ def test_multiple_bus(self): class AsyncNotifierTest(unittest.TestCase): - @unittest.skipIf(asyncio is None, 'Test requires asyncio') def test_asyncio_notifier(self): loop = asyncio.get_event_loop() bus = can.Bus('test', bustype='virtual', receive_own_messages=True) @@ -60,6 +56,5 @@ def test_asyncio_notifier(self): bus.shutdown() - if __name__ == '__main__': unittest.main() diff --git a/test/serial_test.py b/test/serial_test.py index 5b26ae42a..c49c15354 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -7,8 +7,6 @@ Copyright: 2017 Boris Wenzlaff """ -from __future__ import division - import unittest from mock import patch @@ -18,7 +16,7 @@ from .message_helper import ComparingMessagesTestCase -class SerialDummy(object): +class SerialDummy: """ Dummy to mock the serial communication """ diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index a10871648..06712e921 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -5,8 +5,6 @@ This module tests cyclic send tasks. """ -from __future__ import absolute_import - from time import sleep import unittest import gc diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py index ca2d82c15..69addafb3 100644 --- a/test/test_detect_available_configs.py +++ b/test/test_detect_available_configs.py @@ -6,12 +6,8 @@ :meth:`can.BusABC.detect_available_configs`. """ -from __future__ import absolute_import - import sys import unittest -if sys.version_info.major > 2: - basestring = str from can import detect_available_configs @@ -33,7 +29,7 @@ def test_general_values(self): for config in configs: self.assertIn('interface', config) self.assertIn('channel', config) - self.assertIsInstance(config['interface'], basestring) + self.assertIsInstance(config['interface'], str) def test_content_virtual(self): configs = detect_available_configs(interfaces='virtual') diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 3e0bcf396..106ce7dc5 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -8,10 +8,7 @@ import time import logging import unittest -try: - from unittest.mock import Mock, patch -except ImportError: - from mock import patch, Mock +from unittest.mock import Mock, patch import pytest diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py index 52a45d734..dbd7673cb 100644 --- a/test/test_load_file_config.py +++ b/test/test_load_file_config.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # coding: utf-8 + import shutil import tempfile import unittest diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index 1419a4439..1b498b95e 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -5,8 +5,6 @@ This module tests :meth:`can.BusABC._matches_filters`. """ -from __future__ import absolute_import - import unittest from can import Bus, Message diff --git a/test/test_message_sync.py b/test/test_message_sync.py index ec21a0660..ba2332776 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -5,8 +5,6 @@ This module tests :class:`can.MessageSync`. """ -from __future__ import absolute_import - from copy import copy from time import time import gc diff --git a/test/test_scripts.py b/test/test_scripts.py index 74ae71489..11dc06991 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -5,8 +5,6 @@ This module tests that the scripts are all callable. """ -from __future__ import absolute_import - import subprocess import unittest import sys @@ -16,9 +14,7 @@ from .config import * -class CanScriptTest(unittest.TestCase): - - __metaclass__ = ABCMeta +class CanScriptTest(unittest.TestCase, metaclass=ABCMeta): @classmethod def setUpClass(cls): diff --git a/test/test_slcan.py b/test/test_slcan.py index 29869cb1c..43703b3f0 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # coding: utf-8 + import unittest import can diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 828a67307..32389ff9f 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -3,14 +3,9 @@ """ import unittest -try: - from unittest.mock import Mock - from unittest.mock import patch - from unittest.mock import call -except ImportError: - from mock import Mock - from mock import patch - from mock import call +from unittest.mock import Mock +from unittest.mock import patch +from unittest.mock import call import ctypes diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index f1462549a..cc12eb415 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -5,8 +5,6 @@ Tests helpers in `can.interfaces.socketcan.socketcan_common`. """ -from __future__ import absolute_import - import unittest from can.interfaces.socketcan.utils import \ @@ -36,7 +34,7 @@ def test_find_available_interfaces(self): result = list(find_available_interfaces()) self.assertGreaterEqual(len(result), 0) for entry in result: - self.assertRegexpMatches(entry, r"v?can\d+") + self.assertRegex(entry, r"v?can\d+") if TEST_INTERFACE_SOCKETCAN: self.assertGreaterEqual(len(result), 1) self.assertIn("vcan0", result) diff --git a/test/test_systec.py b/test/test_systec.py index ce5dda4a7..0cd38da0d 100644 --- a/test/test_systec.py +++ b/test/test_systec.py @@ -2,10 +2,7 @@ # coding: utf-8 import unittest -try: - from unittest.mock import Mock, patch -except ImportError: - from mock import Mock, patch +from unittest.mock import Mock, patch import can from can.interfaces.systec import ucan, ucanbus diff --git a/test/test_viewer.py b/test/test_viewer.py index c4b7aa45f..9420b6d37 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -23,8 +23,6 @@ # Web : http://www.lauszus.com # e-mail : lauszus@gmail.com -from __future__ import absolute_import - import argparse import can import curses @@ -36,15 +34,8 @@ import unittest import os import six - from typing import Dict, Tuple, Union - -try: - # noinspection PyCompatibility - from unittest.mock import Mock, patch -except ImportError: - # noinspection PyPackageRequirements - from mock import Mock, patch +from unittest.mock import Mock, patch from can.viewer import KEY_ESC, KEY_SPACE, CanViewer, parse_args @@ -337,9 +328,7 @@ def test_pack_unpack(self): parsed_data = CanViewer.unpack_data(CANOPEN_TPDO4 + 2, data_structs, raw_data) self.assertListEqual(parsed_data, [0xFFFFFF, 0xFFFFFFFF]) - # See: http://python-future.org/compatible_idioms.html#long-integers - from past.builtins import long - self.assertTrue(all(isinstance(d, (int, long)) for d in parsed_data)) + self.assertTrue(all(isinstance(d, int) for d in parsed_data)) # Make sure that the ValueError exception is raised with self.assertRaises(ValueError): From 262c209c4d17f7cac8c57106c2401d4ea3d0dc84 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:02:11 +0200 Subject: [PATCH 0147/1235] remove func:send_periodic() --- can/broadcastmanager.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 79d586744..85ad03776 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -142,17 +142,3 @@ def _run(self): # Compensate for the time it takes to send the message delay = self.period - (time.time() - started) time.sleep(max(0.0, delay)) - - -def send_periodic(bus, message, period, *args, **kwargs): - """ - Send a :class:`~can.Message` every `period` seconds on the given bus. - - :param can.BusABC bus: A CAN bus which supports sending. - :param can.Message message: Message to send periodically. - :param float period: The minimum time between sending messages. - :return: A started task instance - """ - warnings.warn("The function `can.send_periodic` is deprecated and will " + - "be removed in an upcoming version. Please use `can.Bus.send_periodic` instead.", DeprecationWarning) - return bus.send_periodic(message, period, *args, **kwargs) From c1e10c1949c73769a4e1f1d3632c28144805be02 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:04:58 +0200 Subject: [PATCH 0148/1235] remove old CAN module --- can/CAN.py | 29 ----------------------------- doc/development.rst | 2 -- setup.cfg | 3 --- 3 files changed, 34 deletions(-) delete mode 100644 can/CAN.py diff --git a/can/CAN.py b/can/CAN.py deleted file mode 100644 index 0ed96dfb1..000000000 --- a/can/CAN.py +++ /dev/null @@ -1,29 +0,0 @@ -# coding: utf-8 - -""" -This module was once the core of python-can, containing -implementations of all the major classes in the library, now -however all functionality has been refactored out. This API -is left intact for version 2.x to aide with migration. - -WARNING: -This module is deprecated an will get removed in version 3.x. -Please use ``import can`` instead. -""" - -from __future__ import absolute_import - -from can.message import Message -from can.listener import Listener, BufferedReader, RedirectReader -from can.util import set_logging_level -from can.io import * - -import warnings - -# See #267. -# Version 2.0 - 2.1: Log a Debug message -# Version 2.2: Log a Warning -# Version 3.x: DeprecationWarning -# Version 4.0: Remove the module -warnings.warn('Loading python-can via the old "CAN" API is deprecated since v3.0 an will get removed in v4.0 ' - 'Please use `import can` instead.', DeprecationWarning) diff --git a/doc/development.rst b/doc/development.rst index 602e4e347..8bad5c58e 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -69,8 +69,6 @@ The modules in ``python-can`` are: |:doc:`broadcastmanager ` | Contains interface independent broadcast manager | | | code. | +---------------------------------+------------------------------------------------------+ -|:doc:`CAN ` | Legacy API. Deprecated. | -+---------------------------------+------------------------------------------------------+ Creating a new Release diff --git a/setup.cfg b/setup.cfg index 49177e68e..e9a7cb985 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,9 +15,6 @@ addopts = -v --timeout=300 --cov=can --cov-config=setup.cfg branch = False # already specified by call to pytest using --cov=can #source = can -omit = - # legacy code - can/CAN.py [coverage:report] # two digits after decimal point From 682403cab9ac67c1b66eb9f2b01972b848dc0519 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:06:14 +0200 Subject: [PATCH 0149/1235] remove now obsolete docs for send_periodic() --- doc/bcm.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/doc/bcm.rst b/doc/bcm.rst index 96e73d52d..549b06edd 100644 --- a/doc/bcm.rst +++ b/doc/bcm.rst @@ -42,14 +42,3 @@ which inherits from :class:`~can.broadcastmanager.CyclicTask`. .. autoclass:: can.RestartableCyclicTaskABC :members: - - -Functional API --------------- - -.. warning:: - The functional API in :func:`can.broadcastmanager.send_periodic` is now deprecated - and will be removed in version 4.0. - Use the object oriented API via :meth:`can.BusABC.send_periodic` instead. - -.. autofunction:: can.broadcastmanager.send_periodic From 36434c8a05841a664319773f9cb5896ec90012cd Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:07:46 +0200 Subject: [PATCH 0150/1235] remove deprecated access to socketcan member --- can/interface.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/can/interface.py b/can/interface.py index 6c830d0c4..cbc907f86 100644 --- a/can/interface.py +++ b/can/interface.py @@ -18,11 +18,6 @@ from .util import load_config from .interfaces import BACKENDS -if 'linux' in sys.platform: - # Deprecated and undocumented access to SocketCAN cyclic tasks - # Will be removed in version 4.0 - from can.interfaces.socketcan import CyclicSendTask, MultiRateCyclicSendTask - # Required by "detect_available_configs" for argument interpretation if sys.version_info.major > 2: basestring = str From 8ad901e84a87231951423f2f8d71bb5146afabbb Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:12:20 +0200 Subject: [PATCH 0151/1235] remove entry point python_can.interface --- can/interfaces/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index ec79e51d6..c4c7f52f7 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -31,10 +31,4 @@ for interface in iter_entry_points('can.interface') }) -# Old entry point name. May be removed >3.0. -for interface in iter_entry_points('python_can.interface'): - BACKENDS[interface.name] = (interface.module_name, interface.attrs[0]) - warnings.warn('{} is using the deprecated python_can.interface entry point. '.format(interface.name) + - 'Please change to can.interface instead.', DeprecationWarning) - VALID_INTERFACES = frozenset(list(BACKENDS.keys()) + ['socketcan_native', 'socketcan_ctypes']) From a688695f7da3f49596f4243705ee4412ae92e9fb Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:14:30 +0200 Subject: [PATCH 0152/1235] remove special case for socketcan_{native,ctypes} --- can/interfaces/socketcan/socketcan.py | 4 +--- can/util.py | 7 ------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 633c87b22..6aebc7221 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -1,6 +1,6 @@ # coding: utf-8 -import logging +import logging import ctypes import ctypes.util import os @@ -14,8 +14,6 @@ log_tx = log.getChild("tx") log_rx = log.getChild("rx") -log.debug("Loading socketcan native backend") - try: import fcntl except ImportError: diff --git a/can/util.py b/can/util.py index af421651f..be6793152 100644 --- a/can/util.py +++ b/can/util.py @@ -186,13 +186,6 @@ def load_config(path=None, config=None, context=None): if key not in config: config[key] = None - # Handle deprecated socketcan types - if config['interface'] in ('socketcan_native', 'socketcan_ctypes'): - # DeprecationWarning in 3.x releases - # TODO: Remove completely in 4.0 - warnings.warn('{} is deprecated, use socketcan instead'.format(config['interface']), DeprecationWarning) - config['interface'] = 'socketcan' - if config['interface'] not in VALID_INTERFACES: raise NotImplementedError('Invalid CAN Bus Type - {}'.format(config['interface'])) From 6a38debd041e4448017e451517b15daaa0933dd5 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:18:10 +0200 Subject: [PATCH 0153/1235] remove deprecated members of can.Message --- can/message.py | 51 ++------------------------------------------------ 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/can/message.py b/can/message.py index f85218fc0..1c1a3fd9d 100644 --- a/can/message.py +++ b/can/message.py @@ -45,46 +45,13 @@ class Message(object): "is_fd", "bitrate_switch", "error_state_indicator", - "__weakref__", # support weak references to messages - "_dict" # see __getattr__ + "__weakref__" # support weak references to messages ) - def __getattr__(self, key): - # TODO keep this for a version, in order to not break old code - # this entire method (as well as the _dict attribute in __slots__ and the __setattr__ method) - # can be removed in 4.0 - # this method is only called if the attribute was not found elsewhere, like in __slots__ - try: - warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) - return self._dict[key] - except KeyError: - raise AttributeError("'message' object has no attribute '{}'".format(key)) - - def __setattr__(self, key, value): - # see __getattr__ - try: - super(Message, self).__setattr__(key, value) - except AttributeError: - warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) - self._dict[key] = value - - @property - def id_type(self): - # TODO remove in 4.0 - warnings.warn("Message.id_type is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) - return self.is_extended_id - - @id_type.setter - def id_type(self, value): - # TODO remove in 4.0 - warnings.warn("Message.id_type is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) - self.is_extended_id = value - def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, is_remote_frame=False, is_error_frame=False, channel=None, dlc=None, data=None, is_fd=False, bitrate_switch=False, error_state_indicator=False, - extended_id=None, # deprecated in 3.x, TODO remove in 4.x check=False): """ To create a message object, simply provide any of the below attributes @@ -98,24 +65,12 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, :raises ValueError: iff `check` is set to `True` and one or more arguments were invalid """ - self._dict = dict() # see __getattr__ - self.timestamp = timestamp self.arbitration_id = arbitration_id - - if extended_id is not None: - # TODO remove in 4.0 - warnings.warn("The extended_id parameter is deprecated and will be removed in 4.0, use is_extended_id instead", DeprecationWarning) - - if is_extended_id is not None: - self.is_extended_id = is_extended_id - else: - self.is_extended_id = True if extended_id is None else extended_id - + self.is_extended_id = is_extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame self.channel = channel - self.is_fd = is_fd self.bitrate_switch = bitrate_switch self.error_state_indicator = error_state_indicator @@ -239,7 +194,6 @@ def __copy__(self): bitrate_switch=self.bitrate_switch, error_state_indicator=self.error_state_indicator ) - new._dict.update(self._dict) return new def __deepcopy__(self, memo): @@ -256,7 +210,6 @@ def __deepcopy__(self, memo): bitrate_switch=self.bitrate_switch, error_state_indicator=self.error_state_indicator ) - new._dict.update(self._dict) return new def _check(self): From f31a36f92382a13a18f961573e8c33cfd38db95c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:33:33 +0200 Subject: [PATCH 0154/1235] remove import of removed function --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index a612363ae..0bc8cfe62 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -45,7 +45,7 @@ class CanError(IOError): from . import interface from .interface import Bus, detect_available_configs -from .broadcastmanager import send_periodic, \ +from .broadcastmanager import \ CyclicSendTaskABC, \ LimitedDurationCyclicSendTaskABC, \ ModifiableCyclicTaskABC, \ From d8a8d811da47a7c5f9902b82e9fe22f74dac49de Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 19:04:22 +0200 Subject: [PATCH 0155/1235] fix small problem in __repr__() --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index 1c1a3fd9d..e0435867e 100644 --- a/can/message.py +++ b/can/message.py @@ -149,7 +149,7 @@ def __nonzero__(self): def __repr__(self): args = ["timestamp={}".format(self.timestamp), "arbitration_id={:#x}".format(self.arbitration_id), - "extended_id={}".format(self.is_extended_id)] + "is_extended_id={}".format(self.is_extended_id)] if self.is_remote_frame: args.append("is_remote_frame={}".format(self.is_remote_frame)) From 978d0109f01c288aacfda8fb45ab0963cd1b169c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 19:05:09 +0200 Subject: [PATCH 0156/1235] make is_extended_id True by default again --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index e0435867e..3826cede2 100644 --- a/can/message.py +++ b/can/message.py @@ -48,7 +48,7 @@ class Message(object): "__weakref__" # support weak references to messages ) - def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, + 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, From 342f7400f41822560e2a24ea6f6d2e5ec9b89cdb Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 12:27:00 +0200 Subject: [PATCH 0157/1235] remove python <3.6 from root filea --- .appveyor.yml | 6 +----- .travis.yml | 5 +---- README.rst | 4 ++-- setup.py | 5 +---- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index f65386f3e..500c71320 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,14 +3,10 @@ environment: # For Python versions available on Appveyor, see # https://www.appveyor.com/docs/windows-images-software/#python - # Python pre-2.7 and 3.0-3.4 have reached EOL + # Only Python 3.6+ is supported - - PYTHON: "C:\\Python27" - - PYTHON: "C:\\Python35" - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python37" - - PYTHON: "C:\\Python27-x64" - - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36-x64" - PYTHON: "C:\\Python37-x64" diff --git a/.travis.yml b/.travis.yml index b45e17d95..6e84eb22a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,12 @@ cache: - "$HOME/.cache/pip" python: - # CPython; versions pre-2.7 and 3.0-3.5 have reached EOL - - "2.7" + # CPython; only 3.6 is supported - "3.6" - "3.7" - 3.8-dev - nightly # PyPy: - - pypy # Python 2.7 - - pypy3.5 # Python 3.5 - pypy3 env: diff --git a/README.rst b/README.rst index affcde831..5b29f46ad 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ Python developers; providing common abstractions to different hardware devices, and a suite of utilities for sending and receiving messages on a can bus. -The library supports Python 2.7, Python 3.5+ as well as PyPy 2 & 3 and runs +The library currently supports Python 3.6+ as well as PyPy 3 and runs on Mac, Linux and Windows. ================== =========== @@ -45,7 +45,7 @@ Library Version Python ------------------ ----------- 2.x 2.6+, 3.4+ 3.x 2.7+, 3.5+ - 4.x (expected) 3.6+ + 4.x 3.6+ ================== =========== diff --git a/setup.py b/setup.py index c600b7215..a5d452370 100644 --- a/setup.py +++ b/setup.py @@ -51,8 +51,6 @@ classifiers=[ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: CPython", @@ -97,11 +95,10 @@ # Installation # see https://www.python.org/dev/peps/pep-0345/#version-specifiers - python_requires=">=2.7", + python_requires=">=3.6", install_requires=[ 'wrapt~=1.10', 'aenum', - 'typing;python_version<"3.5"', 'windows-curses;platform_system=="Windows"', ], setup_requires=["pytest-runner"], From 036b99fd6d5c73e92fe7b6c368d2302944fe34ca Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 12:39:08 +0200 Subject: [PATCH 0158/1235] remove python <3.6 from can/* --- can/__init__.py | 2 - can/broadcastmanager.py | 2 +- can/bus.py | 2 - can/interface.py | 2 - can/listener.py | 82 +++++++++++++++++++---------------------- can/logger.py | 3 +- can/message.py | 3 +- can/notifier.py | 7 +--- can/player.py | 2 - can/thread_safe_bus.py | 2 - can/util.py | 8 +--- can/viewer.py | 6 +-- 12 files changed, 45 insertions(+), 76 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 0bc8cfe62..b9b3b3e21 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -4,8 +4,6 @@ ``can`` is an object-orient Controller Area Network (CAN) interface module. """ -from __future__ import absolute_import - import logging __version__ = "3.2.0" diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 85ad03776..564d6ee49 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -16,7 +16,7 @@ log = logging.getLogger('can.bcm') -class CyclicTask(object): +class CyclicTask: """ Abstract Base for all cyclic tasks. """ diff --git a/can/bus.py b/can/bus.py index 2b36b3c57..9e46cbbf3 100644 --- a/can/bus.py +++ b/can/bus.py @@ -4,8 +4,6 @@ Contains the ABC bus implementation and its documentation. """ -from __future__ import print_function, absolute_import - from abc import ABCMeta, abstractmethod import logging import threading diff --git a/can/interface.py b/can/interface.py index cbc907f86..92c592874 100644 --- a/can/interface.py +++ b/can/interface.py @@ -6,8 +6,6 @@ CyclicSendTasks. """ -from __future__ import absolute_import, print_function - import sys import importlib import logging diff --git a/can/listener.py b/can/listener.py index a91b1dac1..a44d1c04f 100644 --- a/can/listener.py +++ b/can/listener.py @@ -10,20 +10,13 @@ # Python 3.7 from queue import SimpleQueue, Empty except ImportError: - try: - # Python 3.0 - 3.6 - from queue import Queue as SimpleQueue, Empty - except ImportError: - # Python 2 - from Queue import Queue as SimpleQueue, Empty + # Python 3.0 - 3.6 + from queue import Queue as SimpleQueue, Empty -try: - import asyncio -except ImportError: - asyncio = None +import asyncio -class Listener(object): +class Listener: """The basic listener that can be called directly to handle some CAN message:: @@ -133,42 +126,41 @@ def stop(self): self.is_stopped = True -if asyncio is not None: - class AsyncBufferedReader(Listener): - """A message buffer for use with :mod:`asyncio`. +class AsyncBufferedReader(Listener): + """A message buffer for use with :mod:`asyncio`. - See :ref:`asyncio` for how to use with :class:`can.Notifier`. - - Can also be used as an asynchronous iterator:: + See :ref:`asyncio` for how to use with :class:`can.Notifier`. + + Can also be used as an asynchronous iterator:: + + async for msg in reader: + print(msg) + """ + + def __init__(self, loop=None): + # set to "infinite" size + self.buffer = asyncio.Queue(loop=loop) - async for msg in reader: - print(msg) + def on_message_received(self, msg): + """Append a message to the buffer. + + Must only be called inside an event loop! """ + self.buffer.put_nowait(msg) - def __init__(self, loop=None): - # set to "infinite" size - self.buffer = asyncio.Queue(loop=loop) - - def on_message_received(self, msg): - """Append a message to the buffer. - - Must only be called inside an event loop! - """ - self.buffer.put_nowait(msg) - - def get_message(self): - """ - Retrieve the latest message when awaited for:: - - msg = await reader.get_message() - - :rtype: can.Message - :return: The CAN message. - """ - return self.buffer.get() - - def __aiter__(self): - return self + def get_message(self): + """ + Retrieve the latest message when awaited for:: - def __anext__(self): - return self.buffer.get() + msg = await reader.get_message() + + :rtype: can.Message + :return: The CAN message. + """ + return self.buffer.get() + + def __aiter__(self): + return self + + def __anext__(self): + return self.buffer.get() diff --git a/can/logger.py b/can/logger.py index 204eb8dfb..bc35252e9 100644 --- a/can/logger.py +++ b/can/logger.py @@ -16,8 +16,6 @@ Dynamic Controls 2010 """ -from __future__ import absolute_import, print_function - import sys import argparse import socket @@ -116,5 +114,6 @@ def main(): bus.shutdown() logger.stop() + if __name__ == "__main__": main() diff --git a/can/message.py b/can/message.py index 3826cede2..9bb864c0a 100644 --- a/can/message.py +++ b/can/message.py @@ -8,14 +8,13 @@ starting with Python 3.7. """ -from __future__ import absolute_import, division import warnings from copy import deepcopy from math import isinf, isnan -class Message(object): +class Message: """ The :class:`~can.Message` object is used to represent CAN messages for sending, receiving and other purposes like converting between different diff --git a/can/notifier.py b/can/notifier.py index 737ec978e..256085a7b 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -7,15 +7,12 @@ import threading import logging import time -try: - import asyncio -except ImportError: - asyncio = None +import asyncio logger = logging.getLogger('can.Notifier') -class Notifier(object): +class Notifier: def __init__(self, bus, listeners, timeout=1.0, loop=None): """Manages the distribution of :class:`can.Message` instances to listeners. diff --git a/can/player.py b/can/player.py index c712f1714..e744837c3 100644 --- a/can/player.py +++ b/can/player.py @@ -7,8 +7,6 @@ Similar to canplayer in the can-utils package. """ -from __future__ import absolute_import, print_function - import sys import argparse from datetime import datetime diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index d82ac6bd6..648285b05 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -1,7 +1,5 @@ # coding: utf-8 -from __future__ import print_function, absolute_import - from threading import RLock try: diff --git a/can/util.py b/can/util.py index be6793152..7e106fde4 100644 --- a/can/util.py +++ b/can/util.py @@ -4,8 +4,6 @@ Utilities and configuration file parsing. """ -from __future__ import absolute_import, print_function - import os import os.path import sys @@ -13,11 +11,7 @@ import re import logging import warnings - -try: - from configparser import ConfigParser -except ImportError: - from ConfigParser import SafeConfigParser as ConfigParser +from configparser import ConfigParser import can from can.interfaces import VALID_INTERFACES diff --git a/can/viewer.py b/can/viewer.py index 316d3e3e4..d7e734fb9 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -1,5 +1,5 @@ # coding: utf-8 -# + # Copyright (C) 2018 Kristian Sloth Lauszus. # # This program is free software; you can redistribute it and/or @@ -22,8 +22,6 @@ # Web : http://www.lauszus.com # e-mail : lauszus@gmail.com -from __future__ import absolute_import, print_function - import argparse import os import struct @@ -496,7 +494,7 @@ def main(): # pragma: no cover curses.wrapper(CanViewer, bus, data_structs) -if __name__ == '__main__': # pragma: no cover +if __name__ == '__main__': # Catch ctrl+c try: main() From 877343b8bead125bfe24754ab41403fc406447c3 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 12:42:42 +0200 Subject: [PATCH 0159/1235] remove python <3.6 from can/io/* --- can/io/__init__.py | 2 -- can/io/asc.py | 2 -- can/io/blf.py | 2 -- can/io/canutils.py | 2 -- can/io/csv.py | 2 -- can/io/generic.py | 2 +- can/io/logger.py | 4 +--- can/io/player.py | 4 +--- can/io/printer.py | 2 -- can/io/sqlite.py | 6 ------ 10 files changed, 3 insertions(+), 25 deletions(-) diff --git a/can/io/__init__.py b/can/io/__init__.py index a0d89f28b..3797d4b5d 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -5,8 +5,6 @@ and Writers based off the file extension. """ -from __future__ import absolute_import - # Generic from .logger import Logger from .player import LogReader, MessageSync diff --git a/can/io/asc.py b/can/io/asc.py index 3ed50f04a..6cbfeb60d 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -8,8 +8,6 @@ - under `test/data/logfile.asc` """ -from __future__ import absolute_import - from datetime import datetime import time import logging diff --git a/can/io/blf.py b/can/io/blf.py index d162fdebc..55aab0715 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -14,8 +14,6 @@ objects types. """ -from __future__ import absolute_import - import struct import zlib import datetime diff --git a/can/io/canutils.py b/can/io/canutils.py index 69c0227a4..2d1232c74 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -6,8 +6,6 @@ (https://github.com/linux-can/can-utils). """ -from __future__ import absolute_import, division - import time import datetime import logging diff --git a/can/io/csv.py b/can/io/csv.py index 92f841f8f..c85e6339f 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -11,8 +11,6 @@ of a CSV file. """ -from __future__ import absolute_import - from base64 import b64encode, b64decode from can.message import Message diff --git a/can/io/generic.py b/can/io/generic.py index a61c33a9f..26b85f8e3 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -9,7 +9,7 @@ from can import Listener -class BaseIOHandler(object): +class BaseIOHandler: """A generic file handler that can be used for reading and writing. Can be used as a context manager. diff --git a/can/io/logger.py b/can/io/logger.py index 52d2e8d83..da3edb2b5 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -4,8 +4,6 @@ See the :class:`Logger` class. """ -from __future__ import absolute_import - import logging from ..listener import Listener @@ -61,5 +59,5 @@ def __new__(cls, filename, *args, **kwargs): return CanutilsLogWriter(filename, *args, **kwargs) # else: - log.info('unknown file type "%s", falling pack to can.Printer', filename) + log.warning('unknown file type "%s", falling pack to can.Printer', filename) return Printer(filename, *args, **kwargs) diff --git a/can/io/player.py b/can/io/player.py index 229c157c3..3e856767b 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -6,8 +6,6 @@ in the recorded order an time intervals. """ -from __future__ import absolute_import - from time import time, sleep import logging @@ -65,7 +63,7 @@ def __new__(cls, filename, *args, **kwargs): raise NotImplementedError("No read support for this log format: {}".format(filename)) -class MessageSync(object): +class MessageSync: """ Used to iterate over some given messages in the recorded time. """ diff --git a/can/io/printer.py b/can/io/printer.py index 6cc01f69b..7eac86935 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -4,8 +4,6 @@ This Listener simply prints to stdout / the terminal or a file. """ -from __future__ import print_function, absolute_import - import logging from can.listener import Listener diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 21cd2aafc..f61117384 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -6,8 +6,6 @@ .. note:: The database schema is given in the documentation of the loggers. """ -from __future__ import absolute_import - import sys import time import threading @@ -20,10 +18,6 @@ log = logging.getLogger('can.io.sqlite') -if sys.version_info.major < 3: - # legacy fallback for Python 2 - memoryview = buffer - class SqliteReader(BaseIOHandler): """ From 71d9fa4fd7ecd00ebb50acd21fcc9876c2769fda Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 12:45:13 +0200 Subject: [PATCH 0160/1235] remove python <3.6 from can/interfaces/* --- can/interfaces/canalystii.py | 2 ++ can/interfaces/iscan.py | 2 -- can/interfaces/slcan.py | 2 -- can/interfaces/virtual.py | 5 +---- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 35f240a66..2d4c69fcb 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,3 +1,5 @@ +# coding: utf-8 + from ctypes import * import logging import platform diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index a646bd96e..bfca3fdd3 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -4,8 +4,6 @@ Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH. """ -from __future__ import absolute_import, division - import ctypes import time import logging diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 8793e2c22..70b3dc27b 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -9,8 +9,6 @@ """ -from __future__ import absolute_import - import time import logging diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 6f24c73f2..5594674c1 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -11,10 +11,7 @@ from copy import deepcopy import logging import time -try: - import queue -except ImportError: - import Queue as queue +import queue from threading import RLock from random import randint From fb9b63fa7bf06a45d4efe931ccdddd6fada3f614 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 17:59:16 +0200 Subject: [PATCH 0161/1235] remove support for Python < 3.6 from can/interfaces/*/* --- can/interfaces/ixxat/canlib.py | 8 +------- can/interfaces/kvaser/canlib.py | 2 -- can/interfaces/pcan/pcan.py | 16 +++------------- can/interfaces/serial/serial_can.py | 2 -- can/interfaces/socketcan/__init__.py | 2 +- can/interfaces/systec/ucanbus.py | 1 + can/interfaces/usb2can/__init__.py | 2 -- can/interfaces/usb2can/serial_selector.py | 2 -- can/interfaces/usb2can/usb2canInterface.py | 2 -- .../usb2can/usb2canabstractionlayer.py | 2 -- 10 files changed, 6 insertions(+), 33 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 84c8751c1..63b422035 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -13,8 +13,6 @@ """ -from __future__ import absolute_import, division - import ctypes import functools import logging @@ -32,11 +30,7 @@ log = logging.getLogger('can.ixxat') -try: - # since Python 3.3 - from time import perf_counter as _timer_function -except ImportError: - from time import clock as _timer_function +from time import perf_counter as _timer_function # Hack to have vciFormatError as a free function, see below vciFormatError = None diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index fa3a70221..fdde4ef1e 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -8,8 +8,6 @@ Copyright (C) 2010 Dynamic Controls """ -from __future__ import absolute_import - import sys import time import logging diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 864308bab..f772ff576 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -4,8 +4,6 @@ Enable basic CAN over a PCAN USB device. """ -from __future__ import absolute_import, print_function, division - import logging import sys import time @@ -16,9 +14,8 @@ from can.util import len2dlc, dlc2len from .basic import * -boottimeEpoch = 0 try: - import uptime + import uptime # isn't this from a library? import datetime boottimeEpoch = (uptime.boottime() - datetime.datetime.utcfromtimestamp(0)).total_seconds() except: @@ -39,13 +36,6 @@ # Use polling instead HAS_EVENTS = False -try: - # new in 3.3 - timeout_clock = time.perf_counter -except AttributeError: - # deprecated in 3.3 - timeout_clock = time.clock - # Set up logging log = logging.getLogger('can.pcan') @@ -277,7 +267,7 @@ def _recv_internal(self, timeout): timeout_ms = int(timeout * 1000) if timeout is not None else INFINITE elif timeout is not None: # Calculate max time - end_time = timeout_clock() + timeout + end_time = time.perf_counter() + timeout #log.debug("Trying to read a msg") @@ -293,7 +283,7 @@ def _recv_internal(self, timeout): val = WaitForSingleObject(self._recv_event, timeout_ms) if val != WAIT_OBJECT_0: return None, False - elif timeout is not None and timeout_clock() >= end_time: + elif timeout is not None and time.perf_counter() >= end_time: return None, False else: result = None diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index afa545734..500614868 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -7,8 +7,6 @@ recording CAN traces. """ -from __future__ import absolute_import, division - import logging import struct diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index 8a2105598..06a9d9959 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -4,4 +4,4 @@ See: https://www.kernel.org/doc/Documentation/networking/can.txt """ -from can.interfaces.socketcan.socketcan import SocketcanBus, CyclicSendTask, MultiRateCyclicSendTask +from .socketcan import SocketcanBus, CyclicSendTask, MultiRateCyclicSendTask diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 9731398bd..d074a3028 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -16,6 +16,7 @@ class Ucan(UcanServer): """ Wrapper around UcanServer to read messages with timeout using events. """ + def __init__(self): super(Ucan, self).__init__() self._msg_received_event = Event() diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index 454942934..623af5bc3 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -3,7 +3,5 @@ """ """ -from __future__ import absolute_import - from .usb2canInterface import Usb2canBus from .usb2canabstractionlayer import Usb2CanAbstractionLayer diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index b47396876..e74430f04 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -3,8 +3,6 @@ """ """ -from __future__ import division, print_function, absolute_import - import logging try: diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index eb87ffbd7..1f2b38c32 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -4,8 +4,6 @@ This interface is for Windows only, otherwise use socketCAN. """ -from __future__ import division, print_function, absolute_import - import logging from ctypes import byref diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index a318bcd6f..0086d542d 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -5,8 +5,6 @@ Socket CAN is recommended under Unix/Linux systems. """ -from __future__ import division, print_function, absolute_import - from ctypes import * from struct import * import logging From bb0103febe28c5868b2af79106f1fd4c84266d4d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:09:25 +0200 Subject: [PATCH 0162/1235] remove obsolete string compatability fix --- can/interface.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/can/interface.py b/can/interface.py index 92c592874..9eba8bcc9 100644 --- a/can/interface.py +++ b/can/interface.py @@ -16,10 +16,6 @@ from .util import load_config from .interfaces import BACKENDS -# Required by "detect_available_configs" for argument interpretation -if sys.version_info.major > 2: - basestring = str - log = logging.getLogger('can.interface') log_autodetect = log.getChild('detect_available_configs') @@ -144,7 +140,7 @@ def detect_available_configs(interfaces=None): if interfaces is None: # use an iterator over the keys so we do not have to copy it interfaces = BACKENDS.keys() - elif isinstance(interfaces, basestring): + elif isinstance(interfaces, str): interfaces = [interfaces, ] # else it is supposed to be an iterable of strings From 5c1644b8ee4bb9dca853c8c740b28e69a4821275 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:19:11 +0200 Subject: [PATCH 0163/1235] remove obsolete __nonzero__()-method in can.Message --- can/message.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/can/message.py b/can/message.py index 9bb864c0a..cc39642bf 100644 --- a/can/message.py +++ b/can/message.py @@ -138,13 +138,8 @@ def __len__(self): return self.dlc def __bool__(self): - # For Python 3 return True - def __nonzero__(self): - # For Python 2 - return self.__bool__() - def __repr__(self): args = ["timestamp={}".format(self.timestamp), "arbitration_id={:#x}".format(self.arbitration_id), From 9f09418b3c5d075a1210f1e3ac9dd081505bfc7f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:22:26 +0200 Subject: [PATCH 0164/1235] remove socketcan_ctypes leftovers --- can/interfaces/socketcan/socketcan.py | 75 ++------------------------- 1 file changed, 5 insertions(+), 70 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 6aebc7221..6979594f8 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -29,42 +29,6 @@ pack_filters, find_available_interfaces, error_code_to_str -try: - socket.CAN_BCM -except AttributeError: - HAS_NATIVE_SUPPORT = False -else: - HAS_NATIVE_SUPPORT = True - - -if not HAS_NATIVE_SUPPORT: - def check_status(result, function, arguments): - if result < 0: - raise can.CanError(error_code_to_str(ctypes.get_errno())) - return result - - try: - libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) - libc.bind.errcheck = check_status - libc.connect.errcheck = check_status - libc.sendto.errcheck = check_status - libc.recvfrom.errcheck = check_status - except: - log.warning("libc is unavailable") - libc = None - - def get_addr(sock, channel): - """Get sockaddr for a channel.""" - if channel: - data = struct.pack("16si", channel.encode(), 0) - res = fcntl.ioctl(sock, SIOCGIFINDEX, data) - idx, = struct.unpack("16xi", res) - else: - # All channels - idx = 0 - return struct.pack("HiLL", AF_CAN, idx, 0, 0) - - # Setup BCM struct def bcm_header_factory(fields, alignment=8): curr_stride = 0 @@ -262,11 +226,7 @@ def dissect_can_frame(frame): def create_bcm_socket(channel): """create a broadcast manager socket and connect to the given interface""" s = socket.socket(PF_CAN, socket.SOCK_DGRAM, CAN_BCM) - if HAS_NATIVE_SUPPORT: - s.connect((channel,)) - else: - addr = get_addr(s, channel) - libc.connect(s.fileno(), addr, len(addr)) + s.connect((channel,)) return s @@ -421,12 +381,7 @@ def bind_socket(sock, channel='can0'): If the specified interface isn't found. """ log.debug('Binding socket to channel=%s', channel) - if HAS_NATIVE_SUPPORT: - sock.bind((channel,)) - else: - # For Python 2.7 - addr = get_addr(sock, channel) - libc.bind(sock.fileno(), addr, len(addr)) + sock.bind((channel,)) log.debug('Bound socket.') @@ -444,22 +399,8 @@ def capture_message(sock, get_channel=False): # Fetching the Arb ID, DLC and Data try: if get_channel: - if HAS_NATIVE_SUPPORT: - cf, addr = sock.recvfrom(CANFD_MTU) - channel = addr[0] if isinstance(addr, tuple) else addr - else: - data = ctypes.create_string_buffer(CANFD_MTU) - addr = ctypes.create_string_buffer(32) - addrlen = ctypes.c_int(len(addr)) - received = libc.recvfrom(sock.fileno(), data, len(data), 0, - addr, ctypes.byref(addrlen)) - cf = data.raw[:received] - # Figure out the channel name - family, ifindex = struct.unpack_from("Hi", addr.raw) - assert family == AF_CAN - data = struct.pack("16xi", ifindex) - res = fcntl.ioctl(sock, SIOCGIFNAME, data) - channel = ctypes.create_string_buffer(res).value.decode() + cf, addr = sock.recvfrom(CANFD_MTU) + channel = addr[0] if isinstance(addr, tuple) else addr else: cf = sock.recv(CANFD_MTU) channel = None @@ -634,13 +575,7 @@ def _send_once(self, data, channel=None): try: if self.channel == "" and channel: # Message must be addressed to a specific channel - if HAS_NATIVE_SUPPORT: - sent = self.socket.sendto(data, (channel, )) - else: - addr = get_addr(self.socket, channel) - sent = libc.sendto(self.socket.fileno(), - data, len(data), 0, - addr, len(addr)) + sent = self.socket.sendto(data, (channel, )) else: sent = self.socket.send(data) except socket.error as exc: From 5214e93342ca67f3b7ddb3748b275368e2f2886b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:34:25 +0200 Subject: [PATCH 0165/1235] remove conditional import, since it will now always work --- can/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index b9b3b3e21..0606266a9 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -20,11 +20,7 @@ class CanError(IOError): pass -from .listener import Listener, BufferedReader, RedirectReader -try: - from .listener import AsyncBufferedReader -except ImportError: - pass +from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader from .io import Logger, Printer, LogReader, MessageSync from .io import ASCWriter, ASCReader From 83710cabf1fedc25a9dee90cbe90f93766b2b2de Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 18:48:18 +0200 Subject: [PATCH 0166/1235] run 2to3 and cleanup afterwards --- can/bus.py | 4 +--- can/interface.py | 5 ++--- can/interfaces/socketcan/socketcan.py | 2 +- can/interfaces/socketcan/utils.py | 1 + can/io/generic.py | 4 +--- can/listener.py | 4 +--- can/logger.py | 6 +++--- can/player.py | 2 +- can/viewer.py | 4 ++-- 9 files changed, 13 insertions(+), 19 deletions(-) diff --git a/can/bus.py b/can/bus.py index 9e46cbbf3..c8ade66d2 100644 --- a/can/bus.py +++ b/can/bus.py @@ -24,7 +24,7 @@ class BusState(Enum): ERROR = auto() -class BusABC(object): +class BusABC(metaclass=ABCMeta): """The CAN Bus Abstract Base Class that serves as the basis for all concrete interfaces. @@ -401,5 +401,3 @@ def _detect_available_configs(): for usage in the interface's bus constructor. """ raise NotImplementedError() - - __metaclass__ = ABCMeta diff --git a/can/interface.py b/can/interface.py index 9eba8bcc9..30bade25c 100644 --- a/can/interface.py +++ b/can/interface.py @@ -138,10 +138,9 @@ def detect_available_configs(interfaces=None): # Figure out where to search if interfaces is None: - # use an iterator over the keys so we do not have to copy it - interfaces = BACKENDS.keys() + interfaces = BACKENDS elif isinstance(interfaces, str): - interfaces = [interfaces, ] + interfaces = (interfaces, ) # else it is supposed to be an iterable of strings result = [] diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 6979594f8..dc91471a5 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -657,7 +657,7 @@ def receiver(event): bind_socket(receiver_socket, 'vcan0') print("Receiver is waiting for a message...") event.set() - print("Receiver got: ", capture_message(receiver_socket)) + print(f"Receiver got: {capture_message(receiver_socket)}") def sender(event): event.wait() diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 44d356920..ebd095f35 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -67,6 +67,7 @@ def find_available_interfaces(): log.debug("find_available_interfaces(): detected: %s", interface_names) return filter(_PATTERN_CAN_INTERFACE.match, interface_names) + def error_code_to_str(code): """ Converts a given error code (errno) to a useful and human readable string. diff --git a/can/io/generic.py b/can/io/generic.py index 26b85f8e3..b452d8404 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -9,7 +9,7 @@ from can import Listener -class BaseIOHandler: +class BaseIOHandler(metaclass=ABCMeta): """A generic file handler that can be used for reading and writing. Can be used as a context manager. @@ -19,8 +19,6 @@ class BaseIOHandler: was opened """ - __metaclass__ = ABCMeta - def __init__(self, file, mode='rt'): """ :param file: a path-like object to open a file, a file-like object diff --git a/can/listener.py b/can/listener.py index a44d1c04f..850a7d4a7 100644 --- a/can/listener.py +++ b/can/listener.py @@ -16,7 +16,7 @@ import asyncio -class Listener: +class Listener(metaclass=ABCMeta): """The basic listener that can be called directly to handle some CAN message:: @@ -32,8 +32,6 @@ class Listener: listener.stop() """ - __metaclass__ = ABCMeta - @abstractmethod def on_message_received(self, msg): """This method is called to handle the given message. diff --git a/can/logger.py b/can/logger.py index bc35252e9..3cf2758e4 100644 --- a/can/logger.py +++ b/can/logger.py @@ -76,7 +76,7 @@ def main(): can_filters = [] if len(results.filter) > 0: - print('Adding filter/s', results.filter) + print(f"Adding filter(s): {results.filter}") for filt in results.filter: if ':' in filt: _ = filt.split(":") @@ -99,8 +99,8 @@ def main(): elif results.passive: bus.state = BusState.PASSIVE - print('Connected to {}: {}'.format(bus.__class__.__name__, bus.channel_info)) - print('Can Logger (Started on {})\n'.format(datetime.now())) + print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") + print(f"Can Logger (Started on {datetime.now()})") logger = Logger(results.log_file) try: diff --git a/can/player.py b/can/player.py index e744837c3..d41396cf5 100644 --- a/can/player.py +++ b/can/player.py @@ -78,7 +78,7 @@ def main(): in_sync = MessageSync(reader, timestamps=results.timestamps, gap=results.gap, skip=results.skip) - print('Can LogReader (Started on {})'.format(datetime.now())) + print(f"Can LogReader (Started on {datetime.now()})") try: for m in in_sync: diff --git a/can/viewer.py b/can/viewer.py index d7e734fb9..cecc17037 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -145,7 +145,7 @@ def unpack_data(cmd, cmd_to_struct, data): # type: (int, Dict, bytes) -> List[U # These messages do not contain a data package return [] - for key in cmd_to_struct.keys(): + for key in cmd_to_struct: if cmd == key if isinstance(key, int) else cmd in key: value = cmd_to_struct[key] if isinstance(value, tuple): @@ -265,7 +265,7 @@ def draw_header(self): def redraw_screen(self): # Trigger a complete redraw self.draw_header() - for key in self.ids.keys(): + for key in self.ids: self.draw_can_bus_message(self.ids[key]['msg']) From 2c69db12ecaeab8db1200e6b02934b4e66bc781d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 03:04:03 +0200 Subject: [PATCH 0167/1235] address review comments --- can/interfaces/pcan/pcan.py | 3 ++- can/listener.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index f772ff576..aa4dec91d 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -15,7 +15,8 @@ from .basic import * try: - import uptime # isn't this from a library? + # use the "uptime" library if available + import uptime import datetime boottimeEpoch = (uptime.boottime() - datetime.datetime.utcfromtimestamp(0)).total_seconds() except: diff --git a/can/listener.py b/can/listener.py index 850a7d4a7..3f2d5a87d 100644 --- a/can/listener.py +++ b/can/listener.py @@ -146,7 +146,7 @@ def on_message_received(self, msg): """ self.buffer.put_nowait(msg) - def get_message(self): + async def get_message(self): """ Retrieve the latest message when awaited for:: @@ -155,7 +155,7 @@ def get_message(self): :rtype: can.Message :return: The CAN message. """ - return self.buffer.get() + return await self.buffer.get() def __aiter__(self): return self From 544f6c3bb8d87613668679a77c1dd4e4f793d989 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 03:20:06 +0200 Subject: [PATCH 0168/1235] remove universal wheel tag in setup.cfg --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index e9a7cb985..a0e8d5b6a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [aliases] test=pytest From e29c162b7388d97cb03a735576e2c3362cdc6c50 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 19 May 2019 12:07:59 +1000 Subject: [PATCH 0169/1235] Change version to 4.0.0-dev --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index d377652d0..ded1c2f94 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.2.0-exception-handling" +__version__ = "4.0.0-dev" log = logging.getLogger('can') From 543163d660eed87edbdc50f026e7fed349e9d22b Mon Sep 17 00:00:00 2001 From: Marcel Kanter Date: Sun, 19 May 2019 07:38:02 +0200 Subject: [PATCH 0170/1235] Fix imports Change version to 4.0.0.dev0 Fix classifiers in setup.py, it should be a list. --- can/__init__.py | 4 ++-- can/interfaces/ixxat/canlib.py | 2 +- can/interfaces/ixxat/exceptions.py | 6 ++---- doc/api.rst | 8 +++++++- setup.py | 4 ++-- test/test_interface_ixxat.py | 8 +++++--- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index d377652d0..25370da36 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,13 +8,13 @@ import logging -__version__ = "3.2.0-exception-handling" +__version__ = "4.0.0.dev0" log = logging.getLogger('can') rc = dict() -from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError +from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError from .listener import Listener, BufferedReader, RedirectReader try: diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 47d88a85b..8ef3ae028 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -17,7 +17,7 @@ import sys from can import BusABC, Message -from can import CanError, CanBackEndError, CanInitializationError, CanOperationError +from can.exceptions import * from can.broadcastmanager import LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 17d51df40..448c6b162 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -7,9 +7,7 @@ Copyright (C) 2019 Marcel Kanter """ -from can import CanError, CanInitializationError - -__all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] +from can import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError class VCITimeout(CanTimeoutError): @@ -17,7 +15,7 @@ class VCITimeout(CanTimeoutError): pass -class VCIError(CanError): +class VCIError(CanOperationError): """ Try to display errors that occur within the wrapped C library nicely. """ pass diff --git a/doc/api.rst b/doc/api.rst index 640f61e2d..7997171be 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -40,4 +40,10 @@ The Notifier object is used as a message distributor for a bus. Errors ------ -.. autoclass:: can.CanError +| CanError +| CanBackendError +| CanInitializationError +| CanOperationError +| CanTimeoutError + +.. automodule:: can.exceptions diff --git a/setup.py b/setup.py index 87c9d489c..c600b7215 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ url="https://github.com/hardbyte/python-can", description="Controller Area Network interface module for Python", long_description=long_description, - classifiers=( + classifiers=[ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", "Programming Language :: Python :: 2.7", @@ -74,7 +74,7 @@ "Topic :: System :: Networking", "Topic :: System :: Hardware :: Hardware Drivers", "Topic :: Utilities" - ), + ], # Code version=version, diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index e57eab368..44a1b61bb 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -1,11 +1,13 @@ """ Unittest for ixxat interface. + +Run only this test: +python setup.py test --addopts "--verbose -s test/test_interface_ixxat.py" """ import unittest import can -from can import CanError, CanBackEndError, CanInitializationError, CanOperationError class SoftwareTestCase(unittest.TestCase): """ @@ -51,14 +53,14 @@ def tearDown(self): def test_bus_creation(self): # non-existent channel -> use arbitrary high value - with self.assertRaises(CanInitializationError): + with self.assertRaises(can.CanInitializationError): bus = can.Bus(interface="ixxat", channel=0xFFFF) def test_send_after_shutdown(self): bus = can.Bus(interface="ixxat", channel=0) msg = can.Message(arbitration_id=0x3FF, dlc=0) bus.shutdown() - with self.assertRaises(CanOperationError): + with self.assertRaises(can.CanOperationError): bus.send(msg) From 3cf73c8c0ff311f83204409224e3adfedcd9039d Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 19 May 2019 15:47:50 +1000 Subject: [PATCH 0171/1235] Update __init__.py --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index ded1c2f94..f8851be5f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "4.0.0-dev" +__version__ = "4.0.0-dev0" log = logging.getLogger('can') From 25db5c9e48b42fe6f2b886a868e7efc692903a13 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 19 May 2019 16:29:32 +1000 Subject: [PATCH 0172/1235] Add a basic tox file to the repository (#599) --- tox.ini | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..7447ccaf2 --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +[tox] +envlist = py36, py37 + +[testenv] +commands = + pip install .[test] + pytest +passenv = CI +recreate = True +usedevelop = True +sitepackages=False From d8828ca69d92874e994daaf34bc601979d737203 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 14:06:29 +0200 Subject: [PATCH 0173/1235] remove unused mock and future dependencies; six will be done in a seperate PR --- setup.py | 2 -- test/serial_test.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index a5d452370..805b582be 100644 --- a/setup.py +++ b/setup.py @@ -29,12 +29,10 @@ } tests_require = [ - 'mock~=2.0', 'pytest~=4.3', 'pytest-timeout~=1.3', 'pytest-cov~=2.6', 'codecov~=2.0', - 'future', 'six', 'hypothesis' ] + extras_require['serial'] diff --git a/test/serial_test.py b/test/serial_test.py index c49c15354..a29379511 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -8,7 +8,7 @@ """ import unittest -from mock import patch +from unittest.mock import patch import can from can.interfaces.serial.serial_can import SerialBus From 048df91f0a0f4109b520fb44829a00d3f9d690dd Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 19:24:35 +0200 Subject: [PATCH 0174/1235] use new super syntax in can/** --- can/broadcastmanager.py | 8 ++++---- can/ctypesutil.py | 8 ++++---- can/interfaces/canalystii.py | 2 +- can/interfaces/ics_neovi/neovi_bus.py | 7 +++---- can/interfaces/iscan.py | 4 ++-- can/interfaces/ixxat/canlib.py | 4 ++-- can/interfaces/ixxat/exceptions.py | 3 ++- can/interfaces/kvaser/canlib.py | 4 ++-- can/interfaces/nican.py | 4 ++-- can/interfaces/pcan/pcan.py | 4 ++-- can/interfaces/serial/serial_can.py | 2 +- can/interfaces/slcan.py | 2 +- can/interfaces/socketcan/socketcan.py | 6 +++--- can/interfaces/systec/exceptions.py | 9 ++++++--- can/interfaces/systec/structures.py | 6 +++--- can/interfaces/systec/ucanbus.py | 6 +++--- can/interfaces/usb2can/usb2canInterface.py | 4 ++-- can/interfaces/vector/canlib.py | 2 +- can/interfaces/vector/exceptions.py | 3 +-- can/interfaces/virtual.py | 2 +- can/io/asc.py | 6 +++--- can/io/blf.py | 6 +++--- can/io/canutils.py | 4 ++-- can/io/csv.py | 4 ++-- can/io/generic.py | 2 +- can/io/printer.py | 2 +- can/io/sqlite.py | 6 +++--- can/thread_safe_bus.py | 2 +- can/viewer.py | 10 +++++----- 29 files changed, 67 insertions(+), 65 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 564d6ee49..05a27197a 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -44,7 +44,7 @@ def __init__(self, message, period): self.can_id = message.arbitration_id self.arbitration_id = message.arbitration_id self.period = period - super(CyclicSendTaskABC, self).__init__() + super().__init__() class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): @@ -57,7 +57,7 @@ def __init__(self, message, period, duration): :param float duration: The duration to keep sending this message at given rate. """ - super(LimitedDurationCyclicSendTaskABC, self).__init__(message, period) + super().__init__(message, period) self.duration = duration @@ -99,7 +99,7 @@ def __init__(self, channel, message, count, initial_period, subsequent_period): :param float initial_period: :param float subsequent_period: """ - super(MultiRateCyclicSendTaskABC, self).__init__(channel, message, subsequent_period) + super().__init__(channel, message, subsequent_period) class ThreadBasedCyclicSendTask(ModifiableCyclicTaskABC, @@ -108,7 +108,7 @@ class ThreadBasedCyclicSendTask(ModifiableCyclicTaskABC, """Fallback cyclic send task using thread.""" def __init__(self, bus, lock, message, period, duration=None): - super(ThreadBasedCyclicSendTask, self).__init__(message, period, duration) + super().__init__(message, period, duration) self.bus = bus self.lock = lock self.stopped = True diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 8a69b8df9..1df53e5fc 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -58,9 +58,9 @@ class CLibrary_Win32(_LibBase, LibraryMixin): def __init__(self, library_or_path): if (isinstance(library_or_path, str)): - super(CLibrary_Win32, self).__init__(library_or_path) + super().__init__(library_or_path) else: - super(CLibrary_Win32, self).__init__(library_or_path._name, library_or_path._handle) + super().__init__(library_or_path._name, library_or_path._handle) @property def function_type(self): @@ -72,9 +72,9 @@ class CLibrary_Unix(ctypes.CDLL, LibraryMixin): def __init__(self, library_or_path): if (isinstance(library_or_path, str)): - super(CLibrary_Unix, self).__init__(library_or_path) + super().__init__(library_or_path) else: - super(CLibrary_Unix, self).__init__(library_or_path._name, library_or_path._handle) + super().__init__(library_or_path._name, library_or_path._handle) @property def function_type(self): diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 2d4c69fcb..2626f607b 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -78,7 +78,7 @@ def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can :param Timing1: :param can_filters: filters for packet """ - super(CANalystIIBus, self).__init__(channel, can_filters) + super().__init__(channel, can_filters) if isinstance(channel, (list, tuple)): self.channels = channel diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 4baee6177..b5218a4cf 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -45,7 +45,7 @@ def __init__( self, error_number, description_short, description_long, severity, restart_needed ): - super(ICSApiError, self).__init__(description_short) + super().__init__(description_short) self.error_number = error_number self.description_short = description_short self.description_long = description_long @@ -97,8 +97,7 @@ def __init__(self, channel, can_filters=None, **kwargs): if ics is None: raise ImportError('Please install python-ics') - super(NeoViBus, self).__init__( - channel=channel, can_filters=can_filters, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) logger.info("CAN Filters: {}".format(can_filters)) logger.info("Got configuration of: {}".format(kwargs)) @@ -174,7 +173,7 @@ def get_serial_number(device): return str(device.SerialNumber) def shutdown(self): - super(NeoViBus, self).shutdown() + super().shutdown() ics.close_device(self.dev) @staticmethod diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index bfca3fdd3..bb02d261e 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -86,7 +86,7 @@ def __init__(self, channel, bitrate=500000, poll_interval=0.01, **kwargs): self.poll_interval = poll_interval iscan.isCAN_DeviceInitEx(self.channel, self.BAUDRATES[bitrate]) - super(IscanBus, self).__init__(channel=channel, bitrate=bitrate, + super().__init__(channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs) def _recv_internal(self, timeout): @@ -159,7 +159,7 @@ class IscanError(CanError): } def __init__(self, function, error_code, arguments): - super(IscanError, self).__init__() + super().__init__() # :Status code self.error_code = error_code # :Function that failed diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 63b422035..b0c9e8d3a 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -389,7 +389,7 @@ def __init__(self, channel, can_filters=None, **kwargs): except (VCITimeout, VCIRxQueueEmptyError): break - super(IXXATBus, self).__init__(channel=channel, can_filters=None, **kwargs) + super().__init__(channel=channel, can_filters=None, **kwargs) def _inWaiting(self): try: @@ -532,7 +532,7 @@ class CyclicSendTask(LimitedDurationCyclicSendTaskABC, """A message in the cyclic transmit list.""" def __init__(self, scheduler, msg, period, duration, resolution): - super(CyclicSendTask, self).__init__(msg, period, duration) + super().__init__(msg, period, duration) self._scheduler = scheduler self._index = None self._count = int(duration / period) if duration else 0 diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index ac1700dca..dde869032 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -23,8 +23,9 @@ class VCIError(CanError): class VCIRxQueueEmptyError(VCIError): """ Wraps the VCI_E_RXQUEUE_EMPTY error """ + def __init__(self): - super(VCIRxQueueEmptyError, self).__init__("Receive queue is empty") + super().__init__("Receive queue is empty") class VCIDeviceNotFoundError(CanError): diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index fdde4ef1e..6506a962d 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -67,7 +67,7 @@ class CANLIBError(CanError): """ def __init__(self, function, error_code, arguments): - super(CANLIBError, self).__init__() + super().__init__() self.error_code = error_code self.function = function self.arguments = arguments @@ -444,7 +444,7 @@ def __init__(self, channel, can_filters=None, **kwargs): self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) self._is_filtered = False - super(KvaserBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) def _apply_filters(self, filters): if filters and len(filters) == 1: diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 0e962cd2f..9953a960f 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -201,7 +201,7 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **k self.handle = ctypes.c_ulong() nican.ncOpenObject(channel, ctypes.byref(self.handle)) - super(NicanBus, self).__init__(channel=channel, + super().__init__(channel=channel, can_filters=can_filters, bitrate=bitrate, log_errors=log_errors, **kwargs) @@ -308,7 +308,7 @@ class NicanError(CanError): """Error from NI-CAN driver.""" def __init__(self, function, error_code, arguments): - super(NicanError, self).__init__() + super().__init__() #: Status code self.error_code = error_code #: Function that failed diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index aa4dec91d..f8291fc2a 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -195,7 +195,7 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) - super(PcanBus, self).__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs) + super().__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs) def _get_formatted_error(self, error): """ @@ -399,7 +399,7 @@ def flash(self, flash): self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_CHANNEL_IDENTIFYING, bool(flash)) def shutdown(self): - super(PcanBus, self).shutdown() + super().shutdown() self.m_objPCANBasic.Uninitialize(self.m_PcanHandle) @property diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 500614868..a30b7d6e5 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -58,7 +58,7 @@ def __init__(self, channel, baudrate=115200, timeout=0.1, rtscts=False, self.ser = serial.serial_for_url( channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts) - super(SerialBus, self).__init__(channel=channel, *args, **kwargs) + super().__init__(channel=channel, *args, **kwargs) def shutdown(self): """ diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 70b3dc27b..cd2dffa60 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -99,7 +99,7 @@ def __init__(self, channel, ttyBaudrate=115200, bitrate=None, self.open() - super(slcanBus, self).__init__(channel, ttyBaudrate=115200, + super().__init__(channel, ttyBaudrate=115200, bitrate=None, rtscts=False, **kwargs) def write(self, string): diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index dc91471a5..d178da2b3 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -285,7 +285,7 @@ def __init__(self, bcm_socket, message, period, duration=None): :param float period: The rate in seconds at which to send the message. :param float duration: Approximate duration in seconds to send the message. """ - super(CyclicSendTask, self).__init__(message, period, duration) + super().__init__(message, period, duration) self.bcm_socket = bcm_socket self.duration = duration self._tx_setup(message) @@ -345,7 +345,7 @@ class MultiRateCyclicSendTask(CyclicSendTask): """ def __init__(self, channel, message, count, initial_period, subsequent_period): - super(MultiRateCyclicSendTask, self).__init__(channel, message, subsequent_period) + super().__init__(channel, message, subsequent_period) # Create a low level packed frame to pass to the kernel frame = build_can_frame(message) @@ -500,7 +500,7 @@ def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): bind_socket(self.socket, channel) kwargs.update({'receive_own_messages': receive_own_messages, 'fd': fd}) - super(SocketcanBus, self).__init__(channel=channel, **kwargs) + super().__init__(channel=channel, **kwargs) def shutdown(self): """Stops all active periodic tasks and closes the socket.""" diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index d3525cd88..65d85d180 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -6,6 +6,7 @@ class UcanException(CanError): """ Base class for USB can errors. """ + def __init__(self, result, func, arguments): self.result = result.value self.func = func @@ -20,7 +21,7 @@ def __str__(self): class UcanError(UcanException): """ Exception class for errors from USB-CAN-library. """ def __init__(self, result, func, arguments): - super(UcanError, self).__init__(result, func, arguments) + super().__init__(result, func, arguments) self.return_msgs = { ReturnCode.ERR_RESOURCE: "could not created a resource (memory, handle, ...)", ReturnCode.ERR_MAXMODULES: "the maximum number of opened modules is reached", @@ -46,8 +47,9 @@ def __init__(self, result, func, arguments): class UcanCmdError(UcanException): """ Exception class for errors from firmware in USB-CANmodul.""" + def __init__(self, result, func, arguments): - super(UcanCmdError, self).__init__(result, func, arguments) + super().__init__(result, func, arguments) self.return_msgs = { ReturnCode.ERRCMD_NOTEQU: "the received response does not match to the transmitted command", ReturnCode.ERRCMD_REGTST: "no access to the CAN controller", @@ -69,8 +71,9 @@ def __init__(self, result, func, arguments): class UcanWarning(UcanException): """ Exception class for warnings, the function has been executed anyway. """ + def __init__(self, result, func, arguments): - super(UcanWarning, self).__init__(result, func, arguments) + super().__init__(result, func, arguments) self.return_msgs = { ReturnCode.WARN_NODATA: "no CAN messages received", ReturnCode.WARN_SYS_RXOVERRUN: "overrun in receive buffer of the kernel driver", diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index a521e044f..32af73aa1 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -36,7 +36,7 @@ class CanMsg(Structure): ] def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=[]): - super(CanMsg, self).__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) + super().__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) def __eq__(self, other): if not isinstance(other, CanMsg): @@ -121,8 +121,8 @@ class InitCanParam(Structure): ] def __init__(self, mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries): - super(InitCanParam, self).__init__(sizeof(InitCanParam), mode, BTR >> 8, BTR, OCR, AMR, ACR, - baudrate, rx_buffer_entries, tx_buffer_entries) + super().__init__(sizeof(InitCanParam), mode, BTR >> 8, BTR, OCR, AMR, ACR, + baudrate, rx_buffer_entries, tx_buffer_entries) def __eq__(self, other): if not isinstance(other, InitCanParam): diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index d074a3028..56fcd598d 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -18,7 +18,7 @@ class Ucan(UcanServer): """ def __init__(self): - super(Ucan, self).__init__() + super().__init__() self._msg_received_event = Event() def can_msg_received_event(self, channel): @@ -29,7 +29,7 @@ def read_can_msg(self, channel, count, timeout): if self.get_msg_pending(channel, PendingFlags.PENDING_FLAG_RX_DLL) == 0: if not self._msg_received_event.wait(timeout): return None, False - return super(Ucan, self).read_can_msg(channel, 1) + return super().read_can_msg(channel, 1) class UcanBus(BusABC): @@ -132,7 +132,7 @@ def __init__(self, channel, can_filters=None, **kwargs): self.channel, self._ucan.get_baudrate_message(self.BITRATES[bitrate]) ) - super(UcanBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) def _recv_internal(self, timeout): message, _ = self._ucan.read_can_msg(self.channel, 1, timeout) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 1f2b38c32..e5693506b 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -109,8 +109,8 @@ def __init__(self, channel=None, dll="usb2can.dll", flags=0x00000008, connector = "{}; {}".format(device_id, baudrate) self.handle = self.can.open(connector, flags) - super(Usb2canBus, self).__init__(channel=channel, dll=dll, flags=flags, - bitrate=bitrate, *args, **kwargs) + super().__init__(channel=channel, dll=dll, flags=flags, bitrate=bitrate, + *args, **kwargs) def send(self, msg, timeout=None): tx = message_convert_tx(msg) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 251b9fa56..1ee98380c 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -210,7 +210,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self._time_offset = time.time() - offset.value * 1e-9 self._is_filtered = False - super(VectorBus, self).__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) def _apply_filters(self, filters): if filters: diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 8715c276f..b88f6c527 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -10,5 +10,4 @@ class VectorError(CanError): def __init__(self, error_code, error_string, function): self.error_code = error_code - text = "%s failed (%s)" % (function, error_string) - super(VectorError, self).__init__(text) + super().__init__(f"{function} failed ({error_string})") diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 5594674c1..66b951c22 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -45,7 +45,7 @@ class VirtualBus(BusABC): """ def __init__(self, channel=None, receive_own_messages=False, rx_queue_size=0, **kwargs): - super(VirtualBus, self).__init__(channel=channel, receive_own_messages=receive_own_messages, **kwargs) + super().__init__(channel=channel, receive_own_messages=receive_own_messages, **kwargs) # the channel identifier may be an arbitrary object self.channel_id = channel diff --git a/can/io/asc.py b/can/io/asc.py index 6cbfeb60d..419b46247 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -37,7 +37,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super(ASCReader, self).__init__(file, mode='r') + super().__init__(file, mode='r') @staticmethod def _extract_can_id(str_can_id): @@ -140,7 +140,7 @@ def __init__(self, file, channel=1): :param channel: a default channel to use when the message does not have a channel set """ - super(ASCWriter, self).__init__(file, mode='w') + super().__init__(file, mode='w') self.channel = channel # write start of file header @@ -157,7 +157,7 @@ def __init__(self, file, channel=1): def stop(self): if not self.file.closed: self.file.write("End TriggerBlock\n") - super(ASCWriter, self).stop() + super().stop() def log_event(self, message, timestamp=None): """Add a message to the log file. diff --git a/can/io/blf.py b/can/io/blf.py index 55aab0715..36d116950 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -140,7 +140,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in binary read mode, not text read mode. """ - super(BLFReader, self).__init__(file, mode='rb') + super().__init__(file, mode='rb') data = self.file.read(FILE_HEADER_STRUCT.size) header = FILE_HEADER_STRUCT.unpack(data) if header[0] != b"LOGG": @@ -310,7 +310,7 @@ def __init__(self, file, channel=1): If this is a file-like object, is has to opened in binary write mode, not text write mode. """ - super(BLFWriter, self).__init__(file, mode='wb') + super().__init__(file, mode='wb') self.channel = channel # Header will be written after log is done self.file.write(b"\x00" * FILE_HEADER_SIZE) @@ -438,7 +438,7 @@ def stop(self): """Stops logging and closes the file.""" self._flush() filesize = self.file.tell() - super(BLFWriter, self).stop() + super().stop() # Write header in the beginning of the file header = [b"LOGG", FILE_HEADER_SIZE, diff --git a/can/io/canutils.py b/can/io/canutils.py index 2d1232c74..304c37f9c 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -39,7 +39,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super(CanutilsLogReader, self).__init__(file, mode='r') + super().__init__(file, mode='r') def __iter__(self): for line in self.file: @@ -106,7 +106,7 @@ def __init__(self, file, channel="vcan0", append=False): the file, else the file is truncated """ mode = 'a' if append else 'w' - super(CanutilsLogWriter, self).__init__(file, mode=mode) + super().__init__(file, mode=mode) self.channel = channel self.last_timestamp = None diff --git a/can/io/csv.py b/can/io/csv.py index c85e6339f..8bafdf180 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -50,7 +50,7 @@ def __init__(self, file, append=False): written header line """ mode = 'a' if append else 'w' - super(CSVWriter, self).__init__(file, mode=mode) + super().__init__(file, mode=mode) # Write a header row if not append: @@ -85,7 +85,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super(CSVReader, self).__init__(file, mode='r') + super().__init__(file, mode='r') def __iter__(self): # skip the header line diff --git a/can/io/generic.py b/can/io/generic.py index b452d8404..abb03345e 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -34,7 +34,7 @@ def __init__(self, file, mode='rt'): self.file = open(file, mode) # for multiple inheritance - super(BaseIOHandler, self).__init__() + super().__init__() def __enter__(self): return self diff --git a/can/io/printer.py b/can/io/printer.py index 7eac86935..c67285581 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -30,7 +30,7 @@ def __init__(self, file=None): write mode, not binary write mode. """ self.write_to_file = file is not None - super(Printer, self).__init__(file, mode='w') + super().__init__(file, mode='w') def on_message_received(self, msg): if self.write_to_file: diff --git a/can/io/sqlite.py b/can/io/sqlite.py index f61117384..94d0af485 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -43,7 +43,7 @@ def __init__(self, file, table_name="messages"): do not accept file-like objects as the `file` parameter. It also runs in ``append=True`` mode all the time. """ - super(SqliteReader, self).__init__(file=None) + super().__init__(file=None) self._conn = sqlite3.connect(file) self._cursor = self._conn.cursor() self.table_name = table_name @@ -81,7 +81,7 @@ def read_all(self): def stop(self): """Closes the connection to the database. """ - super(SqliteReader, self).stop() + super().stop() self._conn.close() @@ -139,7 +139,7 @@ def __init__(self, file, table_name="messages"): .. warning:: In contrary to all other readers/writers the Sqlite handlers do not accept file-like objects as the `file` parameter. """ - super(SqliteWriter, self).__init__(file=None) + super().__init__(file=None) self.table_name = table_name self._db_filename = file self._stop_running_event = threading.Event() diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 648285b05..6034e7b63 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -54,7 +54,7 @@ def __init__(self, *args, **kwargs): if import_exc is not None: raise import_exc - super(ThreadSafeBus, self).__init__(Bus(*args, **kwargs)) + super().__init__(Bus(*args, **kwargs)) # now, BusABC.send_periodic() does not need a lock anymore, but the # implementation still requires a context manager diff --git a/can/viewer.py b/can/viewer.py index cecc17037..d915a9ddb 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -277,11 +277,11 @@ def _get_default_metavar_for_optional(self, action): def _format_usage(self, usage, actions, groups, prefix): # Use uppercase for "Usage:" text - return super(SmartFormatter, self)._format_usage(usage, actions, groups, 'Usage: ') + return super()._format_usage(usage, actions, groups, 'Usage: ') def _format_args(self, action, default_metavar): if action.nargs != argparse.REMAINDER and action.nargs != argparse.ONE_OR_MORE: - return super(SmartFormatter, self)._format_args(action, default_metavar) + return super()._format_args(action, default_metavar) # Use the metavar if "REMAINDER" or "ONE_OR_MORE" is set get_metavar = self._metavar_formatter(action, default_metavar) @@ -289,7 +289,7 @@ def _format_args(self, action, default_metavar): def _format_action_invocation(self, action): if not action.option_strings or action.nargs == 0: - return super(SmartFormatter, self)._format_action_invocation(action) + return super()._format_action_invocation(action) # Modified so "-s ARGS, --long ARGS" is replaced with "-s, --long ARGS" else: @@ -307,14 +307,14 @@ def _split_lines(self, text, width): # Allow to manually split the lines if text.startswith('R|'): return text[2:].splitlines() - return super(SmartFormatter, self)._split_lines(text, width) + return super()._split_lines(text, width) def _fill_text(self, text, width, indent): if text.startswith('R|'): # noinspection PyTypeChecker return ''.join(indent + line + '\n' for line in text[2:].splitlines()) else: - return super(SmartFormatter, self)._fill_text(text, width, indent) + return super()._fill_text(text, width, indent) def parse_args(args): From 1b71a92d9235fbb58b9f450df962d613c2d8d642 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 19:25:06 +0200 Subject: [PATCH 0175/1235] add missing super() call --- can/interfaces/systec/structures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index 32af73aa1..cf84a01e2 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -273,7 +273,7 @@ class ChannelInfo(Structure): ] def __init__(self): - super(ChannelInfo, self).__init__(sizeof(ChannelInfo)) + super().__init__(sizeof(ChannelInfo)) def __eq__(self, other): if not isinstance(other, ChannelInfo): From c3a87fd063c907e8463522b487345d3f2e0e4d5e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 18 May 2019 19:28:02 +0200 Subject: [PATCH 0176/1235] use new super() syntax in test/** --- test/logformats_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index d9551e5d6..d658f4e83 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -313,7 +313,7 @@ class TestAscFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" def _setup_instance(self): - super(TestAscFileFormat, self)._setup_instance_helper( + super()._setup_instance_helper( can.ASCWriter, can.ASCReader, check_fd=False, check_comments=True, @@ -325,7 +325,7 @@ class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader""" def _setup_instance(self): - super(TestBlfFileFormat, self)._setup_instance_helper( + super()._setup_instance_helper( can.BLFWriter, can.BLFReader, binary_file=True, check_fd=False, @@ -359,7 +359,7 @@ class TestCanutilsFileFormat(ReaderWriterTest): """Tests can.CanutilsLogWriter and can.CanutilsLogReader""" def _setup_instance(self): - super(TestCanutilsFileFormat, self)._setup_instance_helper( + super()._setup_instance_helper( can.CanutilsLogWriter, can.CanutilsLogReader, check_fd=False, test_append=True, check_comments=False, @@ -371,7 +371,7 @@ class TestCsvFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" def _setup_instance(self): - super(TestCsvFileFormat, self)._setup_instance_helper( + super()._setup_instance_helper( can.CSVWriter, can.CSVReader, check_fd=False, test_append=True, check_comments=False, @@ -383,7 +383,7 @@ class TestSqliteDatabaseFormat(ReaderWriterTest): """Tests can.SqliteWriter and can.SqliteReader""" def _setup_instance(self): - super(TestSqliteDatabaseFormat, self)._setup_instance_helper( + super()._setup_instance_helper( can.SqliteWriter, can.SqliteReader, check_fd=False, test_append=True, check_comments=False, From d975332d8f580a81907ea276e603243cfac1672c Mon Sep 17 00:00:00 2001 From: Karl Date: Thu, 16 May 2019 22:31:06 -0700 Subject: [PATCH 0177/1235] Enable pylint on Travis CI builds Run pylint on all Travis CI builds for rules that we currently are conformant to. This adds a .pylintrc-wip file, which is intended to be a temporary stop-gap that contains the current rules that the project is conformant to. This allows us to incrementally enable pylint rules and have them them enforced in subsequent code, rather than fixing the violations all at once. The .pylintrc file is intended to be the file containing all the preferred set of rules that the project will eventually be compliant against. --- .pylintrc | 570 +++++++++++++++++++++++++++++ .pylintrc-wip | 823 ++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 10 + requirements-lint.txt | 1 + 4 files changed, 1404 insertions(+) create mode 100644 .pylintrc create mode 100644 .pylintrc-wip create mode 100644 requirements-lint.txt diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..5a2613994 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,570 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, while `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package.. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/.pylintrc-wip b/.pylintrc-wip new file mode 100644 index 000000000..76429f197 --- /dev/null +++ b/.pylintrc-wip @@ -0,0 +1,823 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=blacklisted-name, + invalid-name, + missing-docstring, + empty-docstring, + unneeded-not, + singleton-comparison, + misplaced-comparison-constant, + unidiomatic-typecheck, + consider-using-enumerate, + consider-iterating-dictionary, + bad-classmethod-argument, + bad-mcs-method-argument, + bad-mcs-classmethod-argument, + single-string-used-for-slots, + line-too-long, + too-many-lines, + trailing-whitespace, + missing-final-newline, + trailing-newlines, + multiple-statements, + superfluous-parens, + bad-whitespace, + mixed-line-endings, + unexpected-line-ending-format, + bad-continuation, + wrong-spelling-in-comment, + wrong-spelling-in-docstring, + invalid-characters-in-docstring, + multiple-imports, + wrong-import-order, + ungrouped-imports, + wrong-import-position, + useless-import-alias, + len-as-condition, + syntax-error, + unrecognized-inline-option, + bad-option-value, + init-is-generator, + return-in-init, + function-redefined, + not-in-loop, + return-outside-function, + yield-outside-function, + return-arg-in-generator, + nonexistent-operator, + duplicate-argument-name, + abstract-class-instantiated, + bad-reversed-sequence, + too-many-star-expressions, + invalid-star-assignment-target, + star-needs-assignment-target, + nonlocal-and-global, + continue-in-finally, + nonlocal-without-binding, + used-prior-global-declaration, + misplaced-format-function, + method-hidden, + access-member-before-definition, + no-method-argument, + no-self-argument, + invalid-slots-object, + assigning-non-slot, + invalid-slots, + inherit-non-class, + inconsistent-mro, + duplicate-bases, + non-iterator-returned, + unexpected-special-method-signature, + invalid-length-returned, + import-error, + relative-beyond-top-level, + used-before-assignment, + undefined-variable, + undefined-all-variable, + invalid-all-object, + no-name-in-module, + unpacking-non-sequence, + bad-except-order, + raising-bad-type, + bad-exception-context, + misplaced-bare-raise, + raising-non-exception, + notimplemented-raised, + catching-non-exception, + bad-super-call, + missing-super-argument, + no-member, + not-callable, + assignment-from-no-return, + no-value-for-parameter, + too-many-function-args, + unexpected-keyword-arg, + redundant-keyword-arg, + missing-kwoa, + invalid-sequence-index, + invalid-slice-index, + assignment-from-none, + not-context-manager, + invalid-unary-operand-type, + unsupported-binary-operation, + repeated-keyword, + not-an-iterable, + not-a-mapping, + unsupported-membership-test, + unsubscriptable-object, + unsupported-assignment-operation, + unsupported-delete-operation, + invalid-metaclass, + unhashable-dict-key, + logging-unsupported-format, + logging-format-truncated, + logging-too-many-args, + logging-too-few-args, + bad-format-character, + truncated-format-string, + mixed-format-string, + format-needs-mapping, + missing-format-string-key, + too-many-format-args, + too-few-format-args, + bad-string-format-type, + bad-str-strip-call, + invalid-envvar-value, + print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + yield-inside-async-function, + not-async-context-manager, + fatal, + astroid-error, + parse-error, + method-check-failed, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + c-extension-no-member, + literal-comparison, + comparison-with-itself, + no-self-use, + no-classmethod-decorator, + no-staticmethod-decorator, + useless-object-inheritance, + cyclic-import, + duplicate-code, + too-many-ancestors, + too-many-instance-attributes, + too-few-public-methods, + too-many-public-methods, + too-many-return-statements, + too-many-branches, + too-many-arguments, + too-many-locals, + too-many-statements, + too-many-boolean-expressions, + consider-merging-isinstance, + too-many-nested-blocks, + simplifiable-if-statement, + redefined-argument-from-local, + no-else-return, + consider-using-ternary, + trailing-comma-tuple, + stop-iteration-return, + simplify-boolean-expression, + inconsistent-return-statements, + useless-return, + consider-swap-variables, + consider-using-join, + consider-using-in, + consider-using-get, + chained-comparison, + consider-using-dict-comprehension, + consider-using-set-comprehension, + simplifiable-if-expression, + no-else-raise, + unreachable, + dangerous-default-value, + pointless-statement, + pointless-string-statement, + expression-not-assigned, + unnecessary-pass, + unnecessary-lambda, + duplicate-key, + assign-to-new-keyword, + useless-else-on-loop, + exec-used, + eval-used, + confusing-with-statement, + using-constant-test, + comparison-with-callable, + lost-exception, + assert-on-tuple, + attribute-defined-outside-init, + bad-staticmethod-argument, + protected-access, + arguments-differ, + signature-differs, + abstract-method, + super-init-not-called, + no-init, + non-parent-init-called, + useless-super-delegation, + unnecessary-semicolon, + bad-indentation, + mixed-indentation, + wildcard-import, + deprecated-module, + relative-import, + reimported, + import-self, + misplaced-future, + fixme, + invalid-encoded-data, + global-variable-undefined, + global-variable-not-assigned, + global-statement, + global-at-module-level, + unused-import, + unused-variable, + unused-argument, + unused-wildcard-import, + redefined-outer-name, + redefined-builtin, + redefine-in-handler, + undefined-loop-variable, + unbalanced-tuple-unpacking, + cell-var-from-loop, + possibly-unused-variable, + self-cls-assignment, + bare-except, + broad-except, + duplicate-except, + try-except-raise, + binary-op-exception, + raising-format-tuple, + wrong-exception-operation, + keyword-arg-before-vararg, + logging-not-lazy, + logging-format-interpolation, + logging-fstring-interpolation, + bad-format-string-key, + unused-format-string-key, + bad-format-string, + missing-format-argument-key, + unused-format-string-argument, + format-combined-specification, + missing-format-attribute, + invalid-format-index, + duplicate-string-formatting-argument, + anomalous-backslash-in-string, + anomalous-unicode-escape-in-string, + implicit-str-concat-in-sequence, + bad-open-mode, + boolean-datetime, + redundant-unittest-assert, + deprecated-method, + bad-thread-instantiation, + shallow-copy-environ, + invalid-envvar-default, + subprocess-popen-preexec-fn, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, while `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package.. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/.travis.yml b/.travis.yml index 6e84eb22a..29fac0557 100644 --- a/.travis.yml +++ b/.travis.yml @@ -82,6 +82,16 @@ jobs: # -a Write all files # -n nitpicky - python -m sphinx -an doc build + - stage: linter + name: "Linter Checks" + python: "3.7" + before_install: + - travis_retry pip install -r requirements-lint.txt + script: + # Slowly enable all pylint warnings by adding addressed classes of + # warnings to the .pylintrc-wip file to prevent them from being + # re-introduced + - pylint --rcfile=.pylintrc-wip can/ - stage: deploy name: "PyPi Deployment" python: "3.7" diff --git a/requirements-lint.txt b/requirements-lint.txt new file mode 100644 index 000000000..514974539 --- /dev/null +++ b/requirements-lint.txt @@ -0,0 +1 @@ +pylint==2.3.1 From 9cccf29d9e1c3079ca0d5a8ec7f31184c3d90762 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 22:56:31 +0200 Subject: [PATCH 0178/1235] remove trailing whitespace --- .pylintrc-wip | 3 --- can/interfaces/kvaser/canlib.py | 2 +- can/interfaces/pcan/pcan.py | 4 ++-- can/interfaces/vector/canlib.py | 15 +++++++-------- can/interfaces/vector/vxlapi.py | 4 ++-- can/listener.py | 8 ++++---- can/message.py | 2 +- can/util.py | 6 +++--- 8 files changed, 20 insertions(+), 24 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 76429f197..c9a7f9a9f 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -76,9 +76,6 @@ disable=blacklisted-name, single-string-used-for-slots, line-too-long, too-many-lines, - trailing-whitespace, - missing-final-newline, - trailing-newlines, multiple-statements, superfluous-parens, bad-whitespace, diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 6506a962d..483814557 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -396,7 +396,7 @@ def __init__(self, channel, can_filters=None, **kwargs): canstat.canIOCTL_SET_TIMER_SCALE, ctypes.byref(ctypes.c_long(TIMESTAMP_RESOLUTION)), 4) - + if fd: if 'tseg1' not in kwargs and bitrate in BITRATE_FD: # Use predefined bitrate for arbitration diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index f8291fc2a..558150c0d 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -175,7 +175,7 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 f_clock = "{}={}".format('f_clock_mhz', kwargs.get('f_clock_mhz', None)) else: f_clock = "{}={}".format('f_clock', kwargs.get('f_clock', None)) - + fd_parameters_values = [f_clock] + ["{}={}".format(key, kwargs.get(key, None)) for key in pcan_fd_parameter_list if kwargs.get(key, None) is not None] self.fd_bitrate = ' ,'.join(fd_parameters_values).encode("ascii") @@ -348,7 +348,7 @@ def send(self, msg, timeout=None): CANMsg = TPCANMsgFDMac() else: CANMsg = TPCANMsgFD() - + # configure the message. ID, Length of data, message type and data CANMsg.ID = msg.arbitration_id CANMsg.DLC = len2dlc(msg.dlc) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 1ee98380c..3a926e289 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -175,7 +175,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.canFdConf.sjwDbr = ctypes.c_uint(sjwDbr) self.canFdConf.tseg1Dbr = ctypes.c_uint(tseg1Dbr) self.canFdConf.tseg2Dbr = ctypes.c_uint(tseg2Dbr) - + vxlapi.xlCanFdSetConfiguration(self.port_handle, self.mask, self.canFdConf) LOG.info('SetFdConfig.: ABaudr.=%u, DBaudr.=%u', self.canFdConf.arbitrationBitRate, self.canFdConf.dataBitRate) LOG.info('SetFdConfig.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u', self.canFdConf.sjwAbr, self.canFdConf.tseg1Abr, self.canFdConf.tseg2Abr) @@ -336,14 +336,14 @@ def send(self, msg, timeout=None): flags |= vxlapi.XL_CAN_TXMSG_FLAG_BRS if msg.is_remote_frame: flags |= vxlapi.XL_CAN_TXMSG_FLAG_RTR - + message_count = 1 MsgCntSent = ctypes.c_uint(1) - + XLcanTxEvent = vxlapi.XLcanTxEvent() XLcanTxEvent.tag = vxlapi.XL_CAN_EV_TAG_TX_MSG XLcanTxEvent.transId = 0xffff - + XLcanTxEvent.tagData.canMsg.canId = msg_id XLcanTxEvent.tagData.canMsg.msgFlags = flags XLcanTxEvent.tagData.canMsg.dlc = len2dlc(msg.dlc) @@ -356,10 +356,10 @@ def send(self, msg, timeout=None): flags |= vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME message_count = ctypes.c_uint(1) - + xl_event = vxlapi.XLevent() xl_event.tag = vxlapi.XL_TRANSMIT_MSG - + xl_event.tagData.msg.id = msg_id xl_event.tagData.msg.dlc = msg.dlc xl_event.tagData.msg.flags = flags @@ -367,7 +367,6 @@ def send(self, msg, timeout=None): xl_event.tagData.msg.data[idx] = value vxlapi.xlCanTransmit(self.port_handle, mask, message_count, xl_event) - def flush_tx_buffer(self): vxlapi.xlCanFlushTransmitQueue(self.port_handle, self.mask) @@ -375,7 +374,7 @@ def shutdown(self): vxlapi.xlDeactivateChannel(self.port_handle, self.mask) vxlapi.xlClosePort(self.port_handle) vxlapi.xlCloseDriver() - + def reset(self): vxlapi.xlDeactivateChannel(self.port_handle, self.mask) vxlapi.xlActivateChannel(self.port_handle, self.mask, diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index ae87706c4..e1b93bdc7 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -46,7 +46,7 @@ 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_EF = 0x0200 XL_CAN_STD = 1 XL_CAN_EXT = 2 @@ -114,7 +114,7 @@ class s_rxTagData(ctypes.Union): class s_txTagData(ctypes.Union): _fields_ = [('canMsg', s_xl_can_tx_msg)] -# BASIC events +# BASIC events XLeventTag = ctypes.c_ubyte class XLevent(ctypes.Structure): diff --git a/can/listener.py b/can/listener.py index 3f2d5a87d..b3cc3a886 100644 --- a/can/listener.py +++ b/can/listener.py @@ -128,7 +128,7 @@ class AsyncBufferedReader(Listener): """A message buffer for use with :mod:`asyncio`. See :ref:`asyncio` for how to use with :class:`can.Notifier`. - + Can also be used as an asynchronous iterator:: async for msg in reader: @@ -141,7 +141,7 @@ def __init__(self, loop=None): def on_message_received(self, msg): """Append a message to the buffer. - + Must only be called inside an event loop! """ self.buffer.put_nowait(msg) @@ -149,7 +149,7 @@ def on_message_received(self, msg): async def get_message(self): """ Retrieve the latest message when awaited for:: - + msg = await reader.get_message() :rtype: can.Message @@ -159,6 +159,6 @@ async def get_message(self): def __aiter__(self): return self - + def __anext__(self): return self.buffer.get() diff --git a/can/message.py b/can/message.py index cc39642bf..ec2ee6875 100644 --- a/can/message.py +++ b/can/message.py @@ -152,7 +152,7 @@ def __repr__(self): args.append("is_error_frame={}".format(self.is_error_frame)) if self.channel is not None: - args.append("channel={!r}".format(self.channel)) + args.append("channel={!r}".format(self.channel)) data = ["{:#02x}".format(byte) for byte in self.data] args += ["dlc={}".format(self.dlc), diff --git a/can/util.py b/can/util.py index 7e106fde4..2e682548d 100644 --- a/can/util.py +++ b/can/util.py @@ -116,7 +116,7 @@ def load_config(path=None, config=None, context=None): kvaser, socketcan, pcan, usb2can, ixxat, nican, virtual. .. note:: - + The key ``bustype`` is copied to ``interface`` if that one is missing and does never appear in the result. @@ -189,7 +189,7 @@ def load_config(path=None, config=None, context=None): can.log.debug("can config: {}".format(config)) return config - + def set_logging_level(level_name=None): """Set the logging level for the "can" logger. Expects one of: 'critical', 'error', 'warning', 'info', 'debug', 'subdebug' @@ -235,7 +235,7 @@ def channel2int(channel): :param channel: Channel string (e.g. can0, CAN1) or integer - + :returns: Channel integer or `None` if unsuccessful :rtype: int """ From 525fba100bcf288a6d17a2363940b76a1ab361b5 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 22:56:44 +0200 Subject: [PATCH 0179/1235] remove trailing whitespace --- .pylintrc-wip | 1 - 1 file changed, 1 deletion(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index c9a7f9a9f..65e347a03 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -64,7 +64,6 @@ disable=blacklisted-name, invalid-name, missing-docstring, empty-docstring, - unneeded-not, singleton-comparison, misplaced-comparison-constant, unidiomatic-typecheck, From b9883e239e3d49d2fa5b8e7e8b1efa6be3f46943 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 22:59:05 +0200 Subject: [PATCH 0180/1235] fix actual bug by makeing linter more strict --- .pylintrc-wip | 10 ---------- can/interfaces/systec/structures.py | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 65e347a03..439c54f54 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -66,7 +66,6 @@ disable=blacklisted-name, empty-docstring, singleton-comparison, misplaced-comparison-constant, - unidiomatic-typecheck, consider-using-enumerate, consider-iterating-dictionary, bad-classmethod-argument, @@ -93,22 +92,13 @@ disable=blacklisted-name, syntax-error, unrecognized-inline-option, bad-option-value, - init-is-generator, return-in-init, - function-redefined, - not-in-loop, - return-outside-function, - yield-outside-function, - return-arg-in-generator, - nonexistent-operator, duplicate-argument-name, abstract-class-instantiated, bad-reversed-sequence, too-many-star-expressions, invalid-star-assignment-target, star-needs-assignment-target, - nonlocal-and-global, - continue-in-finally, nonlocal-without-binding, used-prior-global-declaration, misplaced-format-function, diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index cf84a01e2..40ce01dfa 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -165,7 +165,7 @@ def rx_buffer_entries(self, rx_buffer_entries): self.m_wNrOfRxBufferEntries = rx @property def tx_buffer_entries(self): return self.m_wNrOfTxBufferEntries - @rx_buffer_entries.setter + @tx_buffer_entries.setter def tx_buffer_entries(self, tx_buffer_entries): self.m_wNrOfTxBufferEntries = tx_buffer_entries From 2ab6ff2410b51d01af63ecde122b62a22d268a0b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 23:13:09 +0200 Subject: [PATCH 0181/1235] remove other excluded checks --- .pylintrc-wip | 60 ---------------------- can/interface.py | 4 +- can/interfaces/ixxat/canlib.py | 4 +- can/interfaces/kvaser/canlib.py | 2 +- can/interfaces/nican.py | 2 +- can/interfaces/pcan/pcan.py | 5 +- can/interfaces/serial/serial_can.py | 3 +- can/interfaces/systec/exceptions.py | 6 +-- can/interfaces/systec/ucanbus.py | 2 +- can/interfaces/usb2can/usb2canInterface.py | 4 +- can/interfaces/vector/canlib.py | 2 +- can/thread_safe_bus.py | 6 +-- 12 files changed, 20 insertions(+), 80 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 439c54f54..081bae7a6 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -99,18 +99,12 @@ disable=blacklisted-name, too-many-star-expressions, invalid-star-assignment-target, star-needs-assignment-target, - nonlocal-without-binding, - used-prior-global-declaration, - misplaced-format-function, method-hidden, access-member-before-definition, no-method-argument, no-self-argument, invalid-slots-object, assigning-non-slot, - invalid-slots, - inherit-non-class, - inconsistent-mro, duplicate-bases, non-iterator-returned, unexpected-special-method-signature, @@ -123,27 +117,15 @@ disable=blacklisted-name, invalid-all-object, no-name-in-module, unpacking-non-sequence, - bad-except-order, - raising-bad-type, bad-exception-context, misplaced-bare-raise, raising-non-exception, notimplemented-raised, catching-non-exception, - bad-super-call, - missing-super-argument, - no-member, - not-callable, - assignment-from-no-return, no-value-for-parameter, too-many-function-args, unexpected-keyword-arg, redundant-keyword-arg, - missing-kwoa, - invalid-sequence-index, - invalid-slice-index, - assignment-from-none, - not-context-manager, invalid-unary-operand-type, unsupported-binary-operation, repeated-keyword, @@ -258,15 +240,8 @@ disable=blacklisted-name, super-init-not-called, no-init, non-parent-init-called, - useless-super-delegation, - unnecessary-semicolon, bad-indentation, - mixed-indentation, wildcard-import, - deprecated-module, - relative-import, - reimported, - import-self, misplaced-future, fixme, invalid-encoded-data, @@ -283,57 +258,22 @@ disable=blacklisted-name, redefine-in-handler, undefined-loop-variable, unbalanced-tuple-unpacking, - cell-var-from-loop, possibly-unused-variable, - self-cls-assignment, - bare-except, broad-except, - duplicate-except, - try-except-raise, - binary-op-exception, - raising-format-tuple, - wrong-exception-operation, - keyword-arg-before-vararg, logging-not-lazy, logging-format-interpolation, logging-fstring-interpolation, - bad-format-string-key, - unused-format-string-key, bad-format-string, missing-format-argument-key, unused-format-string-argument, - format-combined-specification, - missing-format-attribute, invalid-format-index, duplicate-string-formatting-argument, - anomalous-backslash-in-string, - anomalous-unicode-escape-in-string, implicit-str-concat-in-sequence, - bad-open-mode, - boolean-datetime, redundant-unittest-assert, - deprecated-method, bad-thread-instantiation, shallow-copy-environ, invalid-envvar-default, subprocess-popen-preexec-fn, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, no-absolute-import, old-division, dict-iter-method, diff --git a/can/interface.py b/can/interface.py index 30bade25c..8eb922051 100644 --- a/can/interface.py +++ b/can/interface.py @@ -64,13 +64,13 @@ class Bus(BusABC): """ @staticmethod - def __new__(cls, channel=None, *args, **kwargs): + def __new__(cls, *args, channel=None, **kwargs): """ Takes the same arguments as :class:`can.BusABC.__init__`. Some might have a special meaning, see below. :param channel: - Set to ``None`` to let it be reloved automatically from the default + Set to ``None`` to let it be resloved automatically from the default configuration. That might fail, see below. Expected type is backend dependent. diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index b0c9e8d3a..0f07d29a5 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -450,7 +450,7 @@ def _recv_internal(self, timeout): elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: pass else: - log.warn("Unexpected message info type") + log.warning("Unexpected message info type") if t0 is not None: remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) @@ -521,7 +521,7 @@ def set_filters(self, can_filers=None): """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. """ if self.__set_filters_has_been_called: - log.warn("using filters is not supported like this, see note on IXXATBus") + log.warning("using filters is not supported like this, see note on IXXATBus") else: # allow the constructor to call this without causing a warning self.__set_filters_has_been_called = True diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 483814557..7e8cd4b4f 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -266,7 +266,7 @@ def init_kvaser_library(): log.debug("Initializing Kvaser CAN library") canInitializeLibrary() log.debug("CAN library initialized") - except: + except Exception: log.warning("Kvaser canlib could not be initialized.") diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 9953a960f..b032e3eee 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -298,7 +298,7 @@ def set_filters(self, can_filers=None): """Unsupported. See note on :class:`~can.interfaces.nican.NicanBus`. """ if self.__set_filters_has_been_called: - logger.warn("using filters is not supported like this, see note on NicanBus") + logger.warning("using filters is not supported like this, see note on NicanBus") else: # allow the constructor to call this without causing a warning self.__set_filters_has_been_called = True diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 558150c0d..2d04ff5f4 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -19,7 +19,7 @@ import uptime import datetime boottimeEpoch = (uptime.boottime() - datetime.datetime.utcfromtimestamp(0)).total_seconds() -except: +except ImportError: boottimeEpoch = 0 try: @@ -62,7 +62,8 @@ class PcanBus(BusABC): - def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000, *args, **kwargs): + def __init__(self, *args, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000, + **kwargs): """A PCAN USB interface to CAN. On top of the usual :class:`~can.Bus` methods provided, diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index a30b7d6e5..1904d28e3 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -31,8 +31,7 @@ class SerialBus(BusABC): """ - def __init__(self, channel, baudrate=115200, timeout=0.1, rtscts=False, - *args, **kwargs): + def __init__(self, channel, *args, baudrate=115200, timeout=0.1, rtscts=False, **kwargs): """ :param str channel: The serial device to open. For example "/dev/ttyS1" or diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index 65d85d180..622403d48 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -11,11 +11,11 @@ def __init__(self, result, func, arguments): self.result = result.value self.func = func self.arguments = arguments - self.return_msgs = NotImplemented + self.return_msgs = {} def __str__(self): - return "Function %s returned %d: %s" % \ - (self.func.__name__, self.result, self.return_msgs.get(self.result, "unknown")) + message = self.return_msgs.get(self.result, "unknown") + return f"Function {self.func.__name__} returned {self.result}: {message}" class UcanError(UcanException): diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 56fcd598d..3238c1711 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -188,7 +188,7 @@ def _detect_available_configs(): configs.append({'interface': 'systec', 'channel': Channel.CHANNEL_CH1, 'device_number': hw_info_ex.device_number}) - except: + except Exception: log.warning("The SYSTEC ucan library has not been initialized.") return configs diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index e5693506b..4e7580feb 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -83,8 +83,8 @@ class Usb2canBus(BusABC): """ - def __init__(self, channel=None, dll="usb2can.dll", flags=0x00000008, - bitrate=500000, *args, **kwargs): + def __init__(self, *args, channel=None, dll="usb2can.dll", flags=0x00000008, + bitrate=500000, **kwargs): self.can = Usb2CanAbstractionLayer(dll) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 3a926e289..6fb49c5fb 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -402,6 +402,6 @@ def get_channel_configs(): vxlapi.xlOpenDriver() vxlapi.xlGetDriverConfig(driver_config) vxlapi.xlCloseDriver() - except: + except Exception: pass return [driver_config.channel[i] for i in range(driver_config.channelCount)] diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 6034e7b63..35f923910 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -64,11 +64,11 @@ def __init__(self, *args, **kwargs): self._lock_send = RLock() self._lock_recv = RLock() - def recv(self, timeout=None, *args, **kwargs): + def recv(self, *args, timeout=None, **kwargs): with self._lock_recv: return self.__wrapped__.recv(timeout=timeout, *args, **kwargs) - def send(self, msg, timeout=None, *args, **kwargs): + def send(self, msg, *args, timeout=None, **kwargs): with self._lock_send: return self.__wrapped__.send(msg, timeout=timeout, *args, **kwargs) @@ -85,7 +85,7 @@ def filters(self, filters): with self._lock_recv: self.__wrapped__.filters = filters - def set_filters(self, filters=None, *args, **kwargs): + def set_filters(self, *args, filters=None, **kwargs): with self._lock_recv: return self.__wrapped__.set_filters(filters=filters, *args, **kwargs) From 684dec27e6bbf28af570eb6e7f99cd8dcddc9b32 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 23:23:30 +0200 Subject: [PATCH 0182/1235] fix other linter stuff --- .pylintrc-wip | 39 +------------------ can/interfaces/ixxat/canlib.py | 4 +- can/interfaces/pcan/basic.py | 12 +++--- can/interfaces/usb2can/usb2canInterface.py | 11 ++---- .../usb2can/usb2canabstractionlayer.py | 14 +++---- 5 files changed, 22 insertions(+), 58 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 081bae7a6..0cb2fea88 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -205,16 +205,7 @@ disable=blacklisted-name, simplify-boolean-expression, inconsistent-return-statements, useless-return, - consider-swap-variables, - consider-using-join, - consider-using-in, - consider-using-get, - chained-comparison, - consider-using-dict-comprehension, - consider-using-set-comprehension, - simplifiable-if-expression, no-else-raise, - unreachable, dangerous-default-value, pointless-statement, pointless-string-statement, @@ -253,11 +244,6 @@ disable=blacklisted-name, unused-variable, unused-argument, unused-wildcard-import, - redefined-outer-name, - redefined-builtin, - redefine-in-handler, - undefined-loop-variable, - unbalanced-tuple-unpacking, possibly-unused-variable, broad-except, logging-not-lazy, @@ -275,39 +261,17 @@ disable=blacklisted-name, invalid-envvar-default, subprocess-popen-preexec-fn, no-absolute-import, - old-division, dict-iter-method, dict-view-method, next-method-called, metaclass-assignment, indexing-exception, raising-string, - reload-builtin, oct-method, hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, exception-message-attribute, invalid-str-codec, sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, next-method-defined, dict-items-not-iterating, dict-keys-not-iterating, @@ -317,7 +281,8 @@ disable=blacklisted-name, xreadlines-attribute, deprecated-sys-function, exception-escape, - comprehension-escape + comprehension-escape, + redefined-builtin # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 0f07d29a5..3c91486d0 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -465,8 +465,8 @@ def _recv_internal(self, timeout): # so expect to see the value restarting from 0 rx_msg = Message( timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s - is_remote_frame=True if self._message.uMsgInfo.Bits.rtr else False, - is_extended_id=True if self._message.uMsgInfo.Bits.ext else False, + is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr), + is_extended_id=bool(self._message.uMsgInfo.Bits.ext), arbitration_id=self._message.dwMsgId, dlc=self._message.uMsgInfo.Bits.dlc, data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 053197119..6ac38d850 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -658,13 +658,15 @@ def GetValue( A touple with 2 values """ try: - if Parameter == PCAN_API_VERSION or Parameter == PCAN_HARDWARE_NAME or Parameter == PCAN_CHANNEL_VERSION or Parameter == PCAN_LOG_LOCATION or Parameter == PCAN_TRACE_LOCATION or Parameter == PCAN_BITRATE_INFO_FD or Parameter == PCAN_IP_ADDRESS: + if Parameter in (PCAN_API_VERSION, PCAN_HARDWARE_NAME, PCAN_CHANNEL_VERSION, + PCAN_LOG_LOCATION, PCAN_TRACE_LOCATION, PCAN_BITRATE_INFO_FD, + PCAN_IP_ADDRESS): mybuffer = create_string_buffer(256) else: mybuffer = c_int(0) - res = self.__m_dllBasic.CAN_GetValue(Channel,Parameter,byref(mybuffer),sizeof(mybuffer)) - return TPCANStatus(res),mybuffer.value + res = self.__m_dllBasic.CAN_GetValue(Channel, Parameter, byref(mybuffer), sizeof(mybuffer)) + return TPCANStatus(res), mybuffer.value except: logger.error("Exception on PCANBasic.GetValue") raise @@ -694,13 +696,13 @@ def SetValue( A TPCANStatus error code """ try: - if Parameter == PCAN_LOG_LOCATION or Parameter == PCAN_LOG_TEXT or Parameter == 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) mybuffer.value = Buffer - res = self.__m_dllBasic.CAN_SetValue(Channel,Parameter,byref(mybuffer),sizeof(mybuffer)) + res = self.__m_dllBasic.CAN_SetValue(Channel, Parameter, byref(mybuffer), sizeof(mybuffer)) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.SetValue") diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 4e7580feb..efdd9bf0e 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -89,10 +89,7 @@ def __init__(self, *args, channel=None, dll="usb2can.dll", flags=0x00000008, self.can = Usb2CanAbstractionLayer(dll) # get the serial number of the device - if "serial" in kwargs: - device_id = kwargs["serial"] - else: - device_id = channel + device_id = kwargs.get("serial", d=channel) # search for a serial number if the device_id is None or empty if not device_id: @@ -107,9 +104,9 @@ def __init__(self, *args, channel=None, dll="usb2can.dll", flags=0x00000008, self.channel_info = "USB2CAN device {}".format(device_id) connector = "{}; {}".format(device_id, baudrate) - self.handle = self.can.open(connector, flags) + self.handle = self.can.open(connector, flags_t) - super().__init__(channel=channel, dll=dll, flags=flags, bitrate=bitrate, + super().__init__(channel=channel, dll=dll, flags_t=flags_t, bitrate=bitrate, *args, **kwargs) def send(self, msg, timeout=None): @@ -137,7 +134,7 @@ def _recv_internal(self, timeout): if status == CANAL_ERROR_SUCCESS: rx = message_convert_rx(messagerx) - elif status == CANAL_ERROR_RCV_EMPTY or status == CANAL_ERROR_TIMEOUT: + elif status in (CANAL_ERROR_RCV_EMPTY, CANAL_ERROR_TIMEOUT): rx = None else: log.error('Canal Error %s', status) diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index 0086d542d..e17f255a8 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -14,10 +14,10 @@ log = logging.getLogger('can.usb2can') # type definitions -flags = c_ulong +flags_t = c_ulong pConfigureStr = c_char_p -handle = c_long -timeout = c_ulong +handle_t = c_long +timeout_t = c_ulong filter_t = c_ulong # flags mappings @@ -146,17 +146,17 @@ def blocking_receive(self, handle, msg, timeout): log.warning('Blocking Receive Failed') raise - def get_status(self, handle, CanalStatus): + def get_status(self, handle, status): try: - res = self.__m_dllBasic.CanalGetStatus(handle, CanalStatus) + res = self.__m_dllBasic.CanalGetStatus(handle, status) return res except: log.warning('Get status failed') raise - def get_statistics(self, handle, CanalStatistics): + def get_statistics(self, handle, statistics): try: - res = self.__m_dllBasic.CanalGetStatistics(handle, CanalStatistics) + res = self.__m_dllBasic.CanalGetStatistics(handle, statistics) return res except: log.warning('Get Statistics failed') From fb6c7d60082ea8282651223b0d0b852a9532812a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 23:24:29 +0200 Subject: [PATCH 0183/1235] fix other linter stuff --- .pylintrc-wip | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 0cb2fea88..68e944bf0 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -131,12 +131,7 @@ disable=blacklisted-name, repeated-keyword, not-an-iterable, not-a-mapping, - unsupported-membership-test, - unsubscriptable-object, unsupported-assignment-operation, - unsupported-delete-operation, - invalid-metaclass, - unhashable-dict-key, logging-unsupported-format, logging-format-truncated, logging-too-many-args, @@ -146,29 +141,14 @@ disable=blacklisted-name, mixed-format-string, format-needs-mapping, missing-format-string-key, - too-many-format-args, - too-few-format-args, bad-string-format-type, bad-str-strip-call, invalid-envvar-value, print-statement, parameter-unpacking, unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, import-star-module-level, - non-ascii-bytes-literal, yield-inside-async-function, - not-async-context-manager, - fatal, - astroid-error, - parse-error, - method-check-failed, - raw-checker-failed, - bad-inline-option, locally-disabled, file-ignored, suppressed-message, From 4033a621ec2f530ea168a7f1b6dc6fb0a6a88c11 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 23:42:26 +0200 Subject: [PATCH 0184/1235] fix other linter stuff --- .pylintrc-wip | 70 ++--------------------- can/ctypesutil.py | 8 +-- can/interfaces/ixxat/canlib.py | 4 +- can/interfaces/kvaser/canlib.py | 2 +- can/interfaces/pcan/basic.py | 2 +- can/interfaces/usb2can/serial_selector.py | 4 +- can/interfaces/vector/canlib.py | 2 +- can/io/canutils.py | 5 +- can/io/csv.py | 6 +- can/logger.py | 2 +- can/message.py | 10 ++-- can/viewer.py | 8 +-- 12 files changed, 32 insertions(+), 91 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 68e944bf0..0c9bef035 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -64,41 +64,19 @@ disable=blacklisted-name, invalid-name, missing-docstring, empty-docstring, - singleton-comparison, - misplaced-comparison-constant, - consider-using-enumerate, - consider-iterating-dictionary, - bad-classmethod-argument, - bad-mcs-method-argument, - bad-mcs-classmethod-argument, single-string-used-for-slots, line-too-long, too-many-lines, - multiple-statements, - superfluous-parens, bad-whitespace, mixed-line-endings, unexpected-line-ending-format, bad-continuation, - wrong-spelling-in-comment, - wrong-spelling-in-docstring, - invalid-characters-in-docstring, multiple-imports, wrong-import-order, ungrouped-imports, wrong-import-position, - useless-import-alias, - len-as-condition, - syntax-error, - unrecognized-inline-option, - bad-option-value, - return-in-init, - duplicate-argument-name, - abstract-class-instantiated, - bad-reversed-sequence, too-many-star-expressions, invalid-star-assignment-target, - star-needs-assignment-target, method-hidden, access-member-before-definition, no-method-argument, @@ -151,16 +129,7 @@ disable=blacklisted-name, yield-inside-async-function, locally-disabled, file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - c-extension-no-member, - literal-comparison, - comparison-with-itself, no-self-use, - no-classmethod-decorator, - no-staticmethod-decorator, useless-object-inheritance, cyclic-import, duplicate-code, @@ -173,18 +142,8 @@ disable=blacklisted-name, too-many-arguments, too-many-locals, too-many-statements, - too-many-boolean-expressions, - consider-merging-isinstance, too-many-nested-blocks, - simplifiable-if-statement, - redefined-argument-from-local, - no-else-return, - consider-using-ternary, - trailing-comma-tuple, - stop-iteration-return, - simplify-boolean-expression, inconsistent-return-statements, - useless-return, no-else-raise, dangerous-default-value, pointless-statement, @@ -216,10 +175,6 @@ disable=blacklisted-name, misplaced-future, fixme, invalid-encoded-data, - global-variable-undefined, - global-variable-not-assigned, - global-statement, - global-at-module-level, unused-import, unused-variable, unused-argument, @@ -233,18 +188,8 @@ disable=blacklisted-name, missing-format-argument-key, unused-format-string-argument, invalid-format-index, - duplicate-string-formatting-argument, - implicit-str-concat-in-sequence, - redundant-unittest-assert, - bad-thread-instantiation, - shallow-copy-environ, - invalid-envvar-default, - subprocess-popen-preexec-fn, no-absolute-import, dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, indexing-exception, raising-string, oct-method, @@ -253,16 +198,11 @@ disable=blacklisted-name, invalid-str-codec, sys-max-int, next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, - redefined-builtin + redefined-builtin, + no-else-return, + redefined-argument-from-local, + abstract-class-instantiated, + multiple-statements # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 1df53e5fc..0d8c967c9 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -34,7 +34,7 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): :param callable errcheck: optional error checking function, see ctypes docs for _FuncPtr """ - if (argtypes): + if argtypes: prototype = self.function_type(restype, *argtypes) else: prototype = self.function_type(restype) @@ -46,7 +46,7 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): setattr(symbol, "_name", func_name) log.debug('Wrapped function "{}", result type: {}, error_check {}'.format(func_name, type(restype), errcheck)) - if (errcheck): + if errcheck: symbol.errcheck = errcheck setattr(self, func_name, symbol) @@ -57,7 +57,7 @@ class CLibrary_Win32(_LibBase, LibraryMixin): " Basic ctypes.WinDLL derived class + LibraryMixin " def __init__(self, library_or_path): - if (isinstance(library_or_path, str)): + if isinstance(library_or_path, str): super().__init__(library_or_path) else: super().__init__(library_or_path._name, library_or_path._handle) @@ -71,7 +71,7 @@ class CLibrary_Unix(ctypes.CDLL, LibraryMixin): " Basic ctypes.CDLL derived class + LibraryMixin " def __init__(self, library_or_path): - if (isinstance(library_or_path, str)): + if isinstance(library_or_path, str): super().__init__(library_or_path) else: super().__init__(library_or_path._name, library_or_path._handle) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 3c91486d0..a87ad44a4 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -296,7 +296,7 @@ def __init__(self, channel, can_filters=None, **kwargs): # Usually comes as a string from the config file channel = int(channel) - if (bitrate not in self.CHANNEL_BITRATES[0]): + if bitrate not in self.CHANNEL_BITRATES[0]: raise ValueError("Invalid bitrate {}".format(bitrate)) self._device_handle = HANDLE() @@ -317,7 +317,7 @@ def __init__(self, channel, can_filters=None, **kwargs): try: _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) except StopIteration: - if (UniqueHardwareId is None): + if UniqueHardwareId is None: raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") else: raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 7e8cd4b4f..af6eafff4 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -113,7 +113,7 @@ class c_canHandle(ctypes.c_int): def __handle_is_valid(handle): - return (handle.value > canINVALID_HANDLE) + return handle.value > canINVALID_HANDLE def __check_bus_handle_validity(handle, function, arguments): diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 6ac38d850..79410a54e 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -361,7 +361,7 @@ def __init__(self): self.__m_dllBasic = cdll.LoadLibrary('libPCBUSB.dylib') else: self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so") - if self.__m_dllBasic == None: + if self.__m_dllBasic is None: logger.error("Exception: The PCAN-Basic DLL couldn't be loaded!") def Initialize( diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index abaf19253..ddd7ff984 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -13,12 +13,12 @@ def WMIDateStringToDate(dtmDate): - if (dtmDate[4] == 0): + if dtmDate[4] == 0: strDateTime = dtmDate[5] + '/' else: strDateTime = dtmDate[4] + dtmDate[5] + '/' - if (dtmDate[6] == 0): + if dtmDate[6] == 0: strDateTime = strDateTime + dtmDate[7] + '/' else: strDateTime = strDateTime + dtmDate[6] + dtmDate[7] + '/' diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 6fb49c5fb..154c80f6d 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -99,7 +99,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, if channel_config.serialNumber == serial: if channel_config.hwChannel in self.channels: channel_index.append(channel_config.channelIndex) - if len(channel_index) > 0: + if channel_index: if len(channel_index) != len(self.channels): LOG.info("At least one defined channel wasn't found on the specified hardware.") self.channels = channel_index diff --git a/can/io/canutils.py b/can/io/canutils.py index 304c37f9c..66fd128f2 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -55,10 +55,7 @@ def __iter__(self): if channel.isdigit(): channel = int(channel) - if len(canId) > 3: - isExtended = True - else: - isExtended = False + isExtended = len(canId) > 3 canId = int(canId, 16) if data and data[0].lower() == 'r': diff --git a/can/io/csv.py b/can/io/csv.py index 8bafdf180..c755b1c5b 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -89,7 +89,11 @@ def __init__(self, file): def __iter__(self): # skip the header line - next(self.file) + try: + next(self.file) + except StopIteration: + # don't crash on a file with only a header + return for line in self.file: diff --git a/can/logger.py b/can/logger.py index 3cf2758e4..ec49c7e2c 100644 --- a/can/logger.py +++ b/can/logger.py @@ -75,7 +75,7 @@ def main(): can.set_logging_level(logging_level_name) can_filters = [] - if len(results.filter) > 0: + if results.filter: print(f"Adding filter(s): {results.filter}") for filt in results.filter: if ':' in filt: diff --git a/can/message.py b/can/message.py index ec2ee6875..0dfd170c2 100644 --- a/can/message.py +++ b/can/message.py @@ -227,21 +227,21 @@ def _check(self): raise ValueError("arbitration IDs may not be negative") if self.is_extended_id: - if 0x20000000 <= self.arbitration_id: + if self.arbitration_id >= 0x20000000: raise ValueError("Extended arbitration IDs must be less than 2^29") - elif 0x800 <= self.arbitration_id: + elif self.arbitration_id >= 0x800: raise ValueError("Normal arbitration IDs must be less than 2^11") if self.dlc < 0: raise ValueError("DLC may not be negative") if self.is_fd: - if 64 < self.dlc: + if self.dlc > 64: raise ValueError("DLC was {} but it should be <= 64 for CAN FD frames".format(self.dlc)) - elif 8 < self.dlc: + elif self.dlc > 8: raise ValueError("DLC was {} but it should be <= 8 for normal CAN frames".format(self.dlc)) if self.is_remote_frame: - if self.data is not None and len(self.data) != 0: + if self.data is not None and not self.data: raise ValueError("remote frames may not carry any data") elif self.dlc != len(self.data): raise ValueError("the DLC and the length of the data must match up for non remote frames") diff --git a/can/viewer.py b/can/viewer.py index d915a9ddb..5e6cf7d5b 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -141,7 +141,7 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod def unpack_data(cmd, cmd_to_struct, data): # type: (int, Dict, bytes) -> List[Union[float, int]] - if not cmd_to_struct or len(data) == 0: + if not cmd_to_struct or data: # These messages do not contain a data package return [] @@ -403,7 +403,7 @@ def parse_args(args): choices=sorted(can.VALID_INTERFACES)) # Print help message when no arguments are given - if len(args) == 0: + if args: parser.print_help(sys.stderr) import errno raise SystemExit(errno.EINVAL) @@ -411,7 +411,7 @@ def parse_args(args): parsed_args = parser.parse_args(args) can_filters = [] - if len(parsed_args.filter) > 0: + if parsed_args.filter: # print('Adding filter/s', parsed_args.filter) for flt in parsed_args.filter: # print(filter) @@ -445,7 +445,7 @@ def parse_args(args): # In order to convert from raw integer value the real units are multiplied with the values and similarly the values # are divided by the value in order to convert from real units to raw integer values. data_structs = {} # type: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] - if len(parsed_args.decode) > 0: + if parsed_args.decode: if os.path.isfile(parsed_args.decode[0]): with open(parsed_args.decode[0], 'r') as f: structs = f.readlines() From 29349ecd4d714f5881c16b2ebc3dd2a660207e39 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 23:42:57 +0200 Subject: [PATCH 0185/1235] fix other linter stuff --- .pylintrc-wip | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 0c9bef035..4df60a88f 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -104,24 +104,6 @@ disable=blacklisted-name, too-many-function-args, unexpected-keyword-arg, redundant-keyword-arg, - invalid-unary-operand-type, - unsupported-binary-operation, - repeated-keyword, - not-an-iterable, - not-a-mapping, - unsupported-assignment-operation, - logging-unsupported-format, - logging-format-truncated, - logging-too-many-args, - logging-too-few-args, - bad-format-character, - truncated-format-string, - mixed-format-string, - format-needs-mapping, - missing-format-string-key, - bad-string-format-type, - bad-str-strip-call, - invalid-envvar-value, print-statement, parameter-unpacking, unpacking-in-except, From 53724a63300ec68ea2da722d096f0bbb44a18a9e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 19 May 2019 23:56:03 +0200 Subject: [PATCH 0186/1235] fix other linter stuff --- .pylintrc-wip | 61 +++-------------------------- can/__init__.py | 1 - can/bus.py | 3 -- can/interfaces/ixxat/exceptions.py | 2 - can/interfaces/kvaser/canlib.py | 3 +- can/interfaces/pcan/pcan.py | 1 - can/interfaces/serial/serial_can.py | 2 +- can/interfaces/systec/structures.py | 3 +- can/interfaces/systec/ucan.py | 11 +----- can/io/blf.py | 2 +- can/listener.py | 1 - can/thread_safe_bus.py | 2 +- 12 files changed, 13 insertions(+), 79 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 4df60a88f..2c3f76bb6 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -75,46 +75,13 @@ disable=blacklisted-name, wrong-import-order, ungrouped-imports, wrong-import-position, - too-many-star-expressions, - invalid-star-assignment-target, method-hidden, - access-member-before-definition, - no-method-argument, - no-self-argument, - invalid-slots-object, - assigning-non-slot, - duplicate-bases, - non-iterator-returned, - unexpected-special-method-signature, - invalid-length-returned, - import-error, - relative-beyond-top-level, - used-before-assignment, - undefined-variable, - undefined-all-variable, - invalid-all-object, - no-name-in-module, - unpacking-non-sequence, - bad-exception-context, misplaced-bare-raise, - raising-non-exception, - notimplemented-raised, - catching-non-exception, - no-value-for-parameter, too-many-function-args, - unexpected-keyword-arg, - redundant-keyword-arg, print-statement, - parameter-unpacking, - unpacking-in-except, import-star-module-level, - yield-inside-async-function, locally-disabled, - file-ignored, no-self-use, - useless-object-inheritance, - cyclic-import, - duplicate-code, too-many-ancestors, too-many-instance-attributes, too-few-public-methods, @@ -125,22 +92,7 @@ disable=blacklisted-name, too-many-locals, too-many-statements, too-many-nested-blocks, - inconsistent-return-statements, no-else-raise, - dangerous-default-value, - pointless-statement, - pointless-string-statement, - expression-not-assigned, - unnecessary-pass, - unnecessary-lambda, - duplicate-key, - assign-to-new-keyword, - useless-else-on-loop, - exec-used, - eval-used, - confusing-with-statement, - using-constant-test, - comparison-with-callable, lost-exception, assert-on-tuple, attribute-defined-outside-init, @@ -154,9 +106,7 @@ disable=blacklisted-name, non-parent-init-called, bad-indentation, wildcard-import, - misplaced-future, fixme, - invalid-encoded-data, unused-import, unused-variable, unused-argument, @@ -172,19 +122,18 @@ disable=blacklisted-name, invalid-format-index, no-absolute-import, dict-iter-method, - indexing-exception, - raising-string, oct-method, hex-method, exception-message-attribute, - invalid-str-codec, - sys-max-int, - next-method-defined, redefined-builtin, no-else-return, redefined-argument-from-local, abstract-class-instantiated, - multiple-statements + multiple-statements, + cyclic-import, + useless-else-on-loop, + duplicate-code, + inconsistent-return-statements # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/can/__init__.py b/can/__init__.py index 0606266a9..4ad8bdee7 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -17,7 +17,6 @@ class CanError(IOError): """Indicates an error with the CAN network. """ - pass from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader diff --git a/can/bus.py b/can/bus.py index c8ade66d2..f0cb5080f 100644 --- a/can/bus.py +++ b/can/bus.py @@ -313,7 +313,6 @@ def _apply_filters(self, filters): :param Iterator[dict] filters: See :meth:`~can.BusABC.set_filters` for details. """ - pass def _matches_filters(self, msg): """Checks whether the given message matches at least one of the @@ -354,14 +353,12 @@ def _matches_filters(self, msg): def flush_tx_buffer(self): """Discard every message that may be queued in the output buffer(s). """ - pass def shutdown(self): """ Called to carry out any interface specific cleanup required in shutting down a bus. """ - pass def __enter__(self): return self diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index dde869032..efce76297 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -13,12 +13,10 @@ class VCITimeout(CanError): """ Wraps the VCI_E_TIMEOUT error """ - pass class VCIError(CanError): """ Try to display errors that occur within the wrapped C library nicely. """ - pass class VCIRxQueueEmptyError(VCIError): diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index af6eafff4..3c042b485 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -41,7 +41,8 @@ def _unimplemented_function(*args): raise NotImplementedError('This function is not implemented in canlib') -def __get_canlib_function(func_name, argtypes=[], restype=None, errcheck=None): +def __get_canlib_function(func_name, argtypes=None, restype=None, errcheck=None): + argtypes = [] if argtypes is None else argtypes #log.debug('Wrapping function "%s"' % func_name) try: # e.g. canlib.canBusOn diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 2d04ff5f4..53b8d6b8b 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -426,4 +426,3 @@ class PcanError(CanError): """ A generic error on a PCAN bus. """ - pass diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 1904d28e3..e61c80849 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -121,7 +121,7 @@ def _recv_internal(self, timeout): message are the default values. :rtype: - can.Message, bool + Tuple[can.Message, Bool] """ try: # ser.read can return an empty string diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index 40ce01dfa..11ffd0e39 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -35,7 +35,8 @@ class CanMsg(Structure): ("m_dwTime", DWORD,) # Receive time stamp in ms (for transmit messages no meaning) ] - def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=[]): + def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None): + data = [] if data is None else data super().__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) def __eq__(self, other): diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index e42c187eb..0c153b939 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -291,7 +291,7 @@ def check_result(result, func, arguments): log.warning("Cannot load SYSTEC ucan library: %s.", ex) -class UcanServer(object): +class UcanServer: """ UcanServer is a Python wrapper class for using the usbcan32.dll / usbcan64.dll. """ @@ -987,7 +987,6 @@ def init_hw_event(self): .. note:: To be overridden by subclassing. """ - pass def init_can_event(self, channel): """ @@ -997,7 +996,6 @@ def init_can_event(self, channel): .. note:: To be overridden by subclassing. """ - pass def can_msg_received_event(self, channel): """ @@ -1009,7 +1007,6 @@ def can_msg_received_event(self, channel): .. note:: To be overridden by subclassing. """ - pass def status_event(self, channel): """ @@ -1021,7 +1018,6 @@ def status_event(self, channel): .. note:: To be overridden by subclassing. """ - pass def deinit_can_event(self, channel): """ @@ -1031,7 +1027,6 @@ def deinit_can_event(self, channel): .. note:: To be overridden by subclassing. """ - pass def deinit_hw_event(self): """ @@ -1039,7 +1034,6 @@ def deinit_hw_event(self): .. note:: To be overridden by subclassing. """ - pass def connect_event(self): """ @@ -1047,7 +1041,6 @@ def connect_event(self): .. note:: To be overridden by subclassing. """ - pass def disconnect_event(self): """ @@ -1055,7 +1048,6 @@ def disconnect_event(self): .. note:: To be overridden by subclassing. """ - pass def fatal_disconnect_event(self, device_number): """ @@ -1067,7 +1059,6 @@ def fatal_disconnect_event(self, device_number): .. note:: To be overridden by subclassing. """ - pass UcanServer._enum_callback_ref = EnumCallback(UcanServer._enum_callback) diff --git a/can/io/blf.py b/can/io/blf.py index 36d116950..ad895297e 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -28,7 +28,7 @@ class BLFParseError(Exception): """BLF file could not be parsed correctly.""" - pass + LOG = logging.getLogger(__name__) diff --git a/can/listener.py b/can/listener.py index b3cc3a886..2f773fcf0 100644 --- a/can/listener.py +++ b/can/listener.py @@ -39,7 +39,6 @@ def on_message_received(self, msg): :param can.Message msg: the delivered message """ - pass def __call__(self, msg): return self.on_message_received(msg) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 35f923910..d215add4a 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -18,7 +18,7 @@ from contextlib import nullcontext except ImportError: - class nullcontext(object): + class nullcontext: """A context manager that does nothing at all. A fallback for Python 3.7's :class:`contextlib.nullcontext` manager. """ From 29daaf93433e717538ba178866b3e8f6b5e36bff Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 00:03:54 +0200 Subject: [PATCH 0187/1235] fix other linter stuff --- .pylintrc-wip | 11 ++--------- can/interfaces/ixxat/canlib.py | 10 ---------- can/interfaces/nican.py | 15 ++------------- can/interfaces/systec/exceptions.py | 1 + can/interfaces/usb2can/usb2canInterface.py | 6 +++++- 5 files changed, 10 insertions(+), 33 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 2c3f76bb6..5581e5c1e 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -93,17 +93,9 @@ disable=blacklisted-name, too-many-statements, too-many-nested-blocks, no-else-raise, - lost-exception, - assert-on-tuple, attribute-defined-outside-init, - bad-staticmethod-argument, protected-access, - arguments-differ, - signature-differs, abstract-method, - super-init-not-called, - no-init, - non-parent-init-called, bad-indentation, wildcard-import, fixme, @@ -133,7 +125,8 @@ disable=blacklisted-name, cyclic-import, useless-else-on-loop, duplicate-code, - inconsistent-return-statements + inconsistent-return-statements, + arguments-differ # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index a87ad44a4..d33511660 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -516,16 +516,6 @@ def shutdown(self): _canlib.canControlClose(self._control_handle) _canlib.vciDeviceClose(self._device_handle) - __set_filters_has_been_called = False - def set_filters(self, can_filers=None): - """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. - """ - if self.__set_filters_has_been_called: - log.warning("using filters is not supported like this, see note on IXXATBus") - else: - # allow the constructor to call this without causing a warning - self.__set_filters_has_been_called = True - class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index b032e3eee..d05acf850 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -201,9 +201,8 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **k self.handle = ctypes.c_ulong() nican.ncOpenObject(channel, ctypes.byref(self.handle)) - super().__init__(channel=channel, - can_filters=can_filters, bitrate=bitrate, - log_errors=log_errors, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, bitrate=bitrate, + log_errors=log_errors, **kwargs) def _recv_internal(self, timeout): """ @@ -293,16 +292,6 @@ def shutdown(self): """Close object.""" nican.ncCloseObject(self.handle) - __set_filters_has_been_called = False - def set_filters(self, can_filers=None): - """Unsupported. See note on :class:`~can.interfaces.nican.NicanBus`. - """ - if self.__set_filters_has_been_called: - logger.warning("using filters is not supported like this, see note on NicanBus") - else: - # allow the constructor to call this without causing a warning - self.__set_filters_has_been_called = True - class NicanError(CanError): """Error from NI-CAN driver.""" diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index 622403d48..0d823895b 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -12,6 +12,7 @@ def __init__(self, result, func, arguments): self.func = func self.arguments = arguments self.return_msgs = {} + super().__init__() def __str__(self): message = self.return_msgs.get(self.result, "unknown") diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index efdd9bf0e..2cfe4d0d5 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -154,7 +154,11 @@ def shutdown(self): raise CanError("could not shut down bus: status == {}".format(status)) @staticmethod - def _detect_available_configs(serial_matcher=None): + def _detect_available_configs(): + return Usb2canBus.detect_available_configs() + + @staticmethod + def detect_available_configs(serial_matcher=None): """ Uses the Windows Management Instrumentation to identify serial devices. From cbea5ee74538c11c96724fc63fe319306d472357 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 00:04:17 +0200 Subject: [PATCH 0188/1235] fix other linter stuff --- .pylintrc-wip | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 5581e5c1e..6f2beb7a8 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -99,15 +99,7 @@ disable=blacklisted-name, bad-indentation, wildcard-import, fixme, - unused-import, - unused-variable, - unused-argument, - unused-wildcard-import, - possibly-unused-variable, broad-except, - logging-not-lazy, - logging-format-interpolation, - logging-fstring-interpolation, bad-format-string, missing-format-argument-key, unused-format-string-argument, From 8a0d980304d3e016f9a24de67e6ec93e7466aa73 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 00:26:50 +0200 Subject: [PATCH 0189/1235] fix other linter stuff --- .pylintrc-wip | 15 ++++++++------- can/broadcastmanager.py | 1 - can/bus.py | 5 ++--- can/ctypesutil.py | 3 +-- can/interface.py | 7 ++----- can/interfaces/ics_neovi/neovi_bus.py | 2 +- can/interfaces/ixxat/canlib.py | 4 ++-- can/interfaces/kvaser/canlib.py | 9 ++++----- can/interfaces/pcan/pcan.py | 4 +--- can/interfaces/socketcan/socketcan.py | 5 ++--- can/interfaces/socketcan/utils.py | 1 - can/interfaces/systec/ucan.py | 4 ++-- can/interfaces/systec/ucanbus.py | 5 ++++- can/interfaces/vector/canlib.py | 3 +-- can/io/canutils.py | 2 -- can/io/generic.py | 4 +--- can/io/logger.py | 2 +- can/io/sqlite.py | 7 +++---- can/message.py | 1 - can/thread_safe_bus.py | 4 ++-- can/util.py | 2 -- 21 files changed, 37 insertions(+), 53 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 6f2beb7a8..c58a7d56f 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -93,22 +93,16 @@ disable=blacklisted-name, too-many-statements, too-many-nested-blocks, no-else-raise, - attribute-defined-outside-init, - protected-access, - abstract-method, bad-indentation, wildcard-import, fixme, broad-except, bad-format-string, - missing-format-argument-key, unused-format-string-argument, - invalid-format-index, no-absolute-import, dict-iter-method, oct-method, hex-method, - exception-message-attribute, redefined-builtin, no-else-return, redefined-argument-from-local, @@ -118,7 +112,14 @@ disable=blacklisted-name, useless-else-on-loop, duplicate-code, inconsistent-return-statements, - arguments-differ + arguments-differ, + unused-wildcard-import, + logging-fstring-interpolation, + logging-format-interpolation, + unused-argument, + abstract-method, + attribute-defined-outside-init, + protected-access # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 05a27197a..9cf4cb36c 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -11,7 +11,6 @@ import logging import threading import time -import warnings log = logging.getLogger('can.bcm') diff --git a/can/bus.py b/can/bus.py index f0cb5080f..7ba4ae872 100644 --- a/can/bus.py +++ b/can/bus.py @@ -8,7 +8,6 @@ import logging import threading from time import time -from collections import namedtuple from aenum import Enum, auto from .broadcastmanager import ThreadBasedCyclicSendTask @@ -239,8 +238,8 @@ def _send_periodic_internal(self, msg, period, duration=None): :rtype: can.broadcastmanager.CyclicSendTaskABC """ if not hasattr(self, "_lock_send_periodic"): - # Create a send lock for this bus - self._lock_send_periodic = threading.Lock() + # Create a send lock for this bus, but not for buses which override this method + self._lock_send_periodic = threading.Lock() # pylint: disable=attribute-defined-outside-init task = ThreadBasedCyclicSendTask(self, self._lock_send_periodic, msg, period, duration) return task diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 0d8c967c9..eaac336e3 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -4,7 +4,6 @@ This module contains common `ctypes` utils. """ -import binascii import ctypes import logging import sys @@ -44,7 +43,7 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): raise ImportError("Could not map function '{}' from library {}".format(func_name, self._name)) setattr(symbol, "_name", func_name) - log.debug('Wrapped function "{}", result type: {}, error_check {}'.format(func_name, type(restype), errcheck)) + log.debug(f'Wrapped function "{func_name}", result type: {type(restype)}, error_check {errcheck}') if errcheck: symbol.errcheck = errcheck diff --git a/can/interface.py b/can/interface.py index 8eb922051..81481072c 100644 --- a/can/interface.py +++ b/can/interface.py @@ -6,13 +6,10 @@ CyclicSendTasks. """ -import sys import importlib import logging -import can from .bus import BusABC -from .broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC from .util import load_config from .interfaces import BACKENDS @@ -56,7 +53,7 @@ def _get_class_for_interface(interface): return bus_class -class Bus(BusABC): +class Bus(BusABC): # pylint disable=abstract-method """Bus wrapper with configuration loading. Instantiates a CAN Bus of the given ``interface``, falls back to reading a @@ -154,7 +151,7 @@ def detect_available_configs(interfaces=None): # get available channels try: - available = list(bus_class._detect_available_configs()) + available = list(bus_class._detect_available_configs()) # pylint: disable=protected-access except NotImplementedError: log_autodetect.debug('interface "%s" does not support detection of available configurations', interface) else: diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index b5218a4cf..e8404e6d9 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -234,7 +234,7 @@ def _process_msg_queue(self, timeout=0.1): continue self.rx_buffer.append(ics_msg) if errors: - logger.warning("%d error(s) found" % errors) + logger.warning("%d error(s) found", errors) for msg in ics.get_error_messages(self.dev): error = ICSApiError(*msg) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index d33511660..cbe241be9 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -18,7 +18,7 @@ import logging import sys -from can import CanError, BusABC, Message +from can import BusABC, Message from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT @@ -383,7 +383,7 @@ def __init__(self, channel, can_filters=None, **kwargs): # Usually you get back 3 messages like "CAN initialized" ecc... # Clear the FIFO by filter them out with low timeout - for i in range(rxFifoSize): + for _ in range(rxFifoSize): try: _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) except (VCITimeout, VCIRxQueueEmptyError): diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 3c042b485..c89d0e047 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -375,10 +375,9 @@ def __init__(self, channel, can_filters=None, **kwargs): self.single_handle = single_handle num_channels = ctypes.c_int(0) - res = canGetNumberOfChannels(ctypes.byref(num_channels)) - #log.debug("Res: {}".format(res)) + #log.debug("Res: %d", canGetNumberOfChannels(ctypes.byref(num_channels))) num_channels = int(num_channels.value) - log.info('Found %d available channels' % num_channels) + log.info('Found %d available channels', num_channels) for idx in range(num_channels): channel_info = get_channel_info(idx) log.info('%d: %s', idx, channel_info) @@ -391,7 +390,7 @@ def __init__(self, channel, can_filters=None, **kwargs): if fd: flags |= canstat.canOPEN_CAN_FD - log.debug('Creating read handle to bus channel: %s' % channel) + log.debug('Creating read handle to bus channel: %s', channel) self._read_handle = canOpenChannel(channel, flags) canIoCtl(self._read_handle, canstat.canIOCTL_SET_TIMER_SCALE, @@ -427,7 +426,7 @@ def __init__(self, channel, can_filters=None, **kwargs): log.debug("We don't require separate handles to the bus") self._write_handle = self._read_handle else: - log.debug('Creating separate handle for TX on channel: %s' % channel) + log.debug('Creating separate handle for TX on channel: %s', channel) self._write_handle = canOpenChannel(channel, flags) canBusOn(self._read_handle) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 53b8d6b8b..d76f185a6 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -5,10 +5,8 @@ """ import logging -import sys import time -import can from can import CanError, Message, BusABC from can.bus import BusState from can.util import len2dlc, dlc2len @@ -165,7 +163,7 @@ def __init__(self, *args, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate self.m_PcanHandle = globals()[channel] if state is BusState.ACTIVE or state is BusState.PASSIVE: - self.state = state + self._state = state else: raise ArgumentError("BusState must be Active or Passive") diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index d178da2b3..e793194c5 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -3,7 +3,6 @@ import logging import ctypes import ctypes.util -import os import select import socket import struct @@ -25,8 +24,7 @@ from can.broadcastmanager import ModifiableCyclicTaskABC, \ RestartableCyclicTaskABC, LimitedDurationCyclicSendTaskABC from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG -from can.interfaces.socketcan.utils import \ - pack_filters, find_available_interfaces, error_code_to_str +from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces # Setup BCM struct @@ -478,6 +476,7 @@ def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): self.channel = channel self.channel_info = "socketcan channel '%s'" % channel self._bcm_sockets = {} + self._is_filtered = False # set the receive_own_messages parameter try: diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index ebd095f35..f2c1879c5 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -8,7 +8,6 @@ import os import errno import struct -import sys import subprocess import re diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index 0c153b939..8aded3804 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -947,7 +947,7 @@ def _connect_control(self, event, param, arg): - CbEvent.EVENT_FATALDISCON: USB-CAN-Handle of the disconnected module :param arg: Additional parameter defined with :meth:`init_hardware_ex` (not used in this wrapper class). """ - log.debug("Event: %s, Param: %s" % (event, param)) + log.debug("Event: %s, Param: %s", event, param) if event == CbEvent.EVENT_FATALDISCON: self.fatal_disconnect_event(param) @@ -966,7 +966,7 @@ def _callback(self, handle, event, channel, arg): CAN channel (:data:`Channel.CHANNEL_CH0`, :data:`Channel.CHANNEL_CH1` or :data:`Channel.CHANNEL_ANY`). :param arg: Additional parameter defined with :meth:`init_hardware_ex`. """ - log.debug("Handle: %s, Event: %s, Channel: %s" % (handle, event, channel)) + log.debug("Handle: %s, Event: %s, Channel: %s", handle, event, channel) if event == CbEvent.EVENT_INITHW: self.init_hw_event() diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 3238c1711..b4852db91 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -132,6 +132,8 @@ def __init__(self, channel, can_filters=None, **kwargs): self.channel, self._ucan.get_baudrate_message(self.BITRATES[bitrate]) ) + self._is_filtered = False + super().__init__(channel=channel, can_filters=can_filters, **kwargs) def _recv_internal(self, timeout): @@ -180,7 +182,8 @@ def send(self, msg, timeout=None): def _detect_available_configs(): configs = [] try: - for index, is_used, hw_info_ex, init_info in Ucan.enumerate_hardware(): + # index, is_used, hw_info_ex, init_info + for _, _, hw_info_ex, _ in Ucan.enumerate_hardware(): configs.append({'interface': 'systec', 'channel': Channel.CHANNEL_CH0, 'device_number': hw_info_ex.device_number}) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 154c80f6d..01ee1a0a8 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,7 +10,6 @@ # ============================== import ctypes import logging -import sys import time try: @@ -28,7 +27,7 @@ # Import Modules # ============== -from can import BusABC, Message, CanError +from can import BusABC, Message from can.util import len2dlc, dlc2len from .exceptions import VectorError diff --git a/can/io/canutils.py b/can/io/canutils.py index 66fd128f2..bea1696a1 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -6,8 +6,6 @@ (https://github.com/linux-can/can-utils). """ -import time -import datetime import logging from can.message import Message diff --git a/can/io/generic.py b/can/io/generic.py index abb03345e..56ce82860 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -4,9 +4,7 @@ Contains a generic class for file IO. """ -from abc import ABCMeta, abstractmethod - -from can import Listener +from abc import ABCMeta class BaseIOHandler(metaclass=ABCMeta): diff --git a/can/io/logger.py b/can/io/logger.py index da3edb2b5..2863b0ea5 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -18,7 +18,7 @@ log = logging.getLogger("can.io.logger") -class Logger(BaseIOHandler, Listener): +class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method """ Logs CAN messages to a file. diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 94d0af485..eccba8dc8 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -6,7 +6,6 @@ .. note:: The database schema is given in the documentation of the loggers. """ -import sys import time import threading import logging @@ -143,10 +142,12 @@ def __init__(self, file, table_name="messages"): self.table_name = table_name self._db_filename = file self._stop_running_event = threading.Event() + self._conn = None self._writer_thread = threading.Thread(target=self._db_writer_thread) self._writer_thread.start() self.num_frames = 0 self.last_write = time.time() + self._insert_template = f"INSERT INTO {self.table_name} VALUES (?, ?, ?, ?, ?, ?, ?)" def _create_db(self): """Creates a new databae or opens a connection to an existing one. @@ -173,8 +174,6 @@ def _create_db(self): """.format(self.table_name)) self._conn.commit() - self._insert_template = "INSERT INTO {} VALUES (?, ?, ?, ?, ?, ?, ?)".format(self.table_name) - def _db_writer_thread(self): self._create_db() @@ -198,7 +197,7 @@ def _db_writer_thread(self): if time.time() - self.last_write > self.MAX_TIME_BETWEEN_WRITES or \ len(messages) > self.MAX_BUFFER_SIZE_BEFORE_WRITES: - break + break else: # just go on msg = self.get_message(self.GET_MESSAGE_TIMEOUT) diff --git a/can/message.py b/can/message.py index 0dfd170c2..eb37ab11a 100644 --- a/can/message.py +++ b/can/message.py @@ -9,7 +9,6 @@ """ -import warnings from copy import deepcopy from math import isinf, isnan diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index d215add4a..712958526 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -33,7 +33,7 @@ def __exit__(self, *args): pass -class ThreadSafeBus(ObjectProxy): +class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method """ Contains a thread safe :class:`can.BusABC` implementation that wraps around an existing interface instance. All public methods @@ -58,7 +58,7 @@ def __init__(self, *args, **kwargs): # now, BusABC.send_periodic() does not need a lock anymore, but the # implementation still requires a context manager - self.__wrapped__._lock_send_periodic = nullcontext() + self.__wrapped__._lock_send_periodic = nullcontext() # pylint: disable=protected-access # init locks for sending and receiving separately self._lock_send = RLock() diff --git a/can/util.py b/can/util.py index 2e682548d..c567d8ada 100644 --- a/can/util.py +++ b/can/util.py @@ -6,11 +6,9 @@ import os import os.path -import sys import platform import re import logging -import warnings from configparser import ConfigParser import can From a13701b0c57ee41b3003f3f05dda5cc6833fbd95 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 00:30:35 +0200 Subject: [PATCH 0190/1235] fix other linter stuff --- .pylintrc-wip | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index c58a7d56f..2854b1132 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -60,33 +60,21 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". -disable=blacklisted-name, - invalid-name, +disable=invalid-name, missing-docstring, empty-docstring, - single-string-used-for-slots, line-too-long, too-many-lines, bad-whitespace, - mixed-line-endings, - unexpected-line-ending-format, bad-continuation, - multiple-imports, wrong-import-order, ungrouped-imports, wrong-import-position, - method-hidden, - misplaced-bare-raise, too-many-function-args, - print-statement, - import-star-module-level, locally-disabled, - no-self-use, - too-many-ancestors, too-many-instance-attributes, too-few-public-methods, too-many-public-methods, - too-many-return-statements, too-many-branches, too-many-arguments, too-many-locals, @@ -97,12 +85,6 @@ disable=blacklisted-name, wildcard-import, fixme, broad-except, - bad-format-string, - unused-format-string-argument, - no-absolute-import, - dict-iter-method, - oct-method, - hex-method, redefined-builtin, no-else-return, redefined-argument-from-local, From bfea27a805ad24c1dbfb25be3b8592097a2c905b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 00:38:18 +0200 Subject: [PATCH 0191/1235] fix freshly introduced bug --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index eb37ab11a..c3be33aa7 100644 --- a/can/message.py +++ b/can/message.py @@ -240,7 +240,7 @@ def _check(self): raise ValueError("DLC was {} but it should be <= 8 for normal CAN frames".format(self.dlc)) if self.is_remote_frame: - if self.data is not None and not self.data: + if self.data: raise ValueError("remote frames may not carry any data") elif self.dlc != len(self.data): raise ValueError("the DLC and the length of the data must match up for non remote frames") From 527f71526220ea82390afbf2b867d4474489beb4 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 00:40:19 +0200 Subject: [PATCH 0192/1235] fix freshly introduced bug --- can/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/viewer.py b/can/viewer.py index 5e6cf7d5b..2030c052d 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -141,7 +141,7 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod def unpack_data(cmd, cmd_to_struct, data): # type: (int, Dict, bytes) -> List[Union[float, int]] - if not cmd_to_struct or data: + if not cmd_to_struct or not data: # These messages do not contain a data package return [] From 6f361c224e9497adf590247035163a782a8542ea Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 00:54:47 +0200 Subject: [PATCH 0193/1235] fix problem with keyword-arg-before-vararg --- .pylintrc-wip | 3 ++- can/interface.py | 2 +- can/interfaces/pcan/pcan.py | 2 +- can/interfaces/serial/serial_can.py | 2 +- can/interfaces/usb2can/usb2canInterface.py | 2 +- can/thread_safe_bus.py | 6 +++--- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.pylintrc-wip b/.pylintrc-wip index 2854b1132..c028f9f3d 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -101,7 +101,8 @@ disable=invalid-name, unused-argument, abstract-method, attribute-defined-outside-init, - protected-access + protected-access, + keyword-arg-before-vararg # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/can/interface.py b/can/interface.py index 81481072c..5fd342181 100644 --- a/can/interface.py +++ b/can/interface.py @@ -61,7 +61,7 @@ class Bus(BusABC): # pylint disable=abstract-method """ @staticmethod - def __new__(cls, *args, channel=None, **kwargs): + def __new__(cls, channel=None, *args, **kwargs): """ Takes the same arguments as :class:`can.BusABC.__init__`. Some might have a special meaning, see below. diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index d76f185a6..9ef526a84 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -60,7 +60,7 @@ class PcanBus(BusABC): - def __init__(self, *args, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000, + def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000, *args, **kwargs): """A PCAN USB interface to CAN. diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index e61c80849..6e1327a15 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -31,7 +31,7 @@ class SerialBus(BusABC): """ - def __init__(self, channel, *args, baudrate=115200, timeout=0.1, rtscts=False, **kwargs): + def __init__(self, channel, baudrate=115200, timeout=0.1, rtscts=False, *args, **kwargs): """ :param str channel: The serial device to open. For example "/dev/ttyS1" or diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 2cfe4d0d5..ffe04979a 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -83,7 +83,7 @@ class Usb2canBus(BusABC): """ - def __init__(self, *args, channel=None, dll="usb2can.dll", flags=0x00000008, + def __init__(self, channel=None, dll="usb2can.dll", flags=0x00000008, *args, bitrate=500000, **kwargs): self.can = Usb2CanAbstractionLayer(dll) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 712958526..aaa6d7655 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -64,11 +64,11 @@ def __init__(self, *args, **kwargs): self._lock_send = RLock() self._lock_recv = RLock() - def recv(self, *args, timeout=None, **kwargs): + def recv(self, timeout=None, *args, **kwargs): with self._lock_recv: return self.__wrapped__.recv(timeout=timeout, *args, **kwargs) - def send(self, msg, *args, timeout=None, **kwargs): + def send(self, msg, timeout=None, *args, **kwargs): with self._lock_send: return self.__wrapped__.send(msg, timeout=timeout, *args, **kwargs) @@ -85,7 +85,7 @@ def filters(self, filters): with self._lock_recv: self.__wrapped__.filters = filters - def set_filters(self, *args, filters=None, **kwargs): + def set_filters(self, filters=None, *args, **kwargs): with self._lock_recv: return self.__wrapped__.set_filters(filters=filters, *args, **kwargs) From 047f4d8be1d46bab1cac87a9614e4b8bed688fe1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 00:57:39 +0200 Subject: [PATCH 0194/1235] fix freshly introduced bug --- can/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/viewer.py b/can/viewer.py index 2030c052d..1332766df 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -403,7 +403,7 @@ def parse_args(args): choices=sorted(can.VALID_INTERFACES)) # Print help message when no arguments are given - if args: + if not args: parser.print_help(sys.stderr) import errno raise SystemExit(errno.EINVAL) From 19b4ddf940b83b393268c7f424600acd6af59496 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 09:19:07 +0200 Subject: [PATCH 0195/1235] use set for membership testing --- can/interfaces/pcan/basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 79410a54e..f27db0de1 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -658,9 +658,9 @@ def GetValue( A touple with 2 values """ try: - if Parameter in (PCAN_API_VERSION, PCAN_HARDWARE_NAME, PCAN_CHANNEL_VERSION, + if Parameter in {PCAN_API_VERSION, PCAN_HARDWARE_NAME, PCAN_CHANNEL_VERSION, PCAN_LOG_LOCATION, PCAN_TRACE_LOCATION, PCAN_BITRATE_INFO_FD, - PCAN_IP_ADDRESS): + PCAN_IP_ADDRESS}: mybuffer = create_string_buffer(256) else: mybuffer = c_int(0) @@ -696,7 +696,7 @@ def SetValue( 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) From 266180929d0633ab9271dcac7fdb9c2acac962dc Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 20 May 2019 09:22:57 +0200 Subject: [PATCH 0196/1235] revert state setter code in PCAN and add pylint ignore + comment --- can/interfaces/pcan/pcan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 9ef526a84..a7c8bd9ec 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -163,7 +163,7 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 self.m_PcanHandle = globals()[channel] if state is BusState.ACTIVE or state is BusState.PASSIVE: - self._state = state + self.state = state else: raise ArgumentError("BusState must be Active or Passive") @@ -407,8 +407,8 @@ def state(self): @state.setter def state(self, new_state): - - self._state = new_state + # declare here, which is called by __init__() + self._state = new_state # pylint: disable=attribute-defined-outside-init if new_state is BusState.ACTIVE: self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_OFF) From c21861efa61c84b1ff3fefd9dca136894aeb1f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Thu, 23 May 2019 09:49:36 -0400 Subject: [PATCH 0197/1235] Adding CAN FD frame support to asc writer --- can/io/asc.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 3ed50f04a..ca7f5f12c 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -131,6 +131,25 @@ 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" @@ -217,9 +236,39 @@ def on_message_received(self, msg): # 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) From 2e50e18bdbc9b5f1260511242716a213c3d3a510 Mon Sep 17 00:00:00 2001 From: Karl Date: Mon, 20 May 2019 20:58:27 -0700 Subject: [PATCH 0198/1235] Add black to requirements-lint.txt --- requirements-lint.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-lint.txt b/requirements-lint.txt index 514974539..6a81fe2eb 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1 +1,2 @@ pylint==2.3.1 +black==19.3b0 From 27a7535dc1d437680fca8eb02e73b6d42ee918e4 Mon Sep 17 00:00:00 2001 From: Karl Date: Mon, 20 May 2019 20:58:43 -0700 Subject: [PATCH 0199/1235] Enable black in Travis CI checks --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index 29fac0557..5c24c1cd8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -92,6 +92,14 @@ jobs: # warnings to the .pylintrc-wip file to prevent them from being # re-introduced - pylint --rcfile=.pylintrc-wip can/ + - stage: linter + name: "Formatting Checks" + python: "3.7" + before_install: + - travis_retry pip install -r requirements-lint.txt + script: + - black --diff . + - black --check . - stage: deploy name: "PyPi Deployment" python: "3.7" From 0b97098f1e94750fb758db6fc3f78e45c152101e Mon Sep 17 00:00:00 2001 From: Karl Date: Mon, 20 May 2019 21:25:43 -0700 Subject: [PATCH 0200/1235] Fix pylint directive comment Disable the pylint warning for the block, as black will reformat this over multiple lines instead of the previous one-liner. --- can/thread_safe_bus.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index aaa6d7655..7e0d058f2 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -58,7 +58,9 @@ def __init__(self, *args, **kwargs): # now, BusABC.send_periodic() does not need a lock anymore, but the # implementation still requires a context manager - self.__wrapped__._lock_send_periodic = nullcontext() # pylint: disable=protected-access + # pylint: disable=protected-access + self.__wrapped__._lock_send_periodic = nullcontext() + # pylint: enable=protected-access # init locks for sending and receiving separately self._lock_send = RLock() From ad4d46e5d26bf2ee5eada45491ea8eba6f054ce4 Mon Sep 17 00:00:00 2001 From: Karl Date: Mon, 20 May 2019 21:07:57 -0700 Subject: [PATCH 0201/1235] Format files using black formatter --- can/__init__.py | 15 +- can/broadcastmanager.py | 9 +- can/bus.py | 20 +- can/ctypesutil.py | 15 +- can/interface.py | 59 +- can/interfaces/__init__.py | 42 +- can/interfaces/canalystii.py | 84 +- can/interfaces/ics_neovi/neovi_bus.py | 86 +- can/interfaces/iscan.py | 42 +- can/interfaces/ixxat/canlib.py | 457 +++++++--- can/interfaces/ixxat/constants.py | 202 ++--- can/interfaces/ixxat/exceptions.py | 2 +- can/interfaces/ixxat/structures.py | 55 +- can/interfaces/kvaser/canlib.py | 501 ++++++----- can/interfaces/kvaser/constants.py | 8 +- can/interfaces/kvaser/structures.py | 9 +- can/interfaces/nican.py | 166 ++-- can/interfaces/pcan/basic.py | 791 ++++++++++-------- can/interfaces/pcan/pcan.py | 193 +++-- can/interfaces/serial/serial_can.py | 39 +- can/interfaces/slcan.py | 98 ++- can/interfaces/socketcan/constants.py | 84 +- can/interfaces/socketcan/socketcan.py | 142 ++-- can/interfaces/socketcan/utils.py | 19 +- can/interfaces/systec/constants.py | 50 +- can/interfaces/systec/exceptions.py | 15 +- can/interfaces/systec/structures.py | 260 ++++-- can/interfaces/systec/ucan.py | 188 ++++- can/interfaces/systec/ucanbus.py | 98 ++- can/interfaces/usb2can/serial_selector.py | 26 +- can/interfaces/usb2can/usb2canInterface.py | 39 +- .../usb2can/usb2canabstractionlayer.py | 80 +- can/interfaces/vector/canlib.py | 220 +++-- can/interfaces/vector/exceptions.py | 1 - can/interfaces/vector/vxlapi.py | 292 ++++--- can/interfaces/virtual.py | 10 +- can/io/asc.py | 67 +- can/io/blf.py | 208 +++-- can/io/canutils.py | 57 +- can/io/csv.py | 36 +- can/io/generic.py | 4 +- can/io/logger.py | 2 +- can/io/player.py | 6 +- can/io/printer.py | 6 +- can/io/sqlite.py | 62 +- can/logger.py | 89 +- can/message.py | 110 ++- can/notifier.py | 19 +- can/player.py | 112 ++- can/thread_safe_bus.py | 4 +- can/util.py | 70 +- can/viewer.py | 334 ++++---- doc/conf.py | 110 ++- examples/asyncio_demo.py | 15 +- examples/cyclic.py | 28 +- examples/receive_all.py | 6 +- examples/send_one.py | 10 +- examples/serial_com.py | 15 +- examples/vcan_filtered.py | 10 +- examples/virtual_can_demo.py | 6 +- setup.py | 45 +- test/back2back_test.py | 148 ++-- test/config.py | 24 +- test/contextmanager_test.py | 15 +- test/data/example_data.py | 247 +++--- test/listener_test.py | 55 +- test/logformats_test.py | 185 ++-- test/message_helper.py | 23 +- test/network_test.py | 22 +- test/notifier_test.py | 12 +- test/serial_test.py | 17 +- test/simplecyclic_test.py | 54 +- test/test_detect_available_configs.py | 33 +- test/test_kvaser.py | 119 ++- test/test_load_file_config.py | 62 +- test/test_message_class.py | 40 +- test/test_message_filtering.py | 17 +- test/test_message_sync.py | 25 +- test/test_scripts.py | 23 +- test/test_slcan.py | 53 +- test/test_socketcan_helpers.py | 8 +- test/test_systec.py | 136 +-- test/test_viewer.py | 197 +++-- test/zero_dlc_test.py | 13 +- 84 files changed, 4408 insertions(+), 2968 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 4ad8bdee7..e23f5a9b8 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ __version__ = "3.2.0" -log = logging.getLogger('can') +log = logging.getLogger("can") rc = dict() @@ -38,9 +38,10 @@ class CanError(IOError): from . import interface from .interface import Bus, detect_available_configs -from .broadcastmanager import \ - CyclicSendTaskABC, \ - LimitedDurationCyclicSendTaskABC, \ - ModifiableCyclicTaskABC, \ - MultiRateCyclicSendTaskABC, \ - RestartableCyclicTaskABC +from .broadcastmanager import ( + CyclicSendTaskABC, + LimitedDurationCyclicSendTaskABC, + ModifiableCyclicTaskABC, + MultiRateCyclicSendTaskABC, + RestartableCyclicTaskABC, +) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 9cf4cb36c..ae5d126fd 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -12,7 +12,7 @@ import threading import time -log = logging.getLogger('can.bcm') +log = logging.getLogger("can.bcm") class CyclicTask: @@ -47,7 +47,6 @@ def __init__(self, message, period): class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): - def __init__(self, message, period, duration): """Message send task with a defined duration and period. @@ -101,9 +100,9 @@ def __init__(self, channel, message, count, initial_period, subsequent_period): super().__init__(channel, message, subsequent_period) -class ThreadBasedCyclicSendTask(ModifiableCyclicTaskABC, - LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): +class ThreadBasedCyclicSendTask( + ModifiableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC +): """Fallback cyclic send task using thread.""" def __init__(self, bus, lock, message, period, duration=None): diff --git a/can/bus.py b/can/bus.py index 7ba4ae872..7fcf910b0 100644 --- a/can/bus.py +++ b/can/bus.py @@ -31,7 +31,7 @@ class BusABC(metaclass=ABCMeta): """ #: a string describing the underlying bus and/or channel - channel_info = 'unknown' + channel_info = "unknown" #: Log level for received messages RECV_LOGGING_LEVEL = 9 @@ -81,7 +81,7 @@ def recv(self, timeout=None): # return it, if it matches if msg and (already_filtered or self._matches_filters(msg)): - LOG.log(self.RECV_LOGGING_LEVEL, 'Received: %s', msg) + LOG.log(self.RECV_LOGGING_LEVEL, "Received: %s", msg) return msg # if not, and timeout is None, try indefinitely @@ -213,6 +213,7 @@ def wrapped_stop_method(remove_task=True): except ValueError: pass original_stop_method() + task.stop = wrapped_stop_method if store_task: @@ -239,8 +240,12 @@ def _send_periodic_internal(self, msg, period, duration=None): """ if not hasattr(self, "_lock_send_periodic"): # Create a send lock for this bus, but not for buses which override this method - self._lock_send_periodic = threading.Lock() # pylint: disable=attribute-defined-outside-init - task = ThreadBasedCyclicSendTask(self, self._lock_send_periodic, msg, period, duration) + self._lock_send_periodic = ( + threading.Lock() + ) # pylint: disable=attribute-defined-outside-init + task = ThreadBasedCyclicSendTask( + self, self._lock_send_periodic, msg, period, duration + ) return task def stop_all_periodic_tasks(self, remove_tasks=True): @@ -332,13 +337,12 @@ def _matches_filters(self, msg): for _filter in self._filters: # check if this filter even applies to the message - if 'extended' in _filter and \ - _filter['extended'] != msg.is_extended_id: + if "extended" in _filter and _filter["extended"] != msg.is_extended_id: continue # then check for the mask and id - can_id = _filter['can_id'] - can_mask = _filter['can_mask'] + can_id = _filter["can_id"] + can_mask = _filter["can_mask"] # basically, we compute # `msg.arbitration_id & can_mask == can_id & can_mask` diff --git a/can/ctypesutil.py b/can/ctypesutil.py index eaac336e3..8dca8dc14 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -8,9 +8,9 @@ import logging import sys -log = logging.getLogger('can.ctypesutil') +log = logging.getLogger("can.ctypesutil") -__all__ = ['CLibrary', 'HANDLE', 'PHANDLE', 'HRESULT'] +__all__ = ["CLibrary", "HANDLE", "PHANDLE", "HRESULT"] try: _LibBase = ctypes.WinDLL @@ -40,10 +40,16 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): try: symbol = prototype((func_name, self)) except AttributeError: - raise ImportError("Could not map function '{}' from library {}".format(func_name, self._name)) + raise ImportError( + "Could not map function '{}' from library {}".format( + func_name, self._name + ) + ) setattr(symbol, "_name", func_name) - log.debug(f'Wrapped function "{func_name}", result type: {type(restype)}, error_check {errcheck}') + log.debug( + f'Wrapped function "{func_name}", result type: {type(restype)}, error_check {errcheck}' + ) if errcheck: symbol.errcheck = errcheck @@ -95,4 +101,5 @@ class HRESULT(ctypes.c_long): class HANDLE(ctypes.c_void_p): pass + PHANDLE = ctypes.POINTER(HANDLE) diff --git a/can/interface.py b/can/interface.py index 5fd342181..9e32d9ca3 100644 --- a/can/interface.py +++ b/can/interface.py @@ -13,8 +13,8 @@ from .util import load_config from .interfaces import BACKENDS -log = logging.getLogger('can.interface') -log_autodetect = log.getChild('detect_available_configs') +log = logging.getLogger("can.interface") +log_autodetect = log.getChild("detect_available_configs") def _get_class_for_interface(interface): @@ -38,7 +38,9 @@ def _get_class_for_interface(interface): module = importlib.import_module(module_name) except Exception as e: raise ImportError( - "Cannot import module {} for CAN interface '{}': {}".format(module_name, interface, e) + "Cannot import module {} for CAN interface '{}': {}".format( + module_name, interface, e + ) ) # Get the correct class @@ -46,14 +48,15 @@ def _get_class_for_interface(interface): bus_class = getattr(module, class_name) except Exception as e: raise ImportError( - "Cannot import class {} from module {} for CAN interface '{}': {}" - .format(class_name, module_name, interface, e) + "Cannot import class {} from module {} for CAN interface '{}': {}".format( + class_name, module_name, interface, e + ) ) return bus_class -class Bus(BusABC): # pylint disable=abstract-method +class Bus(BusABC): # pylint disable=abstract-method """Bus wrapper with configuration loading. Instantiates a CAN Bus of the given ``interface``, falls back to reading a @@ -85,26 +88,26 @@ def __new__(cls, channel=None, *args, **kwargs): # figure out the rest of the configuration; this might raise an error if channel is not None: - kwargs['channel'] = channel - if 'context' in kwargs: - context = kwargs['context'] - del kwargs['context'] + kwargs["channel"] = channel + if "context" in kwargs: + context = kwargs["context"] + del kwargs["context"] else: context = None kwargs = load_config(config=kwargs, context=context) # resolve the bus class to use for that interface - cls = _get_class_for_interface(kwargs['interface']) + cls = _get_class_for_interface(kwargs["interface"]) # remove the 'interface' key so it doesn't get passed to the backend - del kwargs['interface'] + del kwargs["interface"] # make sure the bus can handle this config format - if 'channel' not in kwargs: + if "channel" not in kwargs: raise ValueError("'channel' argument missing") else: - channel = kwargs['channel'] - del kwargs['channel'] + channel = kwargs["channel"] + del kwargs["channel"] if channel is None: # Use the default channel for the backend @@ -137,7 +140,7 @@ def detect_available_configs(interfaces=None): if interfaces is None: interfaces = BACKENDS elif isinstance(interfaces, str): - interfaces = (interfaces, ) + interfaces = (interfaces,) # else it is supposed to be an iterable of strings result = [] @@ -146,21 +149,33 @@ def detect_available_configs(interfaces=None): try: bus_class = _get_class_for_interface(interface) except ImportError: - log_autodetect.debug('interface "%s" can not be loaded for detection of available configurations', interface) + log_autodetect.debug( + 'interface "%s" can not be loaded for detection of available configurations', + interface, + ) continue # get available channels try: - available = list(bus_class._detect_available_configs()) # pylint: disable=protected-access + available = list( + bus_class._detect_available_configs() + ) # pylint: disable=protected-access except NotImplementedError: - log_autodetect.debug('interface "%s" does not support detection of available configurations', interface) + log_autodetect.debug( + 'interface "%s" does not support detection of available configurations', + interface, + ) else: - log_autodetect.debug('interface "%s" detected %i available configurations', interface, len(available)) + log_autodetect.debug( + 'interface "%s" detected %i available configurations', + interface, + len(available), + ) # add the interface name to the configs if it is not already present for config in available: - if 'interface' not in config: - config['interface'] = interface + if "interface" not in config: + config["interface"] = interface # append to result result += available diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index c4c7f52f7..e64f6f488 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -10,25 +10,29 @@ # interface_name => (module, classname) BACKENDS = { - 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), - 'socketcan': ('can.interfaces.socketcan', 'SocketcanBus'), - 'serial': ('can.interfaces.serial.serial_can','SerialBus'), - 'pcan': ('can.interfaces.pcan', 'PcanBus'), - 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), - 'ixxat': ('can.interfaces.ixxat', 'IXXATBus'), - 'nican': ('can.interfaces.nican', 'NicanBus'), - 'iscan': ('can.interfaces.iscan', 'IscanBus'), - 'virtual': ('can.interfaces.virtual', 'VirtualBus'), - 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), - 'vector': ('can.interfaces.vector', 'VectorBus'), - 'slcan': ('can.interfaces.slcan', 'slcanBus'), - 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus'), - 'systec': ('can.interfaces.systec', 'UcanBus') + "kvaser": ("can.interfaces.kvaser", "KvaserBus"), + "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), + "serial": ("can.interfaces.serial.serial_can", "SerialBus"), + "pcan": ("can.interfaces.pcan", "PcanBus"), + "usb2can": ("can.interfaces.usb2can", "Usb2canBus"), + "ixxat": ("can.interfaces.ixxat", "IXXATBus"), + "nican": ("can.interfaces.nican", "NicanBus"), + "iscan": ("can.interfaces.iscan", "IscanBus"), + "virtual": ("can.interfaces.virtual", "VirtualBus"), + "neovi": ("can.interfaces.ics_neovi", "NeoViBus"), + "vector": ("can.interfaces.vector", "VectorBus"), + "slcan": ("can.interfaces.slcan", "slcanBus"), + "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), + "systec": ("can.interfaces.systec", "UcanBus"), } -BACKENDS.update({ - interface.name: (interface.module_name, interface.attrs[0]) - for interface in iter_entry_points('can.interface') -}) +BACKENDS.update( + { + interface.name: (interface.module_name, interface.attrs[0]) + for interface in iter_entry_points("can.interface") + } +) -VALID_INTERFACES = frozenset(list(BACKENDS.keys()) + ['socketcan_native', 'socketcan_ctypes']) +VALID_INTERFACES = frozenset( + list(BACKENDS.keys()) + ["socketcan_native", "socketcan_ctypes"] +) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 2626f607b..11f4acfbd 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -9,25 +9,29 @@ class VCI_INIT_CONFIG(Structure): - _fields_ = [("AccCode", c_int32), - ("AccMask", c_int32), - ("Reserved", c_int32), - ("Filter", c_ubyte), - ("Timing0", c_ubyte), - ("Timing1", c_ubyte), - ("Mode", c_ubyte)] + _fields_ = [ + ("AccCode", c_int32), + ("AccMask", c_int32), + ("Reserved", c_int32), + ("Filter", c_ubyte), + ("Timing0", c_ubyte), + ("Timing1", c_ubyte), + ("Mode", c_ubyte), + ] class VCI_CAN_OBJ(Structure): - _fields_ = [("ID", c_uint), - ("TimeStamp", c_int), - ("TimeFlag", c_byte), - ("SendType", c_byte), - ("RemoteFlag", c_byte), - ("ExternFlag", c_byte), - ("DataLen", c_byte), - ("Data", c_ubyte * 8), - ("Reserved", c_byte * 3)] + _fields_ = [ + ("ID", c_uint), + ("TimeStamp", c_int), + ("TimeFlag", c_byte), + ("SendType", c_byte), + ("RemoteFlag", c_byte), + ("ExternFlag", c_byte), + ("DataLen", c_byte), + ("Data", c_ubyte * 8), + ("Reserved", c_byte * 3), + ] VCI_USBCAN2 = 4 @@ -68,7 +72,9 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): - def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None): + def __init__( + self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None + ): """ :param channel: channel number @@ -86,11 +92,13 @@ def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can self.channels = [channel] else: # Assume comma separated string of channels - self.channels = [int(ch.strip()) for ch in channel.split(',')] + self.channels = [int(ch.strip()) for ch in channel.split(",")] self.device = device - self.channel_info = "CANalyst-II: device {}, channels {}".format(self.device, self.channels) + self.channel_info = "CANalyst-II: device {}, channels {}".format( + self.device, self.channels + ) if baud is not None: try: @@ -107,7 +115,12 @@ def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can logger.error("VCI_OpenDevice Error") for channel in self.channels: - if CANalystII.VCI_InitCAN(VCI_USBCAN2, self.device, channel, byref(self.init_config)) == STATUS_ERR: + if ( + CANalystII.VCI_InitCAN( + VCI_USBCAN2, self.device, channel, byref(self.init_config) + ) + == STATUS_ERR + ): logger.error("VCI_InitCAN Error") self.shutdown() return @@ -125,17 +138,28 @@ def send(self, msg, timeout=None): :return: """ extern_flag = 1 if msg.is_extended_id else 0 - raw_message = VCI_CAN_OBJ(msg.arbitration_id, 0, 0, 1, msg.is_remote_frame, extern_flag, msg.dlc, (c_ubyte * 8)(*msg.data), (c_byte * 3)(*[0, 0, 0])) + raw_message = VCI_CAN_OBJ( + msg.arbitration_id, + 0, + 0, + 1, + msg.is_remote_frame, + extern_flag, + msg.dlc, + (c_ubyte * 8)(*msg.data), + (c_byte * 3)(*[0, 0, 0]), + ) if msg.channel is not None: channel = msg.channel elif len(self.channels) == 1: channel = self.channels[0] else: - raise ValueError( - "msg.channel must be set when using multiple channels.") + raise ValueError("msg.channel must be set when using multiple channels.") - CANalystII.VCI_Transmit(VCI_USBCAN2, self.device, channel, byref(raw_message), 1) + CANalystII.VCI_Transmit( + VCI_USBCAN2, self.device, channel, byref(raw_message), 1 + ) def _recv_internal(self, timeout=None): """ @@ -147,7 +171,17 @@ def _recv_internal(self, timeout=None): timeout = -1 if timeout is None else int(timeout * 1000) - if CANalystII.VCI_Receive(VCI_USBCAN2, self.device, self.channels[0], byref(raw_message), 1, timeout) <= STATUS_ERR: + if ( + CANalystII.VCI_Receive( + VCI_USBCAN2, + self.device, + self.channels[0], + byref(raw_message), + 1, + timeout, + ) + <= STATUS_ERR + ): return None, False else: return ( diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index e8404e6d9..3df0ccc39 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -22,7 +22,8 @@ except ImportError as ie: logger.warning( "You won't be able to use the ICS NeoVi can backend without the " - "python-ics module installed!: %s", ie + "python-ics module installed!: %s", + ie, ) ics = None @@ -42,8 +43,12 @@ class ICSApiError(CanError): ICS_SPY_ERR_INFORMATION = 0x40 def __init__( - self, error_number, description_short, description_long, - severity, restart_needed + self, + error_number, + description_short, + description_long, + severity, + restart_needed, ): super().__init__(description_short) self.error_number = error_number @@ -95,15 +100,15 @@ def __init__(self, channel, can_filters=None, **kwargs): Absolute path or relative path to the library including filename. """ if ics is None: - raise ImportError('Please install python-ics') + raise ImportError("Please install python-ics") super().__init__(channel=channel, can_filters=can_filters, **kwargs) logger.info("CAN Filters: {}".format(can_filters)) logger.info("Got configuration of: {}".format(kwargs)) - if 'override_library_name' in kwargs: - ics.override_library_name(kwargs.get('override_library_name')) + if "override_library_name" in kwargs: + ics.override_library_name(kwargs.get("override_library_name")) if isinstance(channel, (list, tuple)): self.channels = channel @@ -111,34 +116,31 @@ def __init__(self, channel, can_filters=None, **kwargs): self.channels = [channel] else: # Assume comma separated string of channels - self.channels = [ch.strip() for ch in channel.split(',')] + self.channels = [ch.strip() for ch in channel.split(",")] self.channels = [NeoViBus.channel_to_netid(ch) for ch in self.channels] - type_filter = kwargs.get('type_filter') - serial = kwargs.get('serial') + type_filter = kwargs.get("type_filter") + serial = kwargs.get("serial") self.dev = self._find_device(type_filter, serial) ics.open_device(self.dev) - if 'bitrate' in kwargs: + if "bitrate" in kwargs: for channel in self.channels: - ics.set_bit_rate(self.dev, kwargs.get('bitrate'), channel) + ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel) - fd = kwargs.get('fd', False) + fd = kwargs.get("fd", False) if fd: - if 'data_bitrate' in kwargs: + if "data_bitrate" in kwargs: for channel in self.channels: - ics.set_fd_bit_rate( - self.dev, kwargs.get('data_bitrate'), channel) + ics.set_fd_bit_rate(self.dev, kwargs.get("data_bitrate"), channel) - self._use_system_timestamp = bool( - kwargs.get('use_system_timestamp', False) - ) - self._receive_own_messages = kwargs.get('receive_own_messages', True) + self._use_system_timestamp = bool(kwargs.get("use_system_timestamp", False)) + self._receive_own_messages = kwargs.get("receive_own_messages", True) - self.channel_info = '%s %s CH:%s' % ( + self.channel_info = "%s %s CH:%s" % ( self.dev.Name, self.get_serial_number(self.dev), - self.channels + self.channels, ) logger.info("Using device: {}".format(self.channel_info)) @@ -154,8 +156,7 @@ def channel_to_netid(channel_name_or_id): channel = getattr(ics, netid) else: raise ValueError( - 'channel must be an integer or ' - 'a valid ICS channel name' + "channel must be an integer or " "a valid ICS channel name" ) return channel @@ -195,10 +196,10 @@ def _detect_available_configs(): return [] # TODO: add the channel(s) - return [{ - 'interface': 'neovi', - 'serial': NeoViBus.get_serial_number(device) - } for device in devices] + return [ + {"interface": "neovi", "serial": NeoViBus.get_serial_number(device)} + for device in devices + ] def _find_device(self, type_filter=None, serial=None): if type_filter is not None: @@ -211,14 +212,14 @@ def _find_device(self, type_filter=None, serial=None): dev = device break else: - msg = ['No device'] + msg = ["No device"] if type_filter is not None: - msg.append('with type {}'.format(type_filter)) + msg.append("with type {}".format(type_filter)) if serial is not None: - msg.append('with serial {}'.format(serial)) - msg.append('found.') - raise Exception(' '.join(msg)) + msg.append("with serial {}".format(serial)) + msg.append("found.") + raise Exception(" ".join(msg)) return dev def _process_msg_queue(self, timeout=0.1): @@ -263,18 +264,16 @@ def _ics_msg_to_message(self, ics_msg): if is_fd: if ics_msg.ExtraDataPtrEnabled: - data = ics_msg.ExtraDataPtr[:ics_msg.NumberBytesData] + data = ics_msg.ExtraDataPtr[: ics_msg.NumberBytesData] else: - data = ics_msg.Data[:ics_msg.NumberBytesData] + data = ics_msg.Data[: ics_msg.NumberBytesData] return Message( timestamp=self._get_timestamp_for_msg(ics_msg), arbitration_id=ics_msg.ArbIDOrHeader, data=data, dlc=ics_msg.NumberBytesData, - is_extended_id=bool( - ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME - ), + is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), is_fd=is_fd, is_remote_frame=bool( ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME @@ -285,22 +284,20 @@ def _ics_msg_to_message(self, ics_msg): bitrate_switch=bool( ics_msg.StatusBitField3 & ics.SPY_STATUS3_CANFD_BRS ), - channel=ics_msg.NetworkID + channel=ics_msg.NetworkID, ) else: return Message( timestamp=self._get_timestamp_for_msg(ics_msg), arbitration_id=ics_msg.ArbIDOrHeader, - data=ics_msg.Data[:ics_msg.NumberBytesData], + data=ics_msg.Data[: ics_msg.NumberBytesData], dlc=ics_msg.NumberBytesData, - is_extended_id=bool( - ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME - ), + is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), is_fd=is_fd, is_remote_frame=bool( ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME ), - channel=ics_msg.NetworkID + channel=ics_msg.NetworkID, ) def _recv_internal(self, timeout=0.1): @@ -346,8 +343,7 @@ def send(self, msg, timeout=None): elif len(self.channels) == 1: message.NetworkID = self.channels[0] else: - raise ValueError( - "msg.channel must be set when using multiple channels.") + raise ValueError("msg.channel must be set when using multiple channels.") try: ics.transmit_messages(self.dev, message) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index bb02d261e..e0774dded 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -61,7 +61,7 @@ class IscanBus(BusABC): 250000: 6, 500000: 7, 800000: 8, - 1000000: 9 + 1000000: 9, } def __init__(self, channel, bitrate=500000, poll_interval=0.01, **kwargs): @@ -86,8 +86,9 @@ def __init__(self, channel, bitrate=500000, poll_interval=0.01, **kwargs): self.poll_interval = poll_interval iscan.isCAN_DeviceInitEx(self.channel, self.BAUDRATES[bitrate]) - super().__init__(channel=channel, bitrate=bitrate, - poll_interval=poll_interval, **kwargs) + super().__init__( + channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs + ) def _recv_internal(self, timeout): raw_msg = MessageExStruct() @@ -108,21 +109,25 @@ def _recv_internal(self, timeout): # A message was received break - msg = Message(arbitration_id=raw_msg.message_id, - is_extended_id=bool(raw_msg.is_extended), - timestamp=time.time(), # Better than nothing... - is_remote_frame=bool(raw_msg.remote_req), - dlc=raw_msg.data_len, - data=raw_msg.data[:raw_msg.data_len], - channel=self.channel.value) + msg = Message( + arbitration_id=raw_msg.message_id, + is_extended_id=bool(raw_msg.is_extended), + timestamp=time.time(), # Better than nothing... + is_remote_frame=bool(raw_msg.remote_req), + dlc=raw_msg.data_len, + data=raw_msg.data[: raw_msg.data_len], + channel=self.channel.value, + ) return msg, False def send(self, msg, timeout=None): - raw_msg = MessageExStruct(msg.arbitration_id, - bool(msg.is_extended_id), - bool(msg.is_remote_frame), - msg.dlc, - CanData(*msg.data)) + raw_msg = MessageExStruct( + msg.arbitration_id, + bool(msg.is_extended_id), + bool(msg.is_remote_frame), + msg.dlc, + CanData(*msg.data), + ) iscan.isCAN_TransmitMessageEx(self.channel, ctypes.byref(raw_msg)) def shutdown(self): @@ -155,7 +160,7 @@ class IscanError(CanError): 31: "Transmission not acknowledged on bus", 32: "Error critical bus", 35: "Callbackthread is blocked, stopping thread failed", - 40: "Need a licence number under NT4" + 40: "Need a licence number under NT4", } def __init__(self, function, error_code, arguments): @@ -168,6 +173,7 @@ def __init__(self, function, error_code, arguments): self.arguments = arguments def __str__(self): - description = self.ERROR_CODES.get(self.error_code, - "Error code %d" % self.error_code) + description = self.ERROR_CODES.get( + self.error_code, "Error code %d" % self.error_code + ) return "Function %s failed: %s" % (self.function.__name__, description) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index cbe241be9..0dae6a513 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -19,16 +19,24 @@ import sys from can import BusABC, Message -from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC) +from can.broadcastmanager import ( + LimitedDurationCyclicSendTaskABC, + RestartableCyclicTaskABC, +) from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT from . import constants, structures from .exceptions import * -__all__ = ["VCITimeout", "VCIError", "VCIDeviceNotFoundError", "IXXATBus", "vciFormatError"] +__all__ = [ + "VCITimeout", + "VCIError", + "VCIDeviceNotFoundError", + "IXXATBus", + "vciFormatError", +] -log = logging.getLogger('can.ixxat') +log = logging.getLogger("can.ixxat") from time import perf_counter as _timer_function @@ -66,10 +74,9 @@ def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): :return: Formatted string """ - #TODO: make sure we don't generate another exception + # TODO: make sure we don't generate another exception return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, HRESULT), - arguments + __vciFormatError(library_instance, function, HRESULT), arguments ) @@ -87,7 +94,9 @@ def __vciFormatError(library_instance, function, HRESULT): buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) - return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) + return "function {} failed ({})".format( + function._name, buf.value.decode("utf-8", "replace") + ) def __check_status(result, function, arguments): @@ -118,19 +127,22 @@ def __check_status(result, function, arguments): elif result == constants.VCI_E_NO_MORE_ITEMS: raise StopIteration() elif result == constants.VCI_E_ACCESSDENIED: - pass # not a real error, might happen if another program has initialized the bus + pass # not a real error, might happen if another program has initialized the bus elif result != constants.VCI_OK: raise VCIError(vciFormatError(function, result)) return result + try: # Map all required symbols and initialize library --------------------------- - #HRESULT VCIAPI vciInitialize ( void ); + # HRESULT VCIAPI vciInitialize ( void ); _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) - #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); - _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) + # void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); + _canlib.map_symbol( + "vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32) + ) # Hack to have vciFormatError as a free function vciFormatError = functools.partial(__vciFormatError, _canlib) @@ -139,70 +151,188 @@ def __check_status(result, function, arguments): # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); - _canlib.map_symbol("vciEnumDeviceNext", ctypes.c_long, (HANDLE, structures.PVCIDEVICEINFO), __check_status) + _canlib.map_symbol( + "vciEnumDeviceNext", + ctypes.c_long, + (HANDLE, structures.PVCIDEVICEINFO), + __check_status, + ) # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); - _canlib.map_symbol("vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status) + _canlib.map_symbol( + "vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status + ) # HRESULT vciDeviceClose( HANDLE hDevice ) _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); - _canlib.map_symbol("canChannelOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), __check_status) + _canlib.map_symbol( + "canChannelOpen", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), + __check_status, + ) # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); - _canlib.map_symbol("canChannelInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), __check_status) + _canlib.map_symbol( + "canChannelInitialize", + ctypes.c_long, + (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), + __check_status, + ) # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); - _canlib.map_symbol("canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) + _canlib.map_symbol( + "canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status + ) # HRESULT canChannelClose( HANDLE hChannel ) - _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelReadMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - #HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPeekMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitTxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); - _canlib.map_symbol("canChannelWaitRxEvent", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelPostMessage", ctypes.c_long, (HANDLE, structures.PCANMSG), __check_status) - #HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); - _canlib.map_symbol("canChannelSendMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status) - - #EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); - _canlib.map_symbol("canControlOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); - _canlib.map_symbol("canControlInitialize", ctypes.c_long, (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), __check_status) - #EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); + _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE,), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); + _canlib.map_symbol( + "canChannelReadMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, structures.PCANMSG), + __check_status, + ) + # HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); + _canlib.map_symbol( + "canChannelPeekMessage", + ctypes.c_long, + (HANDLE, structures.PCANMSG), + __check_status, + ) + # HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); + _canlib.map_symbol( + "canChannelWaitTxEvent", + ctypes.c_long, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); + _canlib.map_symbol( + "canChannelWaitRxEvent", + ctypes.c_long, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); + _canlib.map_symbol( + "canChannelPostMessage", + ctypes.c_long, + (HANDLE, structures.PCANMSG), + __check_status, + ) + # HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); + _canlib.map_symbol( + "canChannelSendMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, structures.PCANMSG), + __check_status, + ) + + # EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); + _canlib.map_symbol( + "canControlOpen", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, PHANDLE), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); + _canlib.map_symbol( + "canControlInitialize", + ctypes.c_long, + (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); + # EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) - #EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); - _canlib.map_symbol("canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); - _canlib.map_symbol("canControlGetStatus", ctypes.c_long, (HANDLE, structures.PCANLINESTATUS), __check_status) - #EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); - _canlib.map_symbol("canControlGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); - _canlib.map_symbol("canControlSetAccFilter", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); - _canlib.map_symbol("canControlAddFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); - _canlib.map_symbol("canControlRemFilterIds", ctypes.c_long, (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); - _canlib.map_symbol("canSchedulerOpen", ctypes.c_long, (HANDLE, ctypes.c_uint32, PHANDLE), __check_status) - #EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); - _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE, ), __check_status) - #EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); - _canlib.map_symbol("canSchedulerGetCaps", ctypes.c_long, (HANDLE, structures.PCANCAPABILITIES), __check_status) - #EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); - _canlib.map_symbol("canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status) - #EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); - _canlib.map_symbol("canSchedulerAddMessage", ctypes.c_long, (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), __check_status) - #EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerRemMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) - #EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); - _canlib.map_symbol("canSchedulerStartMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32, ctypes.c_uint16), __check_status) - #EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol("canSchedulerStopMessage", ctypes.c_long, (HANDLE, ctypes.c_uint32), __check_status) + # EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); + _canlib.map_symbol( + "canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status + ) + # EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); + _canlib.map_symbol( + "canControlGetStatus", + ctypes.c_long, + (HANDLE, structures.PCANLINESTATUS), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); + _canlib.map_symbol( + "canControlGetCaps", + ctypes.c_long, + (HANDLE, structures.PCANCAPABILITIES), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); + _canlib.map_symbol( + "canControlSetAccFilter", + ctypes.c_long, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); + _canlib.map_symbol( + "canControlAddFilterIds", + ctypes.c_long, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); + _canlib.map_symbol( + "canControlRemFilterIds", + ctypes.c_long, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); + _canlib.map_symbol( + "canSchedulerOpen", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, PHANDLE), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); + _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE,), __check_status) + # EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); + _canlib.map_symbol( + "canSchedulerGetCaps", + ctypes.c_long, + (HANDLE, structures.PCANCAPABILITIES), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); + _canlib.map_symbol( + "canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status + ) + # EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); + _canlib.map_symbol( + "canSchedulerAddMessage", + ctypes.c_long, + (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol( + "canSchedulerRemMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); + _canlib.map_symbol( + "canSchedulerStartMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, ctypes.c_uint16), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol( + "canSchedulerStopMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32), + __check_status, + ) _canlib.vciInitialize() except AttributeError: # In case _canlib == None meaning we're not on win32/no lib found @@ -213,20 +343,20 @@ def __check_status(result, function, arguments): CAN_INFO_MESSAGES = { - constants.CAN_INFO_START: "CAN started", - constants.CAN_INFO_STOP: "CAN stopped", - constants.CAN_INFO_RESET: "CAN reset", + constants.CAN_INFO_START: "CAN started", + constants.CAN_INFO_STOP: "CAN stopped", + constants.CAN_INFO_RESET: "CAN reset", } CAN_ERROR_MESSAGES = { - constants.CAN_ERROR_STUFF: "CAN bit stuff error", - constants.CAN_ERROR_FORM: "CAN form error", - constants.CAN_ERROR_ACK: "CAN acknowledgment error", - constants.CAN_ERROR_BIT: "CAN bit error", - constants.CAN_ERROR_CRC: "CAN CRC error", - constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", + constants.CAN_ERROR_STUFF: "CAN bit stuff error", + constants.CAN_ERROR_FORM: "CAN form error", + constants.CAN_ERROR_ACK: "CAN acknowledgment error", + constants.CAN_ERROR_BIT: "CAN bit error", + constants.CAN_ERROR_CRC: "CAN CRC error", + constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", } -#---------------------------------------------------------------------------- +# ---------------------------------------------------------------------------- class IXXATBus(BusABC): @@ -251,7 +381,7 @@ class IXXATBus(BusABC): 250000: constants.CAN_BT0_250KB, 500000: constants.CAN_BT0_500KB, 800000: constants.CAN_BT0_800KB, - 1000000: constants.CAN_BT0_1000KB + 1000000: constants.CAN_BT0_1000KB, }, 1: { 10000: constants.CAN_BT1_10KB, @@ -262,8 +392,8 @@ class IXXATBus(BusABC): 250000: constants.CAN_BT1_250KB, 500000: constants.CAN_BT1_500KB, 800000: constants.CAN_BT1_800KB, - 1000000: constants.CAN_BT1_1000KB - } + 1000000: constants.CAN_BT1_1000KB, + }, } def __init__(self, channel, can_filters=None, **kwargs): @@ -284,15 +414,17 @@ def __init__(self, channel, can_filters=None, **kwargs): Channel bitrate in bit/s """ if _canlib is None: - raise ImportError("The IXXAT VCI library has not been initialized. Check the logs for more details.") + raise ImportError( + "The IXXAT VCI library has not been initialized. Check the logs for more details." + ) log.info("CAN Filters: %s", can_filters) log.info("Got configuration of: %s", kwargs) # Configuration options - bitrate = kwargs.get('bitrate', 500000) - UniqueHardwareId = kwargs.get('UniqueHardwareId', None) - rxFifoSize = kwargs.get('rxFifoSize', 16) - txFifoSize = kwargs.get('txFifoSize', 16) - self._receive_own_messages = kwargs.get('receive_own_messages', False) + bitrate = kwargs.get("bitrate", 500000) + UniqueHardwareId = kwargs.get("UniqueHardwareId", None) + rxFifoSize = kwargs.get("rxFifoSize", 16) + txFifoSize = kwargs.get("txFifoSize", 16) + self._receive_own_messages = kwargs.get("receive_own_messages", False) # Usually comes as a string from the config file channel = int(channel) @@ -315,62 +447,99 @@ def __init__(self, channel, can_filters=None, **kwargs): _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) while True: try: - _canlib.vciEnumDeviceNext(self._device_handle, ctypes.byref(self._device_info)) + _canlib.vciEnumDeviceNext( + self._device_handle, ctypes.byref(self._device_info) + ) except StopIteration: if UniqueHardwareId is None: - raise VCIDeviceNotFoundError("No IXXAT device(s) connected or device(s) in use by other process(es).") + raise VCIDeviceNotFoundError( + "No IXXAT device(s) connected or device(s) in use by other process(es)." + ) else: - raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) + raise VCIDeviceNotFoundError( + "Unique HW ID {} not connected or not available.".format( + UniqueHardwareId + ) + ) else: - if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): + if (UniqueHardwareId is None) or ( + self._device_info.UniqueHardwareId.AsChar + == bytes(UniqueHardwareId, "ascii") + ): break else: - log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) + log.debug( + "Ignoring IXXAT with hardware id '%s'.", + self._device_info.UniqueHardwareId.AsChar.decode("ascii"), + ) _canlib.vciEnumDeviceClose(self._device_handle) - _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) + _canlib.vciDeviceOpen( + ctypes.byref(self._device_info.VciObjectId), + ctypes.byref(self._device_handle), + ) log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) - log.info("Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", channel, rxFifoSize, txFifoSize) - _canlib.canChannelOpen(self._device_handle, channel, constants.FALSE, ctypes.byref(self._channel_handle)) + log.info( + "Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", + channel, + rxFifoSize, + txFifoSize, + ) + _canlib.canChannelOpen( + self._device_handle, + channel, + constants.FALSE, + ctypes.byref(self._channel_handle), + ) # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) _canlib.canChannelActivate(self._channel_handle, constants.TRUE) log.info("Initializing control %d bitrate %d", channel, bitrate) - _canlib.canControlOpen(self._device_handle, channel, ctypes.byref(self._control_handle)) + _canlib.canControlOpen( + self._device_handle, channel, ctypes.byref(self._control_handle) + ) _canlib.canControlInitialize( self._control_handle, - constants.CAN_OPMODE_STANDARD|constants.CAN_OPMODE_EXTENDED|constants.CAN_OPMODE_ERRFRAME, + constants.CAN_OPMODE_STANDARD + | constants.CAN_OPMODE_EXTENDED + | constants.CAN_OPMODE_ERRFRAME, self.CHANNEL_BITRATES[0][bitrate], - self.CHANNEL_BITRATES[1][bitrate] + self.CHANNEL_BITRATES[1][bitrate], + ) + _canlib.canControlGetCaps( + self._control_handle, ctypes.byref(self._channel_capabilities) ) - _canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities)) # With receive messages, this field contains the relative reception time of # the message in ticks. The resolution of a tick can be calculated from the fields # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: # frequency [1/s] = dwClockFreq / dwTscDivisor # We explicitly cast to float for Python 2.x users - self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) + self._tick_resolution = float( + self._channel_capabilities.dwClockFreq + / self._channel_capabilities.dwTscDivisor + ) # Setup filters before starting the channel if can_filters: log.info("The IXXAT VCI backend is filtering messages") # Disable every message coming in for extended in (0, 1): - _canlib.canControlSetAccFilter(self._control_handle, - extended, - constants.CAN_ACC_CODE_NONE, - constants.CAN_ACC_MASK_NONE) + _canlib.canControlSetAccFilter( + self._control_handle, + extended, + constants.CAN_ACC_CODE_NONE, + constants.CAN_ACC_MASK_NONE, + ) for can_filter in can_filters: # Whitelist - code = int(can_filter['can_id']) - mask = int(can_filter['can_mask']) - extended = can_filter.get('extended', False) - _canlib.canControlAddFilterIds(self._control_handle, - 1 if extended else 0, - code << 1, - mask << 1) + code = int(can_filter["can_id"]) + mask = int(can_filter["can_mask"]) + extended = can_filter.get("extended", False) + _canlib.canControlAddFilterIds( + self._control_handle, 1 if extended else 0, code << 1, mask << 1 + ) log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) # Start the CAN controller. Messages will be forwarded to the channel @@ -385,7 +554,9 @@ def __init__(self, channel, can_filters=None, **kwargs): # Clear the FIFO by filter them out with low timeout for _ in range(rxFifoSize): try: - _canlib.canChannelReadMessage(self._channel_handle, 0, ctypes.byref(self._message)) + _canlib.canChannelReadMessage( + self._channel_handle, 0, ctypes.byref(self._message) + ) except (VCITimeout, VCIRxQueueEmptyError): break @@ -413,7 +584,9 @@ def _recv_internal(self, timeout): if timeout == 0: # Peek without waiting try: - _canlib.canChannelPeekMessage(self._channel_handle, ctypes.byref(self._message)) + _canlib.canChannelPeekMessage( + self._channel_handle, ctypes.byref(self._message) + ) except (VCITimeout, VCIRxQueueEmptyError): return None, True else: @@ -431,7 +604,9 @@ def _recv_internal(self, timeout): while True: try: - _canlib.canChannelReadMessage(self._channel_handle, remaining_ms, ctypes.byref(self._message)) + _canlib.canChannelReadMessage( + self._channel_handle, remaining_ms, ctypes.byref(self._message) + ) except (VCITimeout, VCIRxQueueEmptyError): # Ignore the 2 errors, the timeout is handled manually with the _timer_function() pass @@ -442,12 +617,31 @@ def _recv_internal(self, timeout): break elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: - log.info(CAN_INFO_MESSAGES.get(self._message.abData[0], "Unknown CAN info message code {}".format(self._message.abData[0]))) - - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: - log.warning(CAN_ERROR_MESSAGES.get(self._message.abData[0], "Unknown CAN error message code {}".format(self._message.abData[0]))) - - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: + log.info( + CAN_INFO_MESSAGES.get( + self._message.abData[0], + "Unknown CAN info message code {}".format( + self._message.abData[0] + ), + ) + ) + + elif ( + self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR + ): + log.warning( + CAN_ERROR_MESSAGES.get( + self._message.abData[0], + "Unknown CAN error message code {}".format( + self._message.abData[0] + ), + ) + ) + + elif ( + self._message.uMsgInfo.Bits.type + == constants.CAN_MSGTYPE_TIMEOVR + ): pass else: log.warning("Unexpected message info type") @@ -464,13 +658,14 @@ def _recv_internal(self, timeout): # The _message.dwTime is a 32bit tick value and will overrun, # so expect to see the value restarting from 0 rx_msg = Message( - timestamp=self._message.dwTime / self._tick_resolution, # Relative time in s + timestamp=self._message.dwTime + / self._tick_resolution, # Relative time in s is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr), is_extended_id=bool(self._message.uMsgInfo.Bits.ext), arbitration_id=self._message.dwMsgId, dlc=self._message.uMsgInfo.Bits.dlc, - data=self._message.abData[:self._message.uMsgInfo.Bits.dlc], - channel=self.channel + data=self._message.abData[: self._message.uMsgInfo.Bits.dlc], + channel=self.channel, ) return rx_msg, True @@ -491,7 +686,8 @@ def send(self, msg, timeout=None): if timeout: _canlib.canChannelSendMessage( - self._channel_handle, int(timeout * 1000), message) + self._channel_handle, int(timeout * 1000), message + ) else: _canlib.canChannelPostMessage(self._channel_handle, message) @@ -499,14 +695,14 @@ def _send_periodic_internal(self, msg, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" if self._scheduler is None: self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, - self._scheduler) + _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) caps = structures.CANCAPABILITIES() _canlib.canSchedulerGetCaps(self._scheduler, caps) self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask(self._scheduler, msg, period, duration, - self._scheduler_resolution) + return CyclicSendTask( + self._scheduler, msg, period, duration, self._scheduler_resolution + ) def shutdown(self): if self._scheduler is not None: @@ -517,8 +713,7 @@ def shutdown(self): _canlib.vciDeviceClose(self._device_handle) -class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC): +class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" def __init__(self, scheduler, msg, period, duration, resolution): @@ -542,12 +737,8 @@ def start(self): """Start transmitting message (add to list if needed).""" if self._index is None: self._index = ctypes.c_uint32() - _canlib.canSchedulerAddMessage(self._scheduler, - self._msg, - self._index) - _canlib.canSchedulerStartMessage(self._scheduler, - self._index, - self._count) + _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) + _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) def pause(self): """Pause transmitting message (keep it in the list).""" diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index d466e096d..46daccf8d 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -6,143 +6,143 @@ Copyright (C) 2016 Giuseppe Corbelli """ -FALSE = 0 -TRUE = 1 +FALSE = 0 +TRUE = 1 -INFINITE = 0xFFFFFFFF +INFINITE = 0xFFFFFFFF VCI_MAX_ERRSTRLEN = 256 # Bitrates -CAN_BT0_10KB = 0x31 -CAN_BT1_10KB = 0x1C -CAN_BT0_20KB = 0x18 -CAN_BT1_20KB = 0x1C -CAN_BT0_50KB = 0x09 -CAN_BT1_50KB = 0x1C -CAN_BT0_100KB = 0x04 -CAN_BT1_100KB = 0x1C -CAN_BT0_125KB = 0x03 -CAN_BT1_125KB = 0x1C -CAN_BT0_250KB = 0x01 -CAN_BT1_250KB = 0x1C -CAN_BT0_500KB = 0x00 -CAN_BT1_500KB = 0x1C -CAN_BT0_800KB = 0x00 -CAN_BT1_800KB = 0x16 -CAN_BT0_1000KB = 0x00 -CAN_BT1_1000KB = 0x14 +CAN_BT0_10KB = 0x31 +CAN_BT1_10KB = 0x1C +CAN_BT0_20KB = 0x18 +CAN_BT1_20KB = 0x1C +CAN_BT0_50KB = 0x09 +CAN_BT1_50KB = 0x1C +CAN_BT0_100KB = 0x04 +CAN_BT1_100KB = 0x1C +CAN_BT0_125KB = 0x03 +CAN_BT1_125KB = 0x1C +CAN_BT0_250KB = 0x01 +CAN_BT1_250KB = 0x1C +CAN_BT0_500KB = 0x00 +CAN_BT1_500KB = 0x1C +CAN_BT0_800KB = 0x00 +CAN_BT1_800KB = 0x16 +CAN_BT0_1000KB = 0x00 +CAN_BT1_1000KB = 0x14 # Facilities/severities -SEV_INFO = 0x40000000 -SEV_WARN = 0x80000000 -SEV_ERROR = 0xC0000000 -SEV_MASK = 0xC0000000 -SEV_SUCCESS = 0x00000000 +SEV_INFO = 0x40000000 +SEV_WARN = 0x80000000 +SEV_ERROR = 0xC0000000 +SEV_MASK = 0xC0000000 +SEV_SUCCESS = 0x00000000 -RESERVED_FLAG = 0x10000000 -CUSTOMER_FLAG = 0x20000000 +RESERVED_FLAG = 0x10000000 +CUSTOMER_FLAG = 0x20000000 -STATUS_MASK = 0x0000FFFF -FACILITY_MASK = 0x0FFF0000 +STATUS_MASK = 0x0000FFFF +FACILITY_MASK = 0x0FFF0000 # Or so I hope FACILITY_STD = 0 -SEV_STD_INFO = SEV_INFO |CUSTOMER_FLAG|FACILITY_STD -SEV_STD_WARN = SEV_WARN |CUSTOMER_FLAG|FACILITY_STD -SEV_STD_ERROR = SEV_ERROR|CUSTOMER_FLAG|FACILITY_STD +SEV_STD_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_STD +SEV_STD_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_STD +SEV_STD_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_STD -FACILITY_VCI = 0x00010000 -SEV_VCI_INFO = SEV_INFO |CUSTOMER_FLAG|FACILITY_VCI -SEV_VCI_WARN = SEV_WARN |CUSTOMER_FLAG|FACILITY_VCI -SEV_VCI_ERROR = SEV_ERROR|CUSTOMER_FLAG|FACILITY_VCI +FACILITY_VCI = 0x00010000 +SEV_VCI_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_VCI +SEV_VCI_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_VCI +SEV_VCI_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_VCI -FACILITY_DAL = 0x00020000 -SEV_DAL_INFO = SEV_INFO |CUSTOMER_FLAG|FACILITY_DAL -SEV_DAL_WARN = SEV_WARN |CUSTOMER_FLAG|FACILITY_DAL -SEV_DAL_ERROR = SEV_ERROR|CUSTOMER_FLAG|FACILITY_DAL +FACILITY_DAL = 0x00020000 +SEV_DAL_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_DAL +SEV_DAL_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_DAL +SEV_DAL_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_DAL -FACILITY_CCL = 0x00030000 -SEV_CCL_INFO = SEV_INFO |CUSTOMER_FLAG|FACILITY_CCL -SEV_CCL_WARN = SEV_WARN |CUSTOMER_FLAG|FACILITY_CCL -SEV_CCL_ERROR = SEV_ERROR|CUSTOMER_FLAG|FACILITY_CCL +FACILITY_CCL = 0x00030000 +SEV_CCL_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_CCL +SEV_CCL_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_CCL +SEV_CCL_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_CCL -FACILITY_BAL = 0x00040000 -SEV_BAL_INFO = SEV_INFO |CUSTOMER_FLAG|FACILITY_BAL -SEV_BAL_WARN = SEV_WARN |CUSTOMER_FLAG|FACILITY_BAL -SEV_BAL_ERROR = SEV_ERROR|CUSTOMER_FLAG|FACILITY_BAL +FACILITY_BAL = 0x00040000 +SEV_BAL_INFO = SEV_INFO | CUSTOMER_FLAG | FACILITY_BAL +SEV_BAL_WARN = SEV_WARN | CUSTOMER_FLAG | FACILITY_BAL +SEV_BAL_ERROR = SEV_ERROR | CUSTOMER_FLAG | FACILITY_BAL # Errors -VCI_SUCCESS = 0x00 -VCI_OK = 0x00 -VCI_E_UNEXPECTED = SEV_VCI_ERROR | 0x0001 -VCI_E_NOT_IMPLEMENTED = SEV_VCI_ERROR | 0x0002 -VCI_E_OUTOFMEMORY = SEV_VCI_ERROR | 0x0003 -VCI_E_INVALIDARG = SEV_VCI_ERROR | 0x0004 -VCI_E_NOINTERFACE = SEV_VCI_ERROR | 0x0005 -VCI_E_INVPOINTER = SEV_VCI_ERROR | 0x0006 -VCI_E_INVHANDLE = SEV_VCI_ERROR | 0x0007 -VCI_E_ABORT = SEV_VCI_ERROR | 0x0008 -VCI_E_FAIL = SEV_VCI_ERROR | 0x0009 -VCI_E_ACCESSDENIED = SEV_VCI_ERROR | 0x000A -VCI_E_TIMEOUT = SEV_VCI_ERROR | 0x000B -VCI_E_BUSY = SEV_VCI_ERROR | 0x000C -VCI_E_PENDING = SEV_VCI_ERROR | 0x000D -VCI_E_NO_DATA = SEV_VCI_ERROR | 0x000E -VCI_E_NO_MORE_ITEMS = SEV_VCI_ERROR | 0x000F -VCI_E_NOT_INITIALIZED = SEV_VCI_ERROR | 0x0010 +VCI_SUCCESS = 0x00 +VCI_OK = 0x00 +VCI_E_UNEXPECTED = SEV_VCI_ERROR | 0x0001 +VCI_E_NOT_IMPLEMENTED = SEV_VCI_ERROR | 0x0002 +VCI_E_OUTOFMEMORY = SEV_VCI_ERROR | 0x0003 +VCI_E_INVALIDARG = SEV_VCI_ERROR | 0x0004 +VCI_E_NOINTERFACE = SEV_VCI_ERROR | 0x0005 +VCI_E_INVPOINTER = SEV_VCI_ERROR | 0x0006 +VCI_E_INVHANDLE = SEV_VCI_ERROR | 0x0007 +VCI_E_ABORT = SEV_VCI_ERROR | 0x0008 +VCI_E_FAIL = SEV_VCI_ERROR | 0x0009 +VCI_E_ACCESSDENIED = SEV_VCI_ERROR | 0x000A +VCI_E_TIMEOUT = SEV_VCI_ERROR | 0x000B +VCI_E_BUSY = SEV_VCI_ERROR | 0x000C +VCI_E_PENDING = SEV_VCI_ERROR | 0x000D +VCI_E_NO_DATA = SEV_VCI_ERROR | 0x000E +VCI_E_NO_MORE_ITEMS = SEV_VCI_ERROR | 0x000F +VCI_E_NOT_INITIALIZED = SEV_VCI_ERROR | 0x0010 VCI_E_ALREADY_INITIALIZED = SEV_VCI_ERROR | 0x00011 -VCI_E_RXQUEUE_EMPTY = SEV_VCI_ERROR | 0x00012 -VCI_E_TXQUEUE_FULL = SEV_VCI_ERROR | 0x0013 -VCI_E_BUFFER_OVERFLOW = SEV_VCI_ERROR | 0x0014 -VCI_E_INVALID_STATE = SEV_VCI_ERROR | 0x0015 +VCI_E_RXQUEUE_EMPTY = SEV_VCI_ERROR | 0x00012 +VCI_E_TXQUEUE_FULL = SEV_VCI_ERROR | 0x0013 +VCI_E_BUFFER_OVERFLOW = SEV_VCI_ERROR | 0x0014 +VCI_E_INVALID_STATE = SEV_VCI_ERROR | 0x0015 VCI_E_OBJECT_ALREADY_EXISTS = SEV_VCI_ERROR | 0x0016 -VCI_E_INVALID_INDEX = SEV_VCI_ERROR | 0x0017 -VCI_E_END_OF_FILE = SEV_VCI_ERROR | 0x0018 -VCI_E_DISCONNECTED = SEV_VCI_ERROR | 0x0019 +VCI_E_INVALID_INDEX = SEV_VCI_ERROR | 0x0017 +VCI_E_END_OF_FILE = SEV_VCI_ERROR | 0x0018 +VCI_E_DISCONNECTED = SEV_VCI_ERROR | 0x0019 VCI_E_WRONG_FLASHFWVERSION = SEV_VCI_ERROR | 0x001A # Controller status -CAN_STATUS_TXPEND = 0x01 -CAN_STATUS_OVRRUN = 0x02 -CAN_STATUS_ERRLIM = 0x04 -CAN_STATUS_BUSOFF = 0x08 -CAN_STATUS_ININIT = 0x10 -CAN_STATUS_BUSCERR = 0x20 +CAN_STATUS_TXPEND = 0x01 +CAN_STATUS_OVRRUN = 0x02 +CAN_STATUS_ERRLIM = 0x04 +CAN_STATUS_BUSOFF = 0x08 +CAN_STATUS_ININIT = 0x10 +CAN_STATUS_BUSCERR = 0x20 # Controller operating modes CAN_OPMODE_UNDEFINED = 0x00 -CAN_OPMODE_STANDARD = 0x01 -CAN_OPMODE_EXTENDED = 0x02 -CAN_OPMODE_ERRFRAME = 0x04 -CAN_OPMODE_LISTONLY = 0x08 -CAN_OPMODE_LOWSPEED = 0x10 +CAN_OPMODE_STANDARD = 0x01 +CAN_OPMODE_EXTENDED = 0x02 +CAN_OPMODE_ERRFRAME = 0x04 +CAN_OPMODE_LISTONLY = 0x08 +CAN_OPMODE_LOWSPEED = 0x10 # Message types -CAN_MSGTYPE_DATA = 0 -CAN_MSGTYPE_INFO = 1 -CAN_MSGTYPE_ERROR = 2 -CAN_MSGTYPE_STATUS = 3 -CAN_MSGTYPE_WAKEUP = 4 +CAN_MSGTYPE_DATA = 0 +CAN_MSGTYPE_INFO = 1 +CAN_MSGTYPE_ERROR = 2 +CAN_MSGTYPE_STATUS = 3 +CAN_MSGTYPE_WAKEUP = 4 CAN_MSGTYPE_TIMEOVR = 5 CAN_MSGTYPE_TIMERST = 6 # Information supplied in the abData[0] field of info frames # (CANMSGINFO.Bytes.bType = CAN_MSGTYPE_INFO). -CAN_INFO_START = 1 -CAN_INFO_STOP = 2 -CAN_INFO_RESET = 3 +CAN_INFO_START = 1 +CAN_INFO_STOP = 2 +CAN_INFO_RESET = 3 # Information supplied in the abData[0] field of info frames # (CANMSGINFO.Bytes.bType = CAN_MSGTYPE_ERROR). -CAN_ERROR_STUFF = 1 # stuff error -CAN_ERROR_FORM = 2 # form error -CAN_ERROR_ACK = 3 # acknowledgment error -CAN_ERROR_BIT = 4 # bit error -CAN_ERROR_CRC = 6 # CRC error -CAN_ERROR_OTHER = 7 # other (unspecified) error +CAN_ERROR_STUFF = 1 # stuff error +CAN_ERROR_FORM = 2 # form error +CAN_ERROR_ACK = 3 # acknowledgment error +CAN_ERROR_BIT = 4 # bit error +CAN_ERROR_CRC = 6 # CRC error +CAN_ERROR_OTHER = 7 # other (unspecified) error # acceptance code and mask to reject all CAN IDs -CAN_ACC_MASK_NONE = 0xFFFFFFFF -CAN_ACC_CODE_NONE = 0x80000000 +CAN_ACC_MASK_NONE = 0xFFFFFFFF +CAN_ACC_CODE_NONE = 0x80000000 diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index efce76297..d89a98334 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -8,7 +8,7 @@ from can import CanError -__all__ = ['VCITimeout', 'VCIError', 'VCIRxQueueEmptyError', 'VCIDeviceNotFoundError'] +__all__ = ["VCITimeout", "VCIError", "VCIRxQueueEmptyError", "VCIDeviceNotFoundError"] class VCITimeout(CanError): diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index 65b177d94..881d47a13 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -8,19 +8,18 @@ import ctypes + class LUID(ctypes.Structure): - _fields_ = [ - ("LowPart", ctypes.c_uint32), - ("HighPart", ctypes.c_int32), - ] + _fields_ = [("LowPart", ctypes.c_uint32), ("HighPart", ctypes.c_int32)] + + PLUID = ctypes.POINTER(LUID) class VCIID(ctypes.Union): - _fields_ = [ - ("AsLuid", LUID), - ("AsInt64", ctypes.c_int64), - ] + _fields_ = [("AsLuid", LUID), ("AsInt64", ctypes.c_int64)] + + PVCIID = ctypes.POINTER(VCIID) @@ -35,10 +34,8 @@ class GUID(ctypes.Structure): class VCIDEVICEINFO(ctypes.Structure): class UniqueHardwareId(ctypes.Union): - _fields_ = [ - ("AsChar", ctypes.c_char * 16), - ("AsGuid", GUID), - ] + _fields_ = [("AsChar", ctypes.c_char * 16), ("AsGuid", GUID)] + _fields_ = [ ("VciObjectId", VCIID), ("DeviceClass", GUID), @@ -66,8 +63,10 @@ def __str__(self): self.DriverReleaseVersion, self.DriverMajorVersion, self.DriverMinorVersion, - self.DriverBuildVersion + self.DriverBuildVersion, ) + + PVCIDEVICEINFO = ctypes.POINTER(VCIDEVICEINFO) @@ -77,8 +76,10 @@ class CANLINESTATUS(ctypes.Structure): ("bBtReg0", ctypes.c_uint8), ("bBtReg1", ctypes.c_uint8), ("bBusLoad", ctypes.c_uint8), - ("dwStatus", ctypes.c_uint32) + ("dwStatus", ctypes.c_uint32), ] + + PCANLINESTATUS = ctypes.POINTER(CANLINESTATUS) @@ -88,8 +89,10 @@ class CANCHANSTATUS(ctypes.Structure): ("fActivated", ctypes.c_uint32), ("fRxOverrun", ctypes.c_uint32), ("bRxFifoLoad", ctypes.c_uint8), - ("bTxFifoLoad", ctypes.c_uint8) + ("bTxFifoLoad", ctypes.c_uint8), ] + + PCANCHANSTATUS = ctypes.POINTER(CANCHANSTATUS) @@ -103,8 +106,10 @@ class CANCAPABILITIES(ctypes.Structure): ("dwCmsDivisor", ctypes.c_uint32), ("dwCmsMaxTicks", ctypes.c_uint32), ("dwDtxDivisor", ctypes.c_uint32), - ("dwDtxMaxTicks", ctypes.c_uint32) + ("dwDtxMaxTicks", ctypes.c_uint32), ] + + PCANCAPABILITIES = ctypes.POINTER(CANCAPABILITIES) @@ -128,13 +133,12 @@ class Bits(ctypes.Structure): ("srr", ctypes.c_uint32, 1), ("rtr", ctypes.c_uint32, 1), ("ext", ctypes.c_uint32, 1), - ("afc", ctypes.c_uint32, 8) + ("afc", ctypes.c_uint32, 8), ] - _fields_ = [ - ("Bytes", Bytes), - ("Bits", Bits) - ] + _fields_ = [("Bytes", Bytes), ("Bits", Bits)] + + PCANMSGINFO = ctypes.POINTER(CANMSGINFO) @@ -143,10 +147,13 @@ class CANMSG(ctypes.Structure): ("dwTime", ctypes.c_uint32), ("dwMsgId", ctypes.c_uint32), ("uMsgInfo", CANMSGINFO), - ("abData", ctypes.c_uint8 * 8) + ("abData", ctypes.c_uint8 * 8), ] + + PCANMSG = ctypes.POINTER(CANMSG) + class CANCYCLICTXMSG(ctypes.Structure): _fields_ = [ ("wCycleTime", ctypes.c_uint16), @@ -154,6 +161,8 @@ class CANCYCLICTXMSG(ctypes.Structure): ("bByteIndex", ctypes.c_uint8), ("dwMsgId", ctypes.c_uint32), ("uMsgInfo", CANMSGINFO), - ("abData", ctypes.c_uint8 * 8) + ("abData", ctypes.c_uint8 * 8), ] + + PCANCYCLICTXMSG = ctypes.POINTER(CANCYCLICTXMSG) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index c89d0e047..f16004c3b 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -18,7 +18,7 @@ from . import constants as canstat from . import structures -log = logging.getLogger('can.kvaser') +log = logging.getLogger("can.kvaser") # Resolution in us TIMESTAMP_RESOLUTION = 10 @@ -38,22 +38,22 @@ def _unimplemented_function(*args): - raise NotImplementedError('This function is not implemented in canlib') + raise NotImplementedError("This function is not implemented in canlib") def __get_canlib_function(func_name, argtypes=None, restype=None, errcheck=None): argtypes = [] if argtypes is None else argtypes - #log.debug('Wrapping function "%s"' % func_name) + # log.debug('Wrapping function "%s"' % func_name) try: # e.g. canlib.canBusOn retval = getattr(__canlib, func_name) - #log.debug('"%s" found in library', func_name) + # log.debug('"%s" found in library', func_name) except AttributeError: log.warning('"%s" was not found in library', func_name) return _unimplemented_function else: - #log.debug('Result type is: %s' % type(restype)) - #log.debug('Error check function is: %s' % errcheck) + # log.debug('Result type is: %s' % type(restype)) + # log.debug('Error check function is: %s' % errcheck) retval.argtypes = argtypes retval.restype = restype if errcheck: @@ -74,8 +74,10 @@ def __init__(self, function, error_code, arguments): self.arguments = arguments def __str__(self): - return "Function %s failed - %s" % (self.function.__name__, - self.__get_error_message()) + return "Function %s failed - %s" % ( + self.function.__name__, + self.__get_error_message(), + ) def __get_error_message(self): errmsg = ctypes.create_string_buffer(128) @@ -84,7 +86,7 @@ def __get_error_message(self): def __convert_can_status_to_int(result): - #log.debug("converting can status to int {} ({})".format(result, type(result))) + # log.debug("converting can status to int {} ({})".format(result, type(result))) if isinstance(result, int): return result else: @@ -94,7 +96,7 @@ def __convert_can_status_to_int(result): def __check_status(result, function, arguments): result = __convert_can_status_to_int(result) if not canstat.CANSTATUS_SUCCESS(result): - #log.debug('Detected error while checking CAN status') + # log.debug('Detected error while checking CAN status') raise CANLIBError(function, result, arguments) return result @@ -102,7 +104,7 @@ def __check_status(result, function, arguments): def __check_status_read(result, function, arguments): result = __convert_can_status_to_int(result) if not canstat.CANSTATUS_SUCCESS(result) and result != canstat.canERR_NOMSG: - #log.debug('Detected error in which checking status read') + # log.debug('Detected error in which checking status read') raise CANLIBError(function, result, arguments) return result @@ -110,6 +112,7 @@ def __check_status_read(result, function, arguments): class c_canHandle(ctypes.c_int): pass + canINVALID_HANDLE = -1 @@ -124,141 +127,187 @@ def __check_bus_handle_validity(handle, function, arguments): else: return handle + if __canlib is not None: canInitializeLibrary = __get_canlib_function("canInitializeLibrary") - canGetErrorText = __get_canlib_function("canGetErrorText", - argtypes=[canstat.c_canStatus, ctypes.c_char_p, ctypes.c_uint], - restype=canstat.c_canStatus, - errcheck=__check_status) + canGetErrorText = __get_canlib_function( + "canGetErrorText", + argtypes=[canstat.c_canStatus, ctypes.c_char_p, ctypes.c_uint], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) # TODO wrap this type of function to provide a more Pythonic API - canGetNumberOfChannels = __get_canlib_function("canGetNumberOfChannels", - argtypes=[ctypes.c_void_p], - restype=canstat.c_canStatus, - errcheck=__check_status) - - kvReadTimer = __get_canlib_function("kvReadTimer", - argtypes=[c_canHandle, - ctypes.POINTER(ctypes.c_uint)], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canBusOff = __get_canlib_function("canBusOff", - argtypes=[c_canHandle], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canBusOn = __get_canlib_function("canBusOn", - argtypes=[c_canHandle], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canClose = __get_canlib_function("canClose", - argtypes=[c_canHandle], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canOpenChannel = __get_canlib_function("canOpenChannel", - argtypes=[ctypes.c_int, ctypes.c_int], - restype=c_canHandle, - errcheck=__check_bus_handle_validity) - - canSetBusParams = __get_canlib_function("canSetBusParams", - argtypes=[c_canHandle, ctypes.c_long, - ctypes.c_uint, ctypes.c_uint, - ctypes.c_uint, ctypes.c_uint, - ctypes.c_uint], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canSetBusParamsFd = __get_canlib_function("canSetBusParamsFd", - argtypes=[c_canHandle, ctypes.c_long, - ctypes.c_uint, ctypes.c_uint, - ctypes.c_uint], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canSetBusOutputControl = __get_canlib_function("canSetBusOutputControl", - argtypes=[c_canHandle, - ctypes.c_uint], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canSetAcceptanceFilter = __get_canlib_function("canSetAcceptanceFilter", - argtypes=[ - c_canHandle, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_int - ], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canReadWait = __get_canlib_function("canReadWait", - argtypes=[c_canHandle, ctypes.c_void_p, - ctypes.c_void_p, ctypes.c_void_p, - ctypes.c_void_p, ctypes.c_void_p, - ctypes.c_long], - restype=canstat.c_canStatus, - errcheck=__check_status_read) - - canWrite = __get_canlib_function("canWrite", - argtypes=[ - c_canHandle, - ctypes.c_long, - ctypes.c_void_p, - ctypes.c_uint, - ctypes.c_uint], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canWriteSync = __get_canlib_function("canWriteSync", - argtypes=[c_canHandle, ctypes.c_ulong], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canIoCtl = __get_canlib_function("canIoCtl", - argtypes=[c_canHandle, ctypes.c_uint, - ctypes.c_void_p, ctypes.c_uint], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canGetVersion = __get_canlib_function("canGetVersion", - restype=ctypes.c_short, - errcheck=__check_status) - - kvFlashLeds = __get_canlib_function("kvFlashLeds", - argtypes=[c_canHandle, ctypes.c_int, - ctypes.c_int], - restype=ctypes.c_short, - errcheck=__check_status) + canGetNumberOfChannels = __get_canlib_function( + "canGetNumberOfChannels", + argtypes=[ctypes.c_void_p], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + kvReadTimer = __get_canlib_function( + "kvReadTimer", + argtypes=[c_canHandle, ctypes.POINTER(ctypes.c_uint)], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canBusOff = __get_canlib_function( + "canBusOff", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canBusOn = __get_canlib_function( + "canBusOn", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canClose = __get_canlib_function( + "canClose", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canOpenChannel = __get_canlib_function( + "canOpenChannel", + argtypes=[ctypes.c_int, ctypes.c_int], + restype=c_canHandle, + errcheck=__check_bus_handle_validity, + ) + + canSetBusParams = __get_canlib_function( + "canSetBusParams", + argtypes=[ + c_canHandle, + ctypes.c_long, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canSetBusParamsFd = __get_canlib_function( + "canSetBusParamsFd", + argtypes=[ + c_canHandle, + ctypes.c_long, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canSetBusOutputControl = __get_canlib_function( + "canSetBusOutputControl", + argtypes=[c_canHandle, ctypes.c_uint], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canSetAcceptanceFilter = __get_canlib_function( + "canSetAcceptanceFilter", + argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_uint, ctypes.c_int], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canReadWait = __get_canlib_function( + "canReadWait", + argtypes=[ + c_canHandle, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_long, + ], + restype=canstat.c_canStatus, + errcheck=__check_status_read, + ) + + canWrite = __get_canlib_function( + "canWrite", + argtypes=[ + c_canHandle, + ctypes.c_long, + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_uint, + ], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canWriteSync = __get_canlib_function( + "canWriteSync", + argtypes=[c_canHandle, ctypes.c_ulong], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canIoCtl = __get_canlib_function( + "canIoCtl", + argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canGetVersion = __get_canlib_function( + "canGetVersion", restype=ctypes.c_short, errcheck=__check_status + ) + + kvFlashLeds = __get_canlib_function( + "kvFlashLeds", + argtypes=[c_canHandle, ctypes.c_int, ctypes.c_int], + restype=ctypes.c_short, + errcheck=__check_status, + ) if sys.platform == "win32": - canGetVersionEx = __get_canlib_function("canGetVersionEx", - argtypes=[ctypes.c_uint], - restype=ctypes.c_uint, - errcheck=__check_status) - - canGetChannelData = __get_canlib_function("canGetChannelData", - argtypes=[ctypes.c_int, - ctypes.c_int, - ctypes.c_void_p, - ctypes.c_size_t], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canRequestBusStatistics = __get_canlib_function("canRequestBusStatistics", - argtypes=[c_canHandle], - restype=canstat.c_canStatus, - errcheck=__check_status) - - canGetBusStatistics = __get_canlib_function("canGetBusStatistics", - argtypes=[c_canHandle, - ctypes.POINTER(structures.BusStatistics), - ctypes.c_size_t], - restype=canstat.c_canStatus, - errcheck=__check_status) + canGetVersionEx = __get_canlib_function( + "canGetVersionEx", + argtypes=[ctypes.c_uint], + restype=ctypes.c_uint, + errcheck=__check_status, + ) + + canGetChannelData = __get_canlib_function( + "canGetChannelData", + argtypes=[ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canRequestBusStatistics = __get_canlib_function( + "canRequestBusStatistics", + argtypes=[c_canHandle], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) + + canGetBusStatistics = __get_canlib_function( + "canGetBusStatistics", + argtypes=[ + c_canHandle, + ctypes.POINTER(structures.BusStatistics), + ctypes.c_size_t, + ], + restype=canstat.c_canStatus, + errcheck=__check_status, + ) def init_kvaser_library(): @@ -284,7 +333,7 @@ def init_kvaser_library(): 83000: canstat.canBITRATE_83K, 62000: canstat.canBITRATE_62K, 50000: canstat.canBITRATE_50K, - 10000: canstat.canBITRATE_10K + 10000: canstat.canBITRATE_10K, } BITRATE_FD = { @@ -292,7 +341,7 @@ def init_kvaser_library(): 1000000: canstat.canFD_BITRATE_1M_80P, 2000000: canstat.canFD_BITRATE_2M_80P, 4000000: canstat.canFD_BITRATE_4M_80P, - 8000000: canstat.canFD_BITRATE_8M_60P + 8000000: canstat.canFD_BITRATE_8M_60P, } @@ -353,34 +402,34 @@ def __init__(self, channel, can_filters=None, **kwargs): log.info("CAN Filters: {}".format(can_filters)) log.info("Got configuration of: {}".format(kwargs)) - bitrate = kwargs.get('bitrate', 500000) - tseg1 = kwargs.get('tseg1', 0) - tseg2 = kwargs.get('tseg2', 0) - sjw = kwargs.get('sjw', 0) - no_samp = kwargs.get('no_samp', 0) - driver_mode = kwargs.get('driver_mode', DRIVER_MODE_NORMAL) - single_handle = kwargs.get('single_handle', False) - receive_own_messages = kwargs.get('receive_own_messages', False) - accept_virtual = kwargs.get('accept_virtual', True) - fd = kwargs.get('fd', False) - data_bitrate = kwargs.get('data_bitrate', None) + bitrate = kwargs.get("bitrate", 500000) + tseg1 = kwargs.get("tseg1", 0) + tseg2 = kwargs.get("tseg2", 0) + sjw = kwargs.get("sjw", 0) + no_samp = kwargs.get("no_samp", 0) + driver_mode = kwargs.get("driver_mode", DRIVER_MODE_NORMAL) + single_handle = kwargs.get("single_handle", False) + receive_own_messages = kwargs.get("receive_own_messages", False) + accept_virtual = kwargs.get("accept_virtual", True) + fd = kwargs.get("fd", False) + data_bitrate = kwargs.get("data_bitrate", None) try: channel = int(channel) except ValueError: - raise ValueError('channel must be an integer') + raise ValueError("channel must be an integer") self.channel = channel - log.debug('Initialising bus instance') + log.debug("Initialising bus instance") self.single_handle = single_handle num_channels = ctypes.c_int(0) - #log.debug("Res: %d", canGetNumberOfChannels(ctypes.byref(num_channels))) + # log.debug("Res: %d", canGetNumberOfChannels(ctypes.byref(num_channels))) num_channels = int(num_channels.value) - log.info('Found %d available channels', num_channels) + log.info("Found %d available channels", num_channels) for idx in range(num_channels): channel_info = get_channel_info(idx) - log.info('%d: %s', idx, channel_info) + log.info("%d: %s", idx, channel_info) if idx == channel: self.channel_info = channel_info @@ -390,15 +439,17 @@ def __init__(self, channel, can_filters=None, **kwargs): if fd: flags |= canstat.canOPEN_CAN_FD - log.debug('Creating read handle to bus channel: %s', channel) + log.debug("Creating read handle to bus channel: %s", channel) self._read_handle = canOpenChannel(channel, flags) - canIoCtl(self._read_handle, - canstat.canIOCTL_SET_TIMER_SCALE, - ctypes.byref(ctypes.c_long(TIMESTAMP_RESOLUTION)), - 4) + canIoCtl( + self._read_handle, + canstat.canIOCTL_SET_TIMER_SCALE, + ctypes.byref(ctypes.c_long(TIMESTAMP_RESOLUTION)), + 4, + ) if fd: - if 'tseg1' not in kwargs and bitrate in BITRATE_FD: + if "tseg1" not in kwargs and bitrate in BITRATE_FD: # Use predefined bitrate for arbitration bitrate = BITRATE_FD[bitrate] if data_bitrate in BITRATE_FD: @@ -409,7 +460,7 @@ def __init__(self, channel, can_filters=None, **kwargs): data_bitrate = bitrate canSetBusParamsFd(self._read_handle, data_bitrate, tseg1, tseg2, sjw) else: - if 'tseg1' not in kwargs and bitrate in BITRATE_OBJS: + if "tseg1" not in kwargs and bitrate in BITRATE_OBJS: bitrate = BITRATE_OBJS[bitrate] canSetBusParams(self._read_handle, bitrate, tseg1, tseg2, sjw, no_samp, 0) @@ -417,22 +468,28 @@ def __init__(self, channel, can_filters=None, **kwargs): local_echo = single_handle or receive_own_messages if receive_own_messages and single_handle: log.warning("receive_own_messages only works if single_handle is False") - canIoCtl(self._read_handle, - canstat.canIOCTL_SET_LOCAL_TXECHO, - ctypes.byref(ctypes.c_byte(local_echo)), - 1) + canIoCtl( + self._read_handle, + canstat.canIOCTL_SET_LOCAL_TXECHO, + ctypes.byref(ctypes.c_byte(local_echo)), + 1, + ) if self.single_handle: log.debug("We don't require separate handles to the bus") self._write_handle = self._read_handle else: - log.debug('Creating separate handle for TX on channel: %s', channel) + log.debug("Creating separate handle for TX on channel: %s", channel) self._write_handle = canOpenChannel(channel, flags) canBusOn(self._read_handle) - can_driver_mode = canstat.canDRIVER_SILENT if driver_mode == DRIVER_MODE_SILENT else canstat.canDRIVER_NORMAL + can_driver_mode = ( + canstat.canDRIVER_SILENT + if driver_mode == DRIVER_MODE_SILENT + else canstat.canDRIVER_NORMAL + ) canSetBusOutputControl(self._write_handle, can_driver_mode) - log.debug('Going bus on TX handle') + log.debug("Going bus on TX handle") canBusOn(self._write_handle) timer = ctypes.c_uint(0) @@ -448,22 +505,22 @@ def __init__(self, channel, can_filters=None, **kwargs): def _apply_filters(self, filters): if filters and len(filters) == 1: - can_id = filters[0]['can_id'] - can_mask = filters[0]['can_mask'] - extended = 1 if filters[0].get('extended') else 0 + can_id = filters[0]["can_id"] + can_mask = filters[0]["can_mask"] + extended = 1 if filters[0].get("extended") else 0 try: for handle in (self._read_handle, self._write_handle): canSetAcceptanceFilter(handle, can_id, can_mask, extended) except (NotImplementedError, CANLIBError) as e: self._is_filtered = False - log.error('Filtering is not supported - %s', e) + log.error("Filtering is not supported - %s", e) else: self._is_filtered = True - log.info('canlib is filtering on ID 0x%X, mask 0x%X', can_id, can_mask) + log.info("canlib is filtering on ID 0x%X, mask 0x%X", can_id, can_mask) else: self._is_filtered = False - log.info('Hardware filtering has been disabled') + log.info("Hardware filtering has been disabled") try: for handle in (self._read_handle, self._write_handle): for extended in (0, 1): @@ -494,7 +551,7 @@ def _recv_internal(self, timeout=None): else: timeout = int(timeout * 1000) - #log.log(9, 'Reading for %d ms on handle: %s' % (timeout, self._read_handle)) + # log.log(9, 'Reading for %d ms on handle: %s' % (timeout, self._read_handle)) status = canReadWait( self._read_handle, ctypes.byref(arb_id), @@ -502,11 +559,11 @@ def _recv_internal(self, timeout=None): ctypes.byref(dlc), ctypes.byref(flags), ctypes.byref(timestamp), - timeout # This is an X ms blocking read + timeout, # This is an X ms blocking read ) if status == canstat.canOK: - #log.debug('read complete -> status OK') + # log.debug('read complete -> status OK') data_array = data.raw flags = flags.value is_extended = bool(flags & canstat.canMSG_EXT) @@ -516,25 +573,27 @@ def _recv_internal(self, timeout=None): bitrate_switch = bool(flags & canstat.canFDMSG_BRS) error_state_indicator = bool(flags & canstat.canFDMSG_ESI) msg_timestamp = timestamp.value * TIMESTAMP_FACTOR - rx_msg = Message(arbitration_id=arb_id.value, - data=data_array[:dlc.value], - dlc=dlc.value, - is_extended_id=is_extended, - is_error_frame=is_error_frame, - is_remote_frame=is_remote_frame, - is_fd=is_fd, - bitrate_switch=bitrate_switch, - error_state_indicator=error_state_indicator, - channel=self.channel, - timestamp=msg_timestamp + self._timestamp_offset) - #log.debug('Got message: %s' % rx_msg) + rx_msg = Message( + arbitration_id=arb_id.value, + data=data_array[: dlc.value], + dlc=dlc.value, + is_extended_id=is_extended, + is_error_frame=is_error_frame, + is_remote_frame=is_remote_frame, + is_fd=is_fd, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator, + channel=self.channel, + timestamp=msg_timestamp + self._timestamp_offset, + ) + # log.debug('Got message: %s' % rx_msg) return rx_msg, self._is_filtered else: - #log.debug('read complete -> status not okay') + # log.debug('read complete -> status not okay') return None, self._is_filtered def send(self, msg, timeout=None): - #log.debug("Writing a message: {}".format(msg)) + # log.debug("Writing a message: {}".format(msg)) flags = canstat.canMSG_EXT if msg.is_extended_id else canstat.canMSG_STD if msg.is_remote_frame: flags |= canstat.canMSG_RTR @@ -546,11 +605,9 @@ def send(self, msg, timeout=None): flags |= canstat.canFDMSG_BRS ArrayConstructor = ctypes.c_byte * msg.dlc buf = ArrayConstructor(*msg.data) - canWrite(self._write_handle, - msg.arbitration_id, - ctypes.byref(buf), - msg.dlc, - flags) + canWrite( + self._write_handle, msg.arbitration_id, ctypes.byref(buf), msg.dlc, flags + ) if timeout: canWriteSync(self._write_handle, int(timeout * 1000)) @@ -567,7 +624,7 @@ def flash(self, flash=True): try: kvFlashLeds(self._read_handle, action, 30000) except (CANLIBError, NotImplementedError) as e: - log.error('Could not flash LEDs (%s)', e) + log.error("Could not flash LEDs (%s)", e) def shutdown(self): # Wait for transmit queue to be cleared @@ -597,9 +654,9 @@ def get_stats(self): """ canRequestBusStatistics(self._write_handle) stats = structures.BusStatistics() - canGetBusStatistics(self._write_handle, - ctypes.pointer(stats), - ctypes.sizeof(stats)) + canGetBusStatistics( + self._write_handle, ctypes.pointer(stats), ctypes.sizeof(stats) + ) return stats @staticmethod @@ -610,7 +667,7 @@ def _detect_available_configs(): except Exception: pass return [ - {'interface': 'kvaser', 'channel': channel} + {"interface": "kvaser", "channel": channel} for channel in range(num_channels.value) ] @@ -620,18 +677,30 @@ def get_channel_info(channel): serial = ctypes.c_uint64() number = ctypes.c_uint() - canGetChannelData(channel, - canstat.canCHANNELDATA_DEVDESCR_ASCII, - ctypes.byref(name), ctypes.sizeof(name)) - canGetChannelData(channel, - canstat.canCHANNELDATA_CARD_SERIAL_NO, - ctypes.byref(serial), ctypes.sizeof(serial)) - canGetChannelData(channel, - canstat.canCHANNELDATA_CHAN_NO_ON_CARD, - ctypes.byref(number), ctypes.sizeof(number)) - - return '%s, S/N %d (#%d)' % ( - name.value.decode("ascii"), serial.value, number.value + 1) + canGetChannelData( + channel, + canstat.canCHANNELDATA_DEVDESCR_ASCII, + ctypes.byref(name), + ctypes.sizeof(name), + ) + canGetChannelData( + channel, + canstat.canCHANNELDATA_CARD_SERIAL_NO, + ctypes.byref(serial), + ctypes.sizeof(serial), + ) + canGetChannelData( + channel, + canstat.canCHANNELDATA_CHAN_NO_ON_CARD, + ctypes.byref(number), + ctypes.sizeof(number), + ) + + return "%s, S/N %d (#%d)" % ( + name.value.decode("ascii"), + serial.value, + number.value + 1, + ) init_kvaser_library() diff --git a/can/interfaces/kvaser/constants.py b/can/interfaces/kvaser/constants.py index 0188235be..bc31381c6 100644 --- a/can/interfaces/kvaser/constants.py +++ b/can/interfaces/kvaser/constants.py @@ -14,6 +14,7 @@ class c_canStatus(ctypes.c_int): pass + # TODO better formatting canOK = 0 canERR_PARAM = -1 @@ -54,7 +55,8 @@ class c_canStatus(ctypes.c_int): def CANSTATUS_SUCCESS(status): return status >= canOK -canMSG_MASK = 0x00ff + +canMSG_MASK = 0x00FF canMSG_RTR = 0x0001 canMSG_STD = 0x0002 canMSG_EXT = 0x0004 @@ -68,7 +70,7 @@ def CANSTATUS_SUCCESS(status): canFDMSG_BRS = 0x020000 canFDMSG_ESI = 0x040000 -canMSGERR_MASK = 0xff00 +canMSGERR_MASK = 0xFF00 canMSGERR_HW_OVERRUN = 0x0200 canMSGERR_SW_OVERRUN = 0x0400 canMSGERR_STUFF = 0x0800 @@ -153,7 +155,7 @@ def CANSTATUS_SUCCESS(status): canTRANSCEIVER_TYPE_LINX_J1708: "LINX_J1708", canTRANSCEIVER_TYPE_LINX_K: "LINX_K", canTRANSCEIVER_TYPE_LINX_SWC: "LINX_SWC", - canTRANSCEIVER_TYPE_LINX_LS: "LINX_LS" + canTRANSCEIVER_TYPE_LINX_LS: "LINX_LS", } canDRIVER_NORMAL = 4 diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index 21eefe86f..5b6d416d8 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -15,6 +15,7 @@ class BusStatistics(ctypes.Structure): .. seealso:: :meth:`KvaserBus.get_stats` """ + _fields_ = [ ("m_stdData", ctypes.c_ulong), ("m_stdRemote", ctypes.c_ulong), @@ -22,12 +23,14 @@ class BusStatistics(ctypes.Structure): ("m_extRemote", ctypes.c_ulong), ("m_errFrame", ctypes.c_ulong), ("m_busLoad", ctypes.c_ulong), - ("m_overruns", ctypes.c_ulong) + ("m_overruns", ctypes.c_ulong), ] def __str__(self): - return ("std_data: {}, std_remote: {}, ext_data: {}, ext_remote: {}, " - "err_frame: {}, bus_load: {:.1f}%, overruns: {}").format( + return ( + "std_data: {}, std_remote: {}, ext_data: {}, ext_remote: {}, " + "err_frame: {}, bus_load: {:.1f}%, overruns: {}" + ).format( self.std_data, self.std_remote, self.ext_data, diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index d05acf850..2d5abf0d0 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -23,35 +23,35 @@ logger = logging.getLogger(__name__) -NC_SUCCESS = 0 -NC_ERR_TIMEOUT = 1 -TIMEOUT_ERROR_CODE = -1074388991 +NC_SUCCESS = 0 +NC_ERR_TIMEOUT = 1 +TIMEOUT_ERROR_CODE = -1074388991 -NC_DURATION_INFINITE = 0xFFFFFFFF +NC_DURATION_INFINITE = 0xFFFFFFFF -NC_OP_START = 0x80000001 -NC_OP_STOP = 0x80000002 -NC_OP_RESET = 0x80000003 +NC_OP_START = 0x80000001 +NC_OP_STOP = 0x80000002 +NC_OP_RESET = 0x80000003 -NC_FRMTYPE_REMOTE = 1 -NC_FRMTYPE_COMM_ERR = 2 +NC_FRMTYPE_REMOTE = 1 +NC_FRMTYPE_COMM_ERR = 2 -NC_ST_READ_AVAIL = 0x00000001 -NC_ST_WRITE_SUCCESS = 0x00000002 -NC_ST_ERROR = 0x00000010 -NC_ST_WARNING = 0x00000020 +NC_ST_READ_AVAIL = 0x00000001 +NC_ST_WRITE_SUCCESS = 0x00000002 +NC_ST_ERROR = 0x00000010 +NC_ST_WARNING = 0x00000020 -NC_ATTR_BAUD_RATE = 0x80000007 +NC_ATTR_BAUD_RATE = 0x80000007 NC_ATTR_START_ON_OPEN = 0x80000006 -NC_ATTR_READ_Q_LEN = 0x80000013 -NC_ATTR_WRITE_Q_LEN = 0x80000014 -NC_ATTR_CAN_COMP_STD = 0x80010001 -NC_ATTR_CAN_MASK_STD = 0x80010002 -NC_ATTR_CAN_COMP_XTD = 0x80010003 -NC_ATTR_CAN_MASK_XTD = 0x80010004 +NC_ATTR_READ_Q_LEN = 0x80000013 +NC_ATTR_WRITE_Q_LEN = 0x80000014 +NC_ATTR_CAN_COMP_STD = 0x80010001 +NC_ATTR_CAN_MASK_STD = 0x80010002 +NC_ATTR_CAN_COMP_XTD = 0x80010003 +NC_ATTR_CAN_MASK_XTD = 0x80010004 NC_ATTR_LOG_COMM_ERRS = 0x8001000A -NC_FL_CAN_ARBID_XTD = 0x20000000 +NC_FL_CAN_ARBID_XTD = 0x20000000 CanData = ctypes.c_ubyte * 8 @@ -66,6 +66,7 @@ class RxMessageStruct(ctypes.Structure): ("data", CanData), ] + class TxMessageStruct(ctypes.Structure): _fields_ = [ ("arb_id", ctypes.c_ulong), @@ -98,7 +99,11 @@ def get_error_message(status_code): logger.error("Failed to load NI-CAN driver: %s", e) else: nican.ncConfig.argtypes = [ - ctypes.c_char_p, ctypes.c_ulong, ctypes.c_void_p, ctypes.c_void_p] + ctypes.c_char_p, + ctypes.c_ulong, + ctypes.c_void_p, + ctypes.c_void_p, + ] nican.ncConfig.errcheck = check_status nican.ncOpenObject.argtypes = [ctypes.c_char_p, ctypes.c_void_p] nican.ncOpenObject.errcheck = check_status @@ -108,14 +113,18 @@ def get_error_message(status_code): nican.ncRead.errcheck = check_status nican.ncWrite.errcheck = check_status nican.ncWaitForState.argtypes = [ - ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_void_p] + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_void_p, + ] nican.ncWaitForState.errcheck = check_status - nican.ncStatusToString.argtypes = [ - ctypes.c_int, ctypes.c_uint, ctypes.c_char_p] + nican.ncStatusToString.argtypes = [ctypes.c_int, ctypes.c_uint, ctypes.c_char_p] else: nican = None logger.warning("NI-CAN interface is only available on Windows systems") + class NicanBus(BusABC): """ The CAN Bus implemented for the NI-CAN interface. @@ -129,7 +138,9 @@ class NicanBus(BusABC): """ - def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **kwargs): + def __init__( + self, channel, can_filters=None, bitrate=None, log_errors=True, **kwargs + ): """ :param str channel: Name of the object to open (e.g. 'CAN0') @@ -150,42 +161,47 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **k """ if nican is None: - raise ImportError("The NI-CAN driver could not be loaded. " - "Check that you are using 32-bit Python on Windows.") + raise ImportError( + "The NI-CAN driver could not be loaded. " + "Check that you are using 32-bit Python on Windows." + ) self.channel = channel self.channel_info = "NI-CAN: " + channel if not isinstance(channel, bytes): channel = channel.encode() - config = [ - (NC_ATTR_START_ON_OPEN, True), - (NC_ATTR_LOG_COMM_ERRS, log_errors) - ] + config = [(NC_ATTR_START_ON_OPEN, True), (NC_ATTR_LOG_COMM_ERRS, log_errors)] if not can_filters: logger.info("Filtering has been disabled") - config.extend([ - (NC_ATTR_CAN_COMP_STD, 0), - (NC_ATTR_CAN_MASK_STD, 0), - (NC_ATTR_CAN_COMP_XTD, 0), - (NC_ATTR_CAN_MASK_XTD, 0) - ]) + config.extend( + [ + (NC_ATTR_CAN_COMP_STD, 0), + (NC_ATTR_CAN_MASK_STD, 0), + (NC_ATTR_CAN_COMP_XTD, 0), + (NC_ATTR_CAN_MASK_XTD, 0), + ] + ) else: for can_filter in can_filters: can_id = can_filter["can_id"] can_mask = can_filter["can_mask"] logger.info("Filtering on ID 0x%X, mask 0x%X", can_id, can_mask) if can_filter.get("extended"): - config.extend([ - (NC_ATTR_CAN_COMP_XTD, can_id | NC_FL_CAN_ARBID_XTD), - (NC_ATTR_CAN_MASK_XTD, can_mask) - ]) + config.extend( + [ + (NC_ATTR_CAN_COMP_XTD, can_id | NC_FL_CAN_ARBID_XTD), + (NC_ATTR_CAN_MASK_XTD, can_mask), + ] + ) else: - config.extend([ - (NC_ATTR_CAN_COMP_STD, can_id), - (NC_ATTR_CAN_MASK_STD, can_mask), - ]) + config.extend( + [ + (NC_ATTR_CAN_COMP_STD, can_id), + (NC_ATTR_CAN_MASK_STD, can_mask), + ] + ) if bitrate: config.append((NC_ATTR_BAUD_RATE, bitrate)) @@ -193,16 +209,23 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **k AttrList = ctypes.c_ulong * len(config) attr_id_list = AttrList(*(row[0] for row in config)) attr_value_list = AttrList(*(row[1] for row in config)) - nican.ncConfig(channel, - len(config), - ctypes.byref(attr_id_list), - ctypes.byref(attr_value_list)) + nican.ncConfig( + channel, + len(config), + ctypes.byref(attr_id_list), + ctypes.byref(attr_value_list), + ) self.handle = ctypes.c_ulong() nican.ncOpenObject(channel, ctypes.byref(self.handle)) - super().__init__(channel=channel, can_filters=can_filters, bitrate=bitrate, - log_errors=log_errors, **kwargs) + super().__init__( + channel=channel, + can_filters=can_filters, + bitrate=bitrate, + log_errors=log_errors, + **kwargs + ) def _recv_internal(self, timeout): """ @@ -222,7 +245,8 @@ def _recv_internal(self, timeout): state = ctypes.c_ulong() try: nican.ncWaitForState( - self.handle, NC_ST_READ_AVAIL, timeout, ctypes.byref(state)) + self.handle, NC_ST_READ_AVAIL, timeout, ctypes.byref(state) + ) except NicanError as e: if e.error_code == TIMEOUT_ERROR_CODE: return None, True @@ -240,14 +264,16 @@ def _recv_internal(self, timeout): if not is_error_frame: arb_id &= 0x1FFFFFFF dlc = raw_msg.dlc - msg = Message(timestamp=timestamp, - channel=self.channel, - is_remote_frame=is_remote_frame, - is_error_frame=is_error_frame, - is_extended_id=is_extended, - arbitration_id=arb_id, - dlc=dlc, - data=raw_msg.data[:dlc]) + msg = Message( + timestamp=timestamp, + channel=self.channel, + is_remote_frame=is_remote_frame, + is_error_frame=is_error_frame, + is_extended_id=is_extended, + arbitration_id=arb_id, + dlc=dlc, + data=raw_msg.data[:dlc], + ) return msg, True def send(self, msg, timeout=None): @@ -264,20 +290,18 @@ def send(self, msg, timeout=None): arb_id = msg.arbitration_id if msg.is_extended_id: arb_id |= NC_FL_CAN_ARBID_XTD - raw_msg = TxMessageStruct(arb_id, - bool(msg.is_remote_frame), - msg.dlc, - CanData(*msg.data)) - nican.ncWrite( - self.handle, ctypes.sizeof(raw_msg), ctypes.byref(raw_msg)) + raw_msg = TxMessageStruct( + arb_id, bool(msg.is_remote_frame), msg.dlc, CanData(*msg.data) + ) + nican.ncWrite(self.handle, ctypes.sizeof(raw_msg), ctypes.byref(raw_msg)) # TODO: # ncWaitForState can not be called here if the recv() method is called # from a different thread, which is a very common use case. # Maybe it is possible to use ncCreateNotification instead but seems a # bit overkill at the moment. - #state = ctypes.c_ulong() - #nican.ncWaitForState( + # state = ctypes.c_ulong() + # nican.ncWaitForState( # self.handle, NC_ST_WRITE_SUCCESS, int(timeout * 1000), ctypes.byref(state)) def reset(self): @@ -307,4 +331,6 @@ def __init__(self, function, error_code, arguments): def __str__(self): return "Function %s failed:\n%s" % ( - self.function.__name__, get_error_message(self.error_code)) + self.function.__name__, + get_error_message(self.error_code), + ) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index f27db0de1..7557f036c 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -17,244 +17,360 @@ import platform import logging -logger = logging.getLogger('can.pcan') +logger = logging.getLogger("can.pcan") -#/////////////////////////////////////////////////////////// +# /////////////////////////////////////////////////////////// # Type definitions -#/////////////////////////////////////////////////////////// - -TPCANHandle = c_ushort # Represents a PCAN hardware channel handle -TPCANStatus = int # Represents a PCAN status/error code -TPCANParameter = c_ubyte # Represents a PCAN parameter to be read or set -TPCANDevice = c_ubyte # Represents a PCAN device -TPCANMessageType = c_ubyte # Represents the type of a PCAN message -TPCANType = c_ubyte # Represents the type of PCAN hardware to be initialized -TPCANMode = c_ubyte # Represents a PCAN filter mode -TPCANBaudrate = c_ushort # Represents a PCAN Baud rate register value -TPCANBitrateFD = c_char_p # Represents a PCAN-FD bit rate string -TPCANTimestampFD = c_ulonglong # Represents a timestamp of a received PCAN FD message - -#/////////////////////////////////////////////////////////// +# /////////////////////////////////////////////////////////// + +TPCANHandle = c_ushort # Represents a PCAN hardware channel handle +TPCANStatus = int # Represents a PCAN status/error code +TPCANParameter = c_ubyte # Represents a PCAN parameter to be read or set +TPCANDevice = c_ubyte # Represents a PCAN device +TPCANMessageType = c_ubyte # Represents the type of a PCAN message +TPCANType = c_ubyte # Represents the type of PCAN hardware to be initialized +TPCANMode = c_ubyte # Represents a PCAN filter mode +TPCANBaudrate = c_ushort # Represents a PCAN Baud rate register value +TPCANBitrateFD = c_char_p # Represents a PCAN-FD bit rate string +TPCANTimestampFD = c_ulonglong # Represents a timestamp of a received PCAN FD message + +# /////////////////////////////////////////////////////////// # Value definitions -#/////////////////////////////////////////////////////////// +# /////////////////////////////////////////////////////////// # Currently defined and supported PCAN channels -PCAN_NONEBUS = TPCANHandle(0x00) # Undefined/default value for a PCAN bus - -PCAN_ISABUS1 = TPCANHandle(0x21) # PCAN-ISA interface, channel 1 -PCAN_ISABUS2 = TPCANHandle(0x22) # PCAN-ISA interface, channel 2 -PCAN_ISABUS3 = TPCANHandle(0x23) # PCAN-ISA interface, channel 3 -PCAN_ISABUS4 = TPCANHandle(0x24) # PCAN-ISA interface, channel 4 -PCAN_ISABUS5 = TPCANHandle(0x25) # PCAN-ISA interface, channel 5 -PCAN_ISABUS6 = TPCANHandle(0x26) # PCAN-ISA interface, channel 6 -PCAN_ISABUS7 = TPCANHandle(0x27) # PCAN-ISA interface, channel 7 -PCAN_ISABUS8 = TPCANHandle(0x28) # PCAN-ISA interface, channel 8 - -PCAN_DNGBUS1 = TPCANHandle(0x31) # PCAN-Dongle/LPT interface, channel 1 - -PCAN_PCIBUS1 = TPCANHandle(0x41) # PCAN-PCI interface, channel 1 -PCAN_PCIBUS2 = TPCANHandle(0x42) # PCAN-PCI interface, channel 2 -PCAN_PCIBUS3 = TPCANHandle(0x43) # PCAN-PCI interface, channel 3 -PCAN_PCIBUS4 = TPCANHandle(0x44) # PCAN-PCI interface, channel 4 -PCAN_PCIBUS5 = TPCANHandle(0x45) # PCAN-PCI interface, channel 5 -PCAN_PCIBUS6 = TPCANHandle(0x46) # PCAN-PCI interface, channel 6 -PCAN_PCIBUS7 = TPCANHandle(0x47) # PCAN-PCI interface, channel 7 -PCAN_PCIBUS8 = TPCANHandle(0x48) # PCAN-PCI interface, channel 8 -PCAN_PCIBUS9 = TPCANHandle(0x409) # PCAN-PCI interface, channel 9 -PCAN_PCIBUS10 = TPCANHandle(0x40A) # PCAN-PCI interface, channel 10 -PCAN_PCIBUS11 = TPCANHandle(0x40B) # PCAN-PCI interface, channel 11 -PCAN_PCIBUS12 = TPCANHandle(0x40C) # PCAN-PCI interface, channel 12 -PCAN_PCIBUS13 = TPCANHandle(0x40D) # PCAN-PCI interface, channel 13 -PCAN_PCIBUS14 = TPCANHandle(0x40E) # PCAN-PCI interface, channel 14 -PCAN_PCIBUS15 = TPCANHandle(0x40F) # PCAN-PCI interface, channel 15 -PCAN_PCIBUS16 = TPCANHandle(0x410) # PCAN-PCI interface, channel 16 - -PCAN_USBBUS1 = TPCANHandle(0x51) # PCAN-USB interface, channel 1 -PCAN_USBBUS2 = TPCANHandle(0x52) # PCAN-USB interface, channel 2 -PCAN_USBBUS3 = TPCANHandle(0x53) # PCAN-USB interface, channel 3 -PCAN_USBBUS4 = TPCANHandle(0x54) # PCAN-USB interface, channel 4 -PCAN_USBBUS5 = TPCANHandle(0x55) # PCAN-USB interface, channel 5 -PCAN_USBBUS6 = TPCANHandle(0x56) # PCAN-USB interface, channel 6 -PCAN_USBBUS7 = TPCANHandle(0x57) # PCAN-USB interface, channel 7 -PCAN_USBBUS8 = TPCANHandle(0x58) # PCAN-USB interface, channel 8 -PCAN_USBBUS9 = TPCANHandle(0x509) # PCAN-USB interface, channel 9 -PCAN_USBBUS10 = TPCANHandle(0x50A) # PCAN-USB interface, channel 10 -PCAN_USBBUS11 = TPCANHandle(0x50B) # PCAN-USB interface, channel 11 -PCAN_USBBUS12 = TPCANHandle(0x50C) # PCAN-USB interface, channel 12 -PCAN_USBBUS13 = TPCANHandle(0x50D) # PCAN-USB interface, channel 13 -PCAN_USBBUS14 = TPCANHandle(0x50E) # PCAN-USB interface, channel 14 -PCAN_USBBUS15 = TPCANHandle(0x50F) # PCAN-USB interface, channel 15 -PCAN_USBBUS16 = TPCANHandle(0x510) # PCAN-USB interface, channel 16 - -PCAN_PCCBUS1 = TPCANHandle(0x61) # PCAN-PC Card interface, channel 1 -PCAN_PCCBUS2 = TPCANHandle(0x62) # PCAN-PC Card interface, channel 2 - -PCAN_LANBUS1 = TPCANHandle(0x801) # PCAN-LAN interface, channel 1 -PCAN_LANBUS2 = TPCANHandle(0x802) # PCAN-LAN interface, channel 2 -PCAN_LANBUS3 = TPCANHandle(0x803) # PCAN-LAN interface, channel 3 -PCAN_LANBUS4 = TPCANHandle(0x804) # PCAN-LAN interface, channel 4 -PCAN_LANBUS5 = TPCANHandle(0x805) # PCAN-LAN interface, channel 5 -PCAN_LANBUS6 = TPCANHandle(0x806) # PCAN-LAN interface, channel 6 -PCAN_LANBUS7 = TPCANHandle(0x807) # PCAN-LAN interface, channel 7 -PCAN_LANBUS8 = TPCANHandle(0x808) # PCAN-LAN interface, channel 8 -PCAN_LANBUS9 = TPCANHandle(0x809) # PCAN-LAN interface, channel 9 -PCAN_LANBUS10 = TPCANHandle(0x80A) # PCAN-LAN interface, channel 10 -PCAN_LANBUS11 = TPCANHandle(0x80B) # PCAN-LAN interface, channel 11 -PCAN_LANBUS12 = TPCANHandle(0x80C) # PCAN-LAN interface, channel 12 -PCAN_LANBUS13 = TPCANHandle(0x80D) # PCAN-LAN interface, channel 13 -PCAN_LANBUS14 = TPCANHandle(0x80E) # PCAN-LAN interface, channel 14 -PCAN_LANBUS15 = TPCANHandle(0x80F) # PCAN-LAN interface, channel 15 -PCAN_LANBUS16 = TPCANHandle(0x810) # PCAN-LAN interface, channel 16 +PCAN_NONEBUS = TPCANHandle(0x00) # Undefined/default value for a PCAN bus + +PCAN_ISABUS1 = TPCANHandle(0x21) # PCAN-ISA interface, channel 1 +PCAN_ISABUS2 = TPCANHandle(0x22) # PCAN-ISA interface, channel 2 +PCAN_ISABUS3 = TPCANHandle(0x23) # PCAN-ISA interface, channel 3 +PCAN_ISABUS4 = TPCANHandle(0x24) # PCAN-ISA interface, channel 4 +PCAN_ISABUS5 = TPCANHandle(0x25) # PCAN-ISA interface, channel 5 +PCAN_ISABUS6 = TPCANHandle(0x26) # PCAN-ISA interface, channel 6 +PCAN_ISABUS7 = TPCANHandle(0x27) # PCAN-ISA interface, channel 7 +PCAN_ISABUS8 = TPCANHandle(0x28) # PCAN-ISA interface, channel 8 + +PCAN_DNGBUS1 = TPCANHandle(0x31) # PCAN-Dongle/LPT interface, channel 1 + +PCAN_PCIBUS1 = TPCANHandle(0x41) # PCAN-PCI interface, channel 1 +PCAN_PCIBUS2 = TPCANHandle(0x42) # PCAN-PCI interface, channel 2 +PCAN_PCIBUS3 = TPCANHandle(0x43) # PCAN-PCI interface, channel 3 +PCAN_PCIBUS4 = TPCANHandle(0x44) # PCAN-PCI interface, channel 4 +PCAN_PCIBUS5 = TPCANHandle(0x45) # PCAN-PCI interface, channel 5 +PCAN_PCIBUS6 = TPCANHandle(0x46) # PCAN-PCI interface, channel 6 +PCAN_PCIBUS7 = TPCANHandle(0x47) # PCAN-PCI interface, channel 7 +PCAN_PCIBUS8 = TPCANHandle(0x48) # PCAN-PCI interface, channel 8 +PCAN_PCIBUS9 = TPCANHandle(0x409) # PCAN-PCI interface, channel 9 +PCAN_PCIBUS10 = TPCANHandle(0x40A) # PCAN-PCI interface, channel 10 +PCAN_PCIBUS11 = TPCANHandle(0x40B) # PCAN-PCI interface, channel 11 +PCAN_PCIBUS12 = TPCANHandle(0x40C) # PCAN-PCI interface, channel 12 +PCAN_PCIBUS13 = TPCANHandle(0x40D) # PCAN-PCI interface, channel 13 +PCAN_PCIBUS14 = TPCANHandle(0x40E) # PCAN-PCI interface, channel 14 +PCAN_PCIBUS15 = TPCANHandle(0x40F) # PCAN-PCI interface, channel 15 +PCAN_PCIBUS16 = TPCANHandle(0x410) # PCAN-PCI interface, channel 16 + +PCAN_USBBUS1 = TPCANHandle(0x51) # PCAN-USB interface, channel 1 +PCAN_USBBUS2 = TPCANHandle(0x52) # PCAN-USB interface, channel 2 +PCAN_USBBUS3 = TPCANHandle(0x53) # PCAN-USB interface, channel 3 +PCAN_USBBUS4 = TPCANHandle(0x54) # PCAN-USB interface, channel 4 +PCAN_USBBUS5 = TPCANHandle(0x55) # PCAN-USB interface, channel 5 +PCAN_USBBUS6 = TPCANHandle(0x56) # PCAN-USB interface, channel 6 +PCAN_USBBUS7 = TPCANHandle(0x57) # PCAN-USB interface, channel 7 +PCAN_USBBUS8 = TPCANHandle(0x58) # PCAN-USB interface, channel 8 +PCAN_USBBUS9 = TPCANHandle(0x509) # PCAN-USB interface, channel 9 +PCAN_USBBUS10 = TPCANHandle(0x50A) # PCAN-USB interface, channel 10 +PCAN_USBBUS11 = TPCANHandle(0x50B) # PCAN-USB interface, channel 11 +PCAN_USBBUS12 = TPCANHandle(0x50C) # PCAN-USB interface, channel 12 +PCAN_USBBUS13 = TPCANHandle(0x50D) # PCAN-USB interface, channel 13 +PCAN_USBBUS14 = TPCANHandle(0x50E) # PCAN-USB interface, channel 14 +PCAN_USBBUS15 = TPCANHandle(0x50F) # PCAN-USB interface, channel 15 +PCAN_USBBUS16 = TPCANHandle(0x510) # PCAN-USB interface, channel 16 + +PCAN_PCCBUS1 = TPCANHandle(0x61) # PCAN-PC Card interface, channel 1 +PCAN_PCCBUS2 = TPCANHandle(0x62) # PCAN-PC Card interface, channel 2 + +PCAN_LANBUS1 = TPCANHandle(0x801) # PCAN-LAN interface, channel 1 +PCAN_LANBUS2 = TPCANHandle(0x802) # PCAN-LAN interface, channel 2 +PCAN_LANBUS3 = TPCANHandle(0x803) # PCAN-LAN interface, channel 3 +PCAN_LANBUS4 = TPCANHandle(0x804) # PCAN-LAN interface, channel 4 +PCAN_LANBUS5 = TPCANHandle(0x805) # PCAN-LAN interface, channel 5 +PCAN_LANBUS6 = TPCANHandle(0x806) # PCAN-LAN interface, channel 6 +PCAN_LANBUS7 = TPCANHandle(0x807) # PCAN-LAN interface, channel 7 +PCAN_LANBUS8 = TPCANHandle(0x808) # PCAN-LAN interface, channel 8 +PCAN_LANBUS9 = TPCANHandle(0x809) # PCAN-LAN interface, channel 9 +PCAN_LANBUS10 = TPCANHandle(0x80A) # PCAN-LAN interface, channel 10 +PCAN_LANBUS11 = TPCANHandle(0x80B) # PCAN-LAN interface, channel 11 +PCAN_LANBUS12 = TPCANHandle(0x80C) # PCAN-LAN interface, channel 12 +PCAN_LANBUS13 = TPCANHandle(0x80D) # PCAN-LAN interface, channel 13 +PCAN_LANBUS14 = TPCANHandle(0x80E) # PCAN-LAN interface, channel 14 +PCAN_LANBUS15 = TPCANHandle(0x80F) # PCAN-LAN interface, channel 15 +PCAN_LANBUS16 = TPCANHandle(0x810) # PCAN-LAN interface, channel 16 # Represent the PCAN error and status codes -PCAN_ERROR_OK = TPCANStatus(0x00000) # No error -PCAN_ERROR_XMTFULL = TPCANStatus(0x00001) # Transmit buffer in CAN controller is full -PCAN_ERROR_OVERRUN = TPCANStatus(0x00002) # CAN controller was read too late -PCAN_ERROR_BUSLIGHT = TPCANStatus(0x00004) # Bus error: an error counter reached the 'light' limit -PCAN_ERROR_BUSHEAVY = TPCANStatus(0x00008) # Bus error: an error counter reached the 'heavy' limit -PCAN_ERROR_BUSWARNING = TPCANStatus(PCAN_ERROR_BUSHEAVY) # Bus error: an error counter reached the 'warning' limit -PCAN_ERROR_BUSPASSIVE = TPCANStatus(0x40000) # Bus error: the CAN controller is error passive -PCAN_ERROR_BUSOFF = TPCANStatus(0x00010) # Bus error: the CAN controller is in bus-off state -PCAN_ERROR_ANYBUSERR = TPCANStatus(PCAN_ERROR_BUSWARNING | PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY | PCAN_ERROR_BUSOFF | PCAN_ERROR_BUSPASSIVE) # Mask for all bus errors -PCAN_ERROR_QRCVEMPTY = TPCANStatus(0x00020) # Receive queue is empty -PCAN_ERROR_QOVERRUN = TPCANStatus(0x00040) # Receive queue was read too late -PCAN_ERROR_QXMTFULL = TPCANStatus(0x00080) # Transmit queue is full -PCAN_ERROR_REGTEST = TPCANStatus(0x00100) # Test of the CAN controller hardware registers failed (no hardware found) -PCAN_ERROR_NODRIVER = TPCANStatus(0x00200) # Driver not loaded -PCAN_ERROR_HWINUSE = TPCANStatus(0x00400) # Hardware already in use by a Net -PCAN_ERROR_NETINUSE = TPCANStatus(0x00800) # A Client is already connected to the Net -PCAN_ERROR_ILLHW = TPCANStatus(0x01400) # Hardware handle is invalid -PCAN_ERROR_ILLNET = TPCANStatus(0x01800) # Net handle is invalid -PCAN_ERROR_ILLCLIENT = TPCANStatus(0x01C00) # Client handle is invalid -PCAN_ERROR_ILLHANDLE = TPCANStatus(PCAN_ERROR_ILLHW | PCAN_ERROR_ILLNET | PCAN_ERROR_ILLCLIENT) # Mask for all handle errors -PCAN_ERROR_RESOURCE = TPCANStatus(0x02000) # Resource (FIFO, Client, timeout) cannot be created -PCAN_ERROR_ILLPARAMTYPE = TPCANStatus(0x04000) # Invalid parameter -PCAN_ERROR_ILLPARAMVAL = TPCANStatus(0x08000) # Invalid parameter value -PCAN_ERROR_UNKNOWN = TPCANStatus(0x10000) # Unknown error -PCAN_ERROR_ILLDATA = TPCANStatus(0x20000) # Invalid data, function, or action -PCAN_ERROR_CAUTION = TPCANStatus(0x2000000)# An operation was successfully carried out, however, irregularities were registered -PCAN_ERROR_INITIALIZE = TPCANStatus(0x4000000)# Channel is not initialized [Value was changed from 0x40000 to 0x4000000] -PCAN_ERROR_ILLOPERATION = TPCANStatus(0x8000000)# Invalid operation [Value was changed from 0x80000 to 0x8000000] +PCAN_ERROR_OK = TPCANStatus(0x00000) # No error +PCAN_ERROR_XMTFULL = TPCANStatus(0x00001) # Transmit buffer in CAN controller is full +PCAN_ERROR_OVERRUN = TPCANStatus(0x00002) # CAN controller was read too late +PCAN_ERROR_BUSLIGHT = TPCANStatus( + 0x00004 +) # Bus error: an error counter reached the 'light' limit +PCAN_ERROR_BUSHEAVY = TPCANStatus( + 0x00008 +) # Bus error: an error counter reached the 'heavy' limit +PCAN_ERROR_BUSWARNING = TPCANStatus( + PCAN_ERROR_BUSHEAVY +) # Bus error: an error counter reached the 'warning' limit +PCAN_ERROR_BUSPASSIVE = TPCANStatus( + 0x40000 +) # Bus error: the CAN controller is error passive +PCAN_ERROR_BUSOFF = TPCANStatus( + 0x00010 +) # Bus error: the CAN controller is in bus-off state +PCAN_ERROR_ANYBUSERR = TPCANStatus( + PCAN_ERROR_BUSWARNING + | PCAN_ERROR_BUSLIGHT + | PCAN_ERROR_BUSHEAVY + | PCAN_ERROR_BUSOFF + | PCAN_ERROR_BUSPASSIVE +) # Mask for all bus errors +PCAN_ERROR_QRCVEMPTY = TPCANStatus(0x00020) # Receive queue is empty +PCAN_ERROR_QOVERRUN = TPCANStatus(0x00040) # Receive queue was read too late +PCAN_ERROR_QXMTFULL = TPCANStatus(0x00080) # Transmit queue is full +PCAN_ERROR_REGTEST = TPCANStatus( + 0x00100 +) # Test of the CAN controller hardware registers failed (no hardware found) +PCAN_ERROR_NODRIVER = TPCANStatus(0x00200) # Driver not loaded +PCAN_ERROR_HWINUSE = TPCANStatus(0x00400) # Hardware already in use by a Net +PCAN_ERROR_NETINUSE = TPCANStatus(0x00800) # A Client is already connected to the Net +PCAN_ERROR_ILLHW = TPCANStatus(0x01400) # Hardware handle is invalid +PCAN_ERROR_ILLNET = TPCANStatus(0x01800) # Net handle is invalid +PCAN_ERROR_ILLCLIENT = TPCANStatus(0x01C00) # Client handle is invalid +PCAN_ERROR_ILLHANDLE = TPCANStatus( + PCAN_ERROR_ILLHW | PCAN_ERROR_ILLNET | PCAN_ERROR_ILLCLIENT +) # Mask for all handle errors +PCAN_ERROR_RESOURCE = TPCANStatus( + 0x02000 +) # Resource (FIFO, Client, timeout) cannot be created +PCAN_ERROR_ILLPARAMTYPE = TPCANStatus(0x04000) # Invalid parameter +PCAN_ERROR_ILLPARAMVAL = TPCANStatus(0x08000) # Invalid parameter value +PCAN_ERROR_UNKNOWN = TPCANStatus(0x10000) # Unknown error +PCAN_ERROR_ILLDATA = TPCANStatus(0x20000) # Invalid data, function, or action +PCAN_ERROR_CAUTION = TPCANStatus( + 0x2000000 +) # An operation was successfully carried out, however, irregularities were registered +PCAN_ERROR_INITIALIZE = TPCANStatus( + 0x4000000 +) # Channel is not initialized [Value was changed from 0x40000 to 0x4000000] +PCAN_ERROR_ILLOPERATION = TPCANStatus( + 0x8000000 +) # Invalid operation [Value was changed from 0x80000 to 0x8000000] # PCAN devices -PCAN_NONE = TPCANDevice(0x00) # Undefined, unknown or not selected PCAN device value -PCAN_PEAKCAN = TPCANDevice(0x01) # PCAN Non-Plug&Play devices. NOT USED WITHIN PCAN-Basic API -PCAN_ISA = TPCANDevice(0x02) # PCAN-ISA, PCAN-PC/104, and PCAN-PC/104-Plus -PCAN_DNG = TPCANDevice(0x03) # PCAN-Dongle -PCAN_PCI = TPCANDevice(0x04) # PCAN-PCI, PCAN-cPCI, PCAN-miniPCI, and PCAN-PCI Express -PCAN_USB = TPCANDevice(0x05) # PCAN-USB and PCAN-USB Pro -PCAN_PCC = TPCANDevice(0x06) # PCAN-PC Card -PCAN_VIRTUAL = TPCANDevice(0x07) # PCAN Virtual hardware. NOT USED WITHIN PCAN-Basic API -PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices +PCAN_NONE = TPCANDevice(0x00) # Undefined, unknown or not selected PCAN device value +PCAN_PEAKCAN = TPCANDevice( + 0x01 +) # PCAN Non-Plug&Play devices. NOT USED WITHIN PCAN-Basic API +PCAN_ISA = TPCANDevice(0x02) # PCAN-ISA, PCAN-PC/104, and PCAN-PC/104-Plus +PCAN_DNG = TPCANDevice(0x03) # PCAN-Dongle +PCAN_PCI = TPCANDevice(0x04) # PCAN-PCI, PCAN-cPCI, PCAN-miniPCI, and PCAN-PCI Express +PCAN_USB = TPCANDevice(0x05) # PCAN-USB and PCAN-USB Pro +PCAN_PCC = TPCANDevice(0x06) # PCAN-PC Card +PCAN_VIRTUAL = TPCANDevice( + 0x07 +) # PCAN Virtual hardware. NOT USED WITHIN PCAN-Basic API +PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices # PCAN parameters -PCAN_DEVICE_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter -PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 5-Volt power parameter -PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter -PCAN_MESSAGE_FILTER = TPCANParameter(0x04) # PCAN message filter parameter -PCAN_API_VERSION = TPCANParameter(0x05) # PCAN-Basic API version parameter -PCAN_CHANNEL_VERSION = TPCANParameter(0x06) # PCAN device channel version parameter -PCAN_BUSOFF_AUTORESET = TPCANParameter(0x07) # PCAN Reset-On-Busoff parameter -PCAN_LISTEN_ONLY = TPCANParameter(0x08) # PCAN Listen-Only parameter -PCAN_LOG_LOCATION = TPCANParameter(0x09) # Directory path for log files -PCAN_LOG_STATUS = TPCANParameter(0x0A) # Debug-Log activation status -PCAN_LOG_CONFIGURE = TPCANParameter(0x0B) # Configuration of the debugged information (LOG_FUNCTION_***) -PCAN_LOG_TEXT = TPCANParameter(0x0C) # Custom insertion of text into the log file -PCAN_CHANNEL_CONDITION = TPCANParameter(0x0D) # Availability status of a PCAN-Channel -PCAN_HARDWARE_NAME = TPCANParameter(0x0E) # PCAN hardware name parameter -PCAN_RECEIVE_STATUS = TPCANParameter(0x0F) # Message reception status of a PCAN-Channel -PCAN_CONTROLLER_NUMBER = TPCANParameter(0x10) # CAN-Controller number of a PCAN-Channel -PCAN_TRACE_LOCATION = TPCANParameter(0x11) # Directory path for PCAN trace files -PCAN_TRACE_STATUS = TPCANParameter(0x12) # CAN tracing activation status -PCAN_TRACE_SIZE = TPCANParameter(0x13) # Configuration of the maximum file size of a CAN trace -PCAN_TRACE_CONFIGURE = TPCANParameter(0x14) # Configuration of the trace file storing mode (TRACE_FILE_***) -PCAN_CHANNEL_IDENTIFYING = TPCANParameter(0x15) # Physical identification of a USB based PCAN-Channel by blinking its associated LED -PCAN_CHANNEL_FEATURES = TPCANParameter(0x16) # Capabilities of a PCAN device (FEATURE_***) -PCAN_BITRATE_ADAPTING = TPCANParameter(0x17) # Using of an existing bit rate (PCAN-View connected to a channel) -PCAN_BITRATE_INFO = TPCANParameter(0x18) # Configured bit rate as Btr0Btr1 value -PCAN_BITRATE_INFO_FD = TPCANParameter(0x19) # Configured bit rate as TPCANBitrateFD string -PCAN_BUSSPEED_NOMINAL = TPCANParameter(0x1A) # Configured nominal CAN Bus speed as Bits per seconds -PCAN_BUSSPEED_DATA = TPCANParameter(0x1B) # Configured CAN data speed as Bits per seconds -PCAN_IP_ADDRESS = TPCANParameter(0x1C) # Remote address of a LAN channel as string in IPv4 format -PCAN_LAN_SERVICE_STATUS = TPCANParameter(0x1D) # Status of the Virtual PCAN-Gateway Service -PCAN_ALLOW_STATUS_FRAMES = TPCANParameter(0x1E) # Status messages reception status within a PCAN-Channel -PCAN_ALLOW_RTR_FRAMES = TPCANParameter(0x1F) # RTR messages reception status within a PCAN-Channel -PCAN_ALLOW_ERROR_FRAMES = TPCANParameter(0x20) # Error messages reception status within a PCAN-Channel -PCAN_INTERFRAME_DELAY = TPCANParameter(0x21) # Delay, in microseconds, between sending frames -PCAN_ACCEPTANCE_FILTER_11BIT = TPCANParameter(0x22) # Filter over code and mask patterns for 11-Bit messages -PCAN_ACCEPTANCE_FILTER_29BIT = TPCANParameter(0x23) # Filter over code and mask patterns for 29-Bit messages -PCAN_IO_DIGITAL_CONFIGURATION = TPCANParameter(0x24) # Output mode of 32 digital I/O pin of a PCAN-USB Chip. 1: Output-Active 0 : Output Inactive -PCAN_IO_DIGITAL_VALUE = TPCANParameter(0x25) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip -PCAN_IO_DIGITAL_SET = TPCANParameter(0x26) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip - Multiple digital I/O pins to 1 = High -PCAN_IO_DIGITAL_CLEAR = TPCANParameter(0x27) # Clear multiple digital I/O pins to 0 -PCAN_IO_ANALOG_VALUE = TPCANParameter(0x28) # Get value of a single analog input pin +PCAN_DEVICE_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter +PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 5-Volt power parameter +PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter +PCAN_MESSAGE_FILTER = TPCANParameter(0x04) # PCAN message filter parameter +PCAN_API_VERSION = TPCANParameter(0x05) # PCAN-Basic API version parameter +PCAN_CHANNEL_VERSION = TPCANParameter(0x06) # PCAN device channel version parameter +PCAN_BUSOFF_AUTORESET = TPCANParameter(0x07) # PCAN Reset-On-Busoff parameter +PCAN_LISTEN_ONLY = TPCANParameter(0x08) # PCAN Listen-Only parameter +PCAN_LOG_LOCATION = TPCANParameter(0x09) # Directory path for log files +PCAN_LOG_STATUS = TPCANParameter(0x0A) # Debug-Log activation status +PCAN_LOG_CONFIGURE = TPCANParameter( + 0x0B +) # Configuration of the debugged information (LOG_FUNCTION_***) +PCAN_LOG_TEXT = TPCANParameter(0x0C) # Custom insertion of text into the log file +PCAN_CHANNEL_CONDITION = TPCANParameter(0x0D) # Availability status of a PCAN-Channel +PCAN_HARDWARE_NAME = TPCANParameter(0x0E) # PCAN hardware name parameter +PCAN_RECEIVE_STATUS = TPCANParameter(0x0F) # Message reception status of a PCAN-Channel +PCAN_CONTROLLER_NUMBER = TPCANParameter(0x10) # CAN-Controller number of a PCAN-Channel +PCAN_TRACE_LOCATION = TPCANParameter(0x11) # Directory path for PCAN trace files +PCAN_TRACE_STATUS = TPCANParameter(0x12) # CAN tracing activation status +PCAN_TRACE_SIZE = TPCANParameter( + 0x13 +) # Configuration of the maximum file size of a CAN trace +PCAN_TRACE_CONFIGURE = TPCANParameter( + 0x14 +) # Configuration of the trace file storing mode (TRACE_FILE_***) +PCAN_CHANNEL_IDENTIFYING = TPCANParameter( + 0x15 +) # Physical identification of a USB based PCAN-Channel by blinking its associated LED +PCAN_CHANNEL_FEATURES = TPCANParameter( + 0x16 +) # Capabilities of a PCAN device (FEATURE_***) +PCAN_BITRATE_ADAPTING = TPCANParameter( + 0x17 +) # Using of an existing bit rate (PCAN-View connected to a channel) +PCAN_BITRATE_INFO = TPCANParameter(0x18) # Configured bit rate as Btr0Btr1 value +PCAN_BITRATE_INFO_FD = TPCANParameter( + 0x19 +) # Configured bit rate as TPCANBitrateFD string +PCAN_BUSSPEED_NOMINAL = TPCANParameter( + 0x1A +) # Configured nominal CAN Bus speed as Bits per seconds +PCAN_BUSSPEED_DATA = TPCANParameter( + 0x1B +) # Configured CAN data speed as Bits per seconds +PCAN_IP_ADDRESS = TPCANParameter( + 0x1C +) # Remote address of a LAN channel as string in IPv4 format +PCAN_LAN_SERVICE_STATUS = TPCANParameter( + 0x1D +) # Status of the Virtual PCAN-Gateway Service +PCAN_ALLOW_STATUS_FRAMES = TPCANParameter( + 0x1E +) # Status messages reception status within a PCAN-Channel +PCAN_ALLOW_RTR_FRAMES = TPCANParameter( + 0x1F +) # RTR messages reception status within a PCAN-Channel +PCAN_ALLOW_ERROR_FRAMES = TPCANParameter( + 0x20 +) # Error messages reception status within a PCAN-Channel +PCAN_INTERFRAME_DELAY = TPCANParameter( + 0x21 +) # Delay, in microseconds, between sending frames +PCAN_ACCEPTANCE_FILTER_11BIT = TPCANParameter( + 0x22 +) # Filter over code and mask patterns for 11-Bit messages +PCAN_ACCEPTANCE_FILTER_29BIT = TPCANParameter( + 0x23 +) # Filter over code and mask patterns for 29-Bit messages +PCAN_IO_DIGITAL_CONFIGURATION = TPCANParameter( + 0x24 +) # Output mode of 32 digital I/O pin of a PCAN-USB Chip. 1: Output-Active 0 : Output Inactive +PCAN_IO_DIGITAL_VALUE = TPCANParameter( + 0x25 +) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip +PCAN_IO_DIGITAL_SET = TPCANParameter( + 0x26 +) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip - Multiple digital I/O pins to 1 = High +PCAN_IO_DIGITAL_CLEAR = TPCANParameter(0x27) # Clear multiple digital I/O pins to 0 +PCAN_IO_ANALOG_VALUE = TPCANParameter(0x28) # Get value of a single analog input pin # PCAN parameter values -PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) -PCAN_PARAMETER_ON = int(0x01) # The PCAN parameter is set (active) -PCAN_FILTER_CLOSE = int(0x00) # The PCAN filter is closed. No messages will be received -PCAN_FILTER_OPEN = int(0x01) # The PCAN filter is fully opened. All messages will be received -PCAN_FILTER_CUSTOM = int(0x02) # The PCAN filter is custom configured. Only registered messages will be received -PCAN_CHANNEL_UNAVAILABLE = int(0x00) # The PCAN-Channel handle is illegal, or its associated hardware is not available -PCAN_CHANNEL_AVAILABLE = int(0x01) # The PCAN-Channel handle is available to be connected (Plug&Play Hardware: it means furthermore that the hardware is plugged-in) -PCAN_CHANNEL_OCCUPIED = int(0x02) # The PCAN-Channel handle is valid, and is already being used -PCAN_CHANNEL_PCANVIEW = PCAN_CHANNEL_AVAILABLE | PCAN_CHANNEL_OCCUPIED # The PCAN-Channel handle is already being used by a PCAN-View application, but is available to connect - -LOG_FUNCTION_DEFAULT = int(0x00) # Logs system exceptions / errors -LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions -LOG_FUNCTION_PARAMETERS = int(0x02) # Logs the parameters passed to the PCAN-Basic API functions -LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions -LOG_FUNCTION_WRITE = int(0x08) # Logs the CAN messages passed to the CAN_Write function -LOG_FUNCTION_READ = int(0x10) # Logs the CAN messages received within the CAN_Read function -LOG_FUNCTION_ALL = int(0xFFFF)# Logs all possible information within the PCAN-Basic API functions - -TRACE_FILE_SINGLE = int(0x00) # A single file is written until it size reaches PAN_TRACE_SIZE -TRACE_FILE_SEGMENTED = int(0x01) # Traced data is distributed in several files with size PAN_TRACE_SIZE -TRACE_FILE_DATE = int(0x02) # Includes the date into the name of the trace file -TRACE_FILE_TIME = int(0x04) # Includes the start time into the name of the trace file -TRACE_FILE_OVERWRITE = int(0x80) # Causes the overwriting of available traces (same name) - -FEATURE_FD_CAPABLE = int(0x01) # Device supports flexible data-rate (CAN-FD) -FEATURE_DELAY_CAPABLE = int(0x02) # Device supports a delay between sending frames (FPGA based USB devices) -FEATURE_IO_CAPABLE = int(0x04) # Device supports I/O functionality for electronic circuits (USB-Chip devices) - -SERVICE_STATUS_STOPPED = int(0x01) # The service is not running -SERVICE_STATUS_RUNNING = int(0x04) # The service is running +PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) +PCAN_PARAMETER_ON = int(0x01) # The PCAN parameter is set (active) +PCAN_FILTER_CLOSE = int(0x00) # The PCAN filter is closed. No messages will be received +PCAN_FILTER_OPEN = int( + 0x01 +) # The PCAN filter is fully opened. All messages will be received +PCAN_FILTER_CUSTOM = int( + 0x02 +) # The PCAN filter is custom configured. Only registered messages will be received +PCAN_CHANNEL_UNAVAILABLE = int( + 0x00 +) # The PCAN-Channel handle is illegal, or its associated hardware is not available +PCAN_CHANNEL_AVAILABLE = int( + 0x01 +) # The PCAN-Channel handle is available to be connected (Plug&Play Hardware: it means furthermore that the hardware is plugged-in) +PCAN_CHANNEL_OCCUPIED = int( + 0x02 +) # The PCAN-Channel handle is valid, and is already being used +PCAN_CHANNEL_PCANVIEW = ( + PCAN_CHANNEL_AVAILABLE | PCAN_CHANNEL_OCCUPIED +) # The PCAN-Channel handle is already being used by a PCAN-View application, but is available to connect + +LOG_FUNCTION_DEFAULT = int(0x00) # Logs system exceptions / errors +LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions +LOG_FUNCTION_PARAMETERS = int( + 0x02 +) # Logs the parameters passed to the PCAN-Basic API functions +LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions +LOG_FUNCTION_WRITE = int(0x08) # Logs the CAN messages passed to the CAN_Write function +LOG_FUNCTION_READ = int( + 0x10 +) # Logs the CAN messages received within the CAN_Read function +LOG_FUNCTION_ALL = int( + 0xFFFF +) # Logs all possible information within the PCAN-Basic API functions + +TRACE_FILE_SINGLE = int( + 0x00 +) # A single file is written until it size reaches PAN_TRACE_SIZE +TRACE_FILE_SEGMENTED = int( + 0x01 +) # Traced data is distributed in several files with size PAN_TRACE_SIZE +TRACE_FILE_DATE = int(0x02) # Includes the date into the name of the trace file +TRACE_FILE_TIME = int(0x04) # Includes the start time into the name of the trace file +TRACE_FILE_OVERWRITE = int( + 0x80 +) # Causes the overwriting of available traces (same name) + +FEATURE_FD_CAPABLE = int(0x01) # Device supports flexible data-rate (CAN-FD) +FEATURE_DELAY_CAPABLE = int( + 0x02 +) # Device supports a delay between sending frames (FPGA based USB devices) +FEATURE_IO_CAPABLE = int( + 0x04 +) # Device supports I/O functionality for electronic circuits (USB-Chip devices) + +SERVICE_STATUS_STOPPED = int(0x01) # The service is not running +SERVICE_STATUS_RUNNING = int(0x04) # The service is running # PCAN message types -PCAN_MESSAGE_STANDARD = TPCANMessageType(0x00) # The PCAN message is a CAN Standard Frame (11-bit identifier) -PCAN_MESSAGE_RTR = TPCANMessageType(0x01) # The PCAN message is a CAN Remote-Transfer-Request Frame -PCAN_MESSAGE_EXTENDED = TPCANMessageType(0x02) # The PCAN message is a CAN Extended Frame (29-bit identifier) -PCAN_MESSAGE_FD = TPCANMessageType(0x04) # The PCAN message represents a FD frame in terms of CiA Specs -PCAN_MESSAGE_BRS = TPCANMessageType(0x08) # The PCAN message represents a FD bit rate switch (CAN data at a higher bit rate) -PCAN_MESSAGE_ESI = TPCANMessageType(0x10) # The PCAN message represents a FD error state indicator(CAN FD transmitter was error active) -PCAN_MESSAGE_ERRFRAME = TPCANMessageType(0x40) # The PCAN message represents an error frame -PCAN_MESSAGE_STATUS = TPCANMessageType(0x80) # The PCAN message represents a PCAN status message +PCAN_MESSAGE_STANDARD = TPCANMessageType( + 0x00 +) # The PCAN message is a CAN Standard Frame (11-bit identifier) +PCAN_MESSAGE_RTR = TPCANMessageType( + 0x01 +) # The PCAN message is a CAN Remote-Transfer-Request Frame +PCAN_MESSAGE_EXTENDED = TPCANMessageType( + 0x02 +) # The PCAN message is a CAN Extended Frame (29-bit identifier) +PCAN_MESSAGE_FD = TPCANMessageType( + 0x04 +) # The PCAN message represents a FD frame in terms of CiA Specs +PCAN_MESSAGE_BRS = TPCANMessageType( + 0x08 +) # The PCAN message represents a FD bit rate switch (CAN data at a higher bit rate) +PCAN_MESSAGE_ESI = TPCANMessageType( + 0x10 +) # The PCAN message represents a FD error state indicator(CAN FD transmitter was error active) +PCAN_MESSAGE_ERRFRAME = TPCANMessageType( + 0x40 +) # The PCAN message represents an error frame +PCAN_MESSAGE_STATUS = TPCANMessageType( + 0x80 +) # The PCAN message represents a PCAN status message # Frame Type / Initialization Mode -PCAN_MODE_STANDARD = PCAN_MESSAGE_STANDARD -PCAN_MODE_EXTENDED = PCAN_MESSAGE_EXTENDED +PCAN_MODE_STANDARD = PCAN_MESSAGE_STANDARD +PCAN_MODE_EXTENDED = PCAN_MESSAGE_EXTENDED # Baud rate codes = BTR0/BTR1 register values for the CAN controller. # You can define your own Baud rate with the BTROBTR1 register. # Take a look at www.peak-system.com for our free software "BAUDTOOL" # to calculate the BTROBTR1 register for every bit rate and sample point. -PCAN_BAUD_1M = TPCANBaudrate(0x0014) # 1 MBit/s -PCAN_BAUD_800K = TPCANBaudrate(0x0016) # 800 kBit/s -PCAN_BAUD_500K = TPCANBaudrate(0x001C) # 500 kBit/s -PCAN_BAUD_250K = TPCANBaudrate(0x011C) # 250 kBit/s -PCAN_BAUD_125K = TPCANBaudrate(0x031C) # 125 kBit/s -PCAN_BAUD_100K = TPCANBaudrate(0x432F) # 100 kBit/s -PCAN_BAUD_95K = TPCANBaudrate(0xC34E) # 95,238 kBit/s -PCAN_BAUD_83K = TPCANBaudrate(0x852B) # 83,333 kBit/s -PCAN_BAUD_50K = TPCANBaudrate(0x472F) # 50 kBit/s -PCAN_BAUD_47K = TPCANBaudrate(0x1414) # 47,619 kBit/s -PCAN_BAUD_33K = TPCANBaudrate(0x8B2F) # 33,333 kBit/s -PCAN_BAUD_20K = TPCANBaudrate(0x532F) # 20 kBit/s -PCAN_BAUD_10K = TPCANBaudrate(0x672F) # 10 kBit/s -PCAN_BAUD_5K = TPCANBaudrate(0x7F7F) # 5 kBit/s +PCAN_BAUD_1M = TPCANBaudrate(0x0014) # 1 MBit/s +PCAN_BAUD_800K = TPCANBaudrate(0x0016) # 800 kBit/s +PCAN_BAUD_500K = TPCANBaudrate(0x001C) # 500 kBit/s +PCAN_BAUD_250K = TPCANBaudrate(0x011C) # 250 kBit/s +PCAN_BAUD_125K = TPCANBaudrate(0x031C) # 125 kBit/s +PCAN_BAUD_100K = TPCANBaudrate(0x432F) # 100 kBit/s +PCAN_BAUD_95K = TPCANBaudrate(0xC34E) # 95,238 kBit/s +PCAN_BAUD_83K = TPCANBaudrate(0x852B) # 83,333 kBit/s +PCAN_BAUD_50K = TPCANBaudrate(0x472F) # 50 kBit/s +PCAN_BAUD_47K = TPCANBaudrate(0x1414) # 47,619 kBit/s +PCAN_BAUD_33K = TPCANBaudrate(0x8B2F) # 33,333 kBit/s +PCAN_BAUD_20K = TPCANBaudrate(0x532F) # 20 kBit/s +PCAN_BAUD_10K = TPCANBaudrate(0x672F) # 10 kBit/s +PCAN_BAUD_5K = TPCANBaudrate(0x7F7F) # 5 kBit/s # Represents the configuration for a CAN bit rate # Note: @@ -264,90 +380,120 @@ # Example: # f_clock=80000000,nom_brp=10,nom_tseg1=5,nom_tseg2=2,nom_sjw=1,data_brp=4,data_tseg1=7,data_tseg2=2,data_sjw=1 # -PCAN_BR_CLOCK = TPCANBitrateFD(b"f_clock") -PCAN_BR_CLOCK_MHZ = TPCANBitrateFD(b"f_clock_mhz") -PCAN_BR_NOM_BRP = TPCANBitrateFD(b"nom_brp") -PCAN_BR_NOM_TSEG1 = TPCANBitrateFD(b"nom_tseg1") -PCAN_BR_NOM_TSEG2 = TPCANBitrateFD(b"nom_tseg2") -PCAN_BR_NOM_SJW = TPCANBitrateFD(b"nom_sjw") -PCAN_BR_NOM_SAMPLE = TPCANBitrateFD(b"nom_sam") -PCAN_BR_DATA_BRP = TPCANBitrateFD(b"data_brp") -PCAN_BR_DATA_TSEG1 = TPCANBitrateFD(b"data_tseg1") -PCAN_BR_DATA_TSEG2 = TPCANBitrateFD(b"data_tseg2") -PCAN_BR_DATA_SJW = TPCANBitrateFD(b"data_sjw") -PCAN_BR_DATA_SAMPLE = TPCANBitrateFD(b"data_ssp_offset") +PCAN_BR_CLOCK = TPCANBitrateFD(b"f_clock") +PCAN_BR_CLOCK_MHZ = TPCANBitrateFD(b"f_clock_mhz") +PCAN_BR_NOM_BRP = TPCANBitrateFD(b"nom_brp") +PCAN_BR_NOM_TSEG1 = TPCANBitrateFD(b"nom_tseg1") +PCAN_BR_NOM_TSEG2 = TPCANBitrateFD(b"nom_tseg2") +PCAN_BR_NOM_SJW = TPCANBitrateFD(b"nom_sjw") +PCAN_BR_NOM_SAMPLE = TPCANBitrateFD(b"nom_sam") +PCAN_BR_DATA_BRP = TPCANBitrateFD(b"data_brp") +PCAN_BR_DATA_TSEG1 = TPCANBitrateFD(b"data_tseg1") +PCAN_BR_DATA_TSEG2 = TPCANBitrateFD(b"data_tseg2") +PCAN_BR_DATA_SJW = TPCANBitrateFD(b"data_sjw") +PCAN_BR_DATA_SAMPLE = TPCANBitrateFD(b"data_ssp_offset") # Supported No-Plug-And-Play Hardware types -PCAN_TYPE_ISA = TPCANType(0x01) # PCAN-ISA 82C200 -PCAN_TYPE_ISA_SJA = TPCANType(0x09) # PCAN-ISA SJA1000 -PCAN_TYPE_ISA_PHYTEC = TPCANType(0x04) # PHYTEC ISA -PCAN_TYPE_DNG = TPCANType(0x02) # PCAN-Dongle 82C200 -PCAN_TYPE_DNG_EPP = TPCANType(0x03) # PCAN-Dongle EPP 82C200 -PCAN_TYPE_DNG_SJA = TPCANType(0x05) # PCAN-Dongle SJA1000 -PCAN_TYPE_DNG_SJA_EPP = TPCANType(0x06) # PCAN-Dongle EPP SJA1000 +PCAN_TYPE_ISA = TPCANType(0x01) # PCAN-ISA 82C200 +PCAN_TYPE_ISA_SJA = TPCANType(0x09) # PCAN-ISA SJA1000 +PCAN_TYPE_ISA_PHYTEC = TPCANType(0x04) # PHYTEC ISA +PCAN_TYPE_DNG = TPCANType(0x02) # PCAN-Dongle 82C200 +PCAN_TYPE_DNG_EPP = TPCANType(0x03) # PCAN-Dongle EPP 82C200 +PCAN_TYPE_DNG_SJA = TPCANType(0x05) # PCAN-Dongle SJA1000 +PCAN_TYPE_DNG_SJA_EPP = TPCANType(0x06) # PCAN-Dongle EPP SJA1000 -class TPCANMsg (Structure): +class TPCANMsg(Structure): """ Represents a PCAN message """ - _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier - ("MSGTYPE", TPCANMessageType), # Type of the message - ("LEN", c_ubyte), # Data Length Code of the message (0..8) - ("DATA", c_ubyte * 8) ] # Data of the message (DATA[0]..DATA[7]) + _fields_ = [ + ("ID", c_uint), # 11/29-bit message identifier + ("MSGTYPE", TPCANMessageType), # Type of the message + ("LEN", c_ubyte), # Data Length Code of the message (0..8) + ("DATA", c_ubyte * 8), + ] # Data of the message (DATA[0]..DATA[7]) -class TPCANMsgMac (Structure): + +class TPCANMsgMac(Structure): """ Represents a PCAN message """ - _fields_ = [ ("ID", c_ulong), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("MSGTYPE", TPCANMessageType), # Type of the message - ("LEN", c_ubyte), # Data Length Code of the message (0..8) - ("DATA", c_ubyte * 8) ] # Data of the message (DATA[0]..DATA[7]) + + _fields_ = [ + ( + "ID", + c_ulong, + ), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS + ("MSGTYPE", TPCANMessageType), # Type of the message + ("LEN", c_ubyte), # Data Length Code of the message (0..8) + ("DATA", c_ubyte * 8), + ] # Data of the message (DATA[0]..DATA[7]) -class TPCANTimestamp (Structure): +class TPCANTimestamp(Structure): """ Represents a timestamp of a received PCAN message Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow """ - _fields_ = [ ("millis", c_uint), # Base-value: milliseconds: 0.. 2^32-1 - ("millis_overflow", c_ushort), # Roll-arounds of millis - ("micros", c_ushort) ] # Microseconds: 0..999 + _fields_ = [ + ("millis", c_uint), # Base-value: milliseconds: 0.. 2^32-1 + ("millis_overflow", c_ushort), # Roll-arounds of millis + ("micros", c_ushort), + ] # Microseconds: 0..999 -class TPCANTimestampMac (Structure): + +class TPCANTimestampMac(Structure): """ Represents a timestamp of a received PCAN message Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow """ - _fields_ = [ ("millis", c_ulong), # Base-value: milliseconds: 0.. 2^32-1 - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("millis_overflow", c_ushort), # Roll-arounds of millis - ("micros", c_ushort) ] # Microseconds: 0..999 + + _fields_ = [ + ( + "millis", + c_ulong, + ), # Base-value: milliseconds: 0.. 2^32-1 - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS + ("millis_overflow", c_ushort), # Roll-arounds of millis + ("micros", c_ushort), + ] # Microseconds: 0..999 -class TPCANMsgFD (Structure): +class TPCANMsgFD(Structure): """ Represents a PCAN message """ - _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier - ("MSGTYPE", TPCANMessageType), # Type of the message - ("DLC", c_ubyte), # Data Length Code of the message (0..15) - ("DATA", c_ubyte * 64) ] # Data of the message (DATA[0]..DATA[63]) -class TPCANMsgFDMac (Structure): + _fields_ = [ + ("ID", c_uint), # 11/29-bit message identifier + ("MSGTYPE", TPCANMessageType), # Type of the message + ("DLC", c_ubyte), # Data Length Code of the message (0..15) + ("DATA", c_ubyte * 64), + ] # Data of the message (DATA[0]..DATA[63]) + + +class TPCANMsgFDMac(Structure): """ Represents a PCAN message """ - _fields_ = [ ("ID", c_ulong), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("MSGTYPE", TPCANMessageType), # Type of the message - ("DLC", c_ubyte), # Data Length Code of the message (0..15) - ("DATA", c_ubyte * 64) ] # Data of the message (DATA[0]..DATA[63]) -#/////////////////////////////////////////////////////////// + _fields_ = [ + ( + "ID", + c_ulong, + ), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS + ("MSGTYPE", TPCANMessageType), # Type of the message + ("DLC", c_ubyte), # Data Length Code of the message (0..15) + ("DATA", c_ubyte * 64), + ] # Data of the message (DATA[0]..DATA[63]) + + +# /////////////////////////////////////////////////////////// # PCAN-Basic API function declarations -#/////////////////////////////////////////////////////////// +# /////////////////////////////////////////////////////////// + class PCANBasic: """PCAN-Basic API class implementation @@ -355,10 +501,10 @@ class PCANBasic: def __init__(self): # Loads the PCANBasic.dll - if platform.system() == 'Windows': + if platform.system() == "Windows": self.__m_dllBasic = windll.LoadLibrary("PCANBasic") - elif platform.system() == 'Darwin': - self.__m_dllBasic = cdll.LoadLibrary('libPCBUSB.dylib') + elif platform.system() == "Darwin": + self.__m_dllBasic = cdll.LoadLibrary("libPCBUSB.dylib") else: self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so") if self.__m_dllBasic is None: @@ -368,9 +514,10 @@ def Initialize( self, Channel, Btr0Btr1, - HwType = TPCANType(0), - IOPort = c_uint(0), - Interrupt = c_ushort(0)): + HwType=TPCANType(0), + IOPort=c_uint(0), + Interrupt=c_ushort(0), + ): """ Initializes a PCAN Channel @@ -386,16 +533,15 @@ def Initialize( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_Initialize(Channel,Btr0Btr1,HwType,IOPort,Interrupt) + res = self.__m_dllBasic.CAN_Initialize( + Channel, Btr0Btr1, HwType, IOPort, Interrupt + ) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.Initialize") raise - def InitializeFD( - self, - Channel, - BitrateFD): + def InitializeFD(self, Channel, BitrateFD): """ Initializes a FD capable PCAN Channel @@ -419,15 +565,13 @@ def InitializeFD( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_InitializeFD(Channel,BitrateFD) + res = self.__m_dllBasic.CAN_InitializeFD(Channel, BitrateFD) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.InitializeFD") raise - def Uninitialize( - self, - Channel): + def Uninitialize(self, Channel): """ Uninitializes one or all PCAN Channels initialized by CAN_Initialize @@ -448,9 +592,7 @@ def Uninitialize( logger.error("Exception on PCANBasic.Uninitialize") raise - def Reset( - self, - Channel): + def Reset(self, Channel): """ Resets the receive and transmit queues of the PCAN Channel @@ -471,9 +613,7 @@ def Reset( logger.error("Exception on PCANBasic.Reset") raise - def GetStatus( - self, - Channel): + def GetStatus(self, Channel): """ Gets the current status of a PCAN Channel @@ -491,9 +631,7 @@ def GetStatus( logger.error("Exception on PCANBasic.GetStatus") raise - def Read( - self, - Channel): + def Read(self, Channel): """ Reads a CAN message from the receive queue of a PCAN Channel @@ -513,21 +651,19 @@ def Read( A touple with three values """ try: - if platform.system() == 'Darwin': + if platform.system() == "Darwin": msg = TPCANMsgMac() timestamp = TPCANTimestampMac() else: msg = TPCANMsg() timestamp = TPCANTimestamp() - res = self.__m_dllBasic.CAN_Read(Channel,byref(msg),byref(timestamp)) - return TPCANStatus(res),msg,timestamp + res = self.__m_dllBasic.CAN_Read(Channel, byref(msg), byref(timestamp)) + return TPCANStatus(res), msg, timestamp except: logger.error("Exception on PCANBasic.Read") raise - def ReadFD( - self, - Channel): + def ReadFD(self, Channel): """ Reads a CAN message from the receive queue of a FD capable PCAN Channel @@ -547,21 +683,18 @@ def ReadFD( A touple with three values """ try: - if platform.system() == 'Darwin': + if platform.system() == "Darwin": msg = TPCANMsgFDMac() else: msg = TPCANMsgFD() timestamp = TPCANTimestampFD() - res = self.__m_dllBasic.CAN_ReadFD(Channel,byref(msg),byref(timestamp)) - return TPCANStatus(res),msg,timestamp + res = self.__m_dllBasic.CAN_ReadFD(Channel, byref(msg), byref(timestamp)) + return TPCANStatus(res), msg, timestamp except: logger.error("Exception on PCANBasic.ReadFD") raise - def Write( - self, - Channel, - MessageBuffer): + def Write(self, Channel, MessageBuffer): """ Transmits a CAN message @@ -574,16 +707,13 @@ def Write( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_Write(Channel,byref(MessageBuffer)) + res = self.__m_dllBasic.CAN_Write(Channel, byref(MessageBuffer)) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.Write") raise - def WriteFD( - self, - Channel, - MessageBuffer): + def WriteFD(self, Channel, MessageBuffer): """ Transmits a CAN message over a FD capable PCAN Channel @@ -596,18 +726,13 @@ def WriteFD( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_WriteFD(Channel,byref(MessageBuffer)) + res = self.__m_dllBasic.CAN_WriteFD(Channel, byref(MessageBuffer)) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.WriteFD") raise - def FilterMessages( - self, - Channel, - FromID, - ToID, - Mode): + def FilterMessages(self, Channel, FromID, ToID, Mode): """ Configures the reception filter @@ -627,16 +752,13 @@ def FilterMessages( A TPCANStatus error code """ try: - res = self.__m_dllBasic.CAN_FilterMessages(Channel,FromID,ToID,Mode) + res = self.__m_dllBasic.CAN_FilterMessages(Channel, FromID, ToID, Mode) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.FilterMessages") raise - def GetValue( - self, - Channel, - Parameter): + def GetValue(self, Channel, Parameter): """ Retrieves a PCAN Channel value @@ -658,24 +780,28 @@ def GetValue( A touple with 2 values """ try: - if Parameter in {PCAN_API_VERSION, PCAN_HARDWARE_NAME, PCAN_CHANNEL_VERSION, - PCAN_LOG_LOCATION, PCAN_TRACE_LOCATION, PCAN_BITRATE_INFO_FD, - PCAN_IP_ADDRESS}: + if Parameter in { + PCAN_API_VERSION, + PCAN_HARDWARE_NAME, + PCAN_CHANNEL_VERSION, + PCAN_LOG_LOCATION, + PCAN_TRACE_LOCATION, + PCAN_BITRATE_INFO_FD, + PCAN_IP_ADDRESS, + }: mybuffer = create_string_buffer(256) else: mybuffer = c_int(0) - res = self.__m_dllBasic.CAN_GetValue(Channel, Parameter, byref(mybuffer), sizeof(mybuffer)) + res = self.__m_dllBasic.CAN_GetValue( + Channel, Parameter, byref(mybuffer), sizeof(mybuffer) + ) return TPCANStatus(res), mybuffer.value except: logger.error("Exception on PCANBasic.GetValue") raise - def SetValue( - self, - Channel, - Parameter, - Buffer): + def SetValue(self, Channel, Parameter, Buffer): """ Returns a descriptive text of a given TPCANStatus error @@ -702,16 +828,15 @@ def SetValue( mybuffer = c_int(0) mybuffer.value = Buffer - res = self.__m_dllBasic.CAN_SetValue(Channel, Parameter, byref(mybuffer), sizeof(mybuffer)) + res = self.__m_dllBasic.CAN_SetValue( + Channel, Parameter, byref(mybuffer), sizeof(mybuffer) + ) return TPCANStatus(res) except: logger.error("Exception on PCANBasic.SetValue") raise - def GetErrorText( - self, - Error, - Language = 0): + def GetErrorText(self, Error, Language=0): """ Configures or sets a PCAN Channel value @@ -735,8 +860,8 @@ def GetErrorText( """ try: mybuffer = create_string_buffer(256) - res = self.__m_dllBasic.CAN_GetErrorText(Error,Language,byref(mybuffer)) - return TPCANStatus(res),mybuffer.value + res = self.__m_dllBasic.CAN_GetErrorText(Error, Language, byref(mybuffer)) + return TPCANStatus(res), mybuffer.value except: logger.error("Exception on PCANBasic.GetErrorText") raise diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index a7c8bd9ec..80b6054d0 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -16,7 +16,10 @@ # use the "uptime" library if available import uptime import datetime - boottimeEpoch = (uptime.boottime() - datetime.datetime.utcfromtimestamp(0)).total_seconds() + + boottimeEpoch = ( + uptime.boottime() - datetime.datetime.utcfromtimestamp(0) + ).total_seconds() except ImportError: boottimeEpoch = 0 @@ -24,44 +27,62 @@ # Try builtin Python 3 Windows API from _overlapped import CreateEvent from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE + HAS_EVENTS = True except ImportError: try: # Try pywin32 package from win32event import CreateEvent from win32event import WaitForSingleObject, WAIT_OBJECT_0, INFINITE + HAS_EVENTS = True except ImportError: # Use polling instead HAS_EVENTS = False # Set up logging -log = logging.getLogger('can.pcan') - - -pcan_bitrate_objs = {1000000 : PCAN_BAUD_1M, - 800000 : PCAN_BAUD_800K, - 500000 : PCAN_BAUD_500K, - 250000 : PCAN_BAUD_250K, - 125000 : PCAN_BAUD_125K, - 100000 : PCAN_BAUD_100K, - 95000 : PCAN_BAUD_95K, - 83000 : PCAN_BAUD_83K, - 50000 : PCAN_BAUD_50K, - 47000 : PCAN_BAUD_47K, - 33000 : PCAN_BAUD_33K, - 20000 : PCAN_BAUD_20K, - 10000 : PCAN_BAUD_10K, - 5000 : PCAN_BAUD_5K} - - -pcan_fd_parameter_list = ['nom_brp', 'nom_tseg1', 'nom_tseg2', 'nom_sjw', 'data_brp', 'data_tseg1', 'data_tseg2', 'data_sjw'] +log = logging.getLogger("can.pcan") + + +pcan_bitrate_objs = { + 1000000: PCAN_BAUD_1M, + 800000: PCAN_BAUD_800K, + 500000: PCAN_BAUD_500K, + 250000: PCAN_BAUD_250K, + 125000: PCAN_BAUD_125K, + 100000: PCAN_BAUD_100K, + 95000: PCAN_BAUD_95K, + 83000: PCAN_BAUD_83K, + 50000: PCAN_BAUD_50K, + 47000: PCAN_BAUD_47K, + 33000: PCAN_BAUD_33K, + 20000: PCAN_BAUD_20K, + 10000: PCAN_BAUD_10K, + 5000: PCAN_BAUD_5K, +} + + +pcan_fd_parameter_list = [ + "nom_brp", + "nom_tseg1", + "nom_tseg2", + "nom_sjw", + "data_brp", + "data_tseg1", + "data_tseg2", + "data_sjw", +] class PcanBus(BusABC): - - def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000, *args, - **kwargs): + def __init__( + self, + channel="PCAN_USBBUS1", + state=BusState.ACTIVE, + bitrate=500000, + *args, + **kwargs + ): """A PCAN USB interface to CAN. On top of the usual :class:`~can.Bus` methods provided, @@ -152,7 +173,7 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 """ self.channel_info = channel - self.fd = kwargs.get('fd', False) + self.fd = kwargs.get("fd", False) pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) hwtype = PCAN_TYPE_ISA @@ -167,22 +188,28 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 else: raise ArgumentError("BusState must be Active or Passive") - if self.fd: - f_clock_val = kwargs.get('f_clock', None) + f_clock_val = kwargs.get("f_clock", None) if f_clock_val is None: - f_clock = "{}={}".format('f_clock_mhz', kwargs.get('f_clock_mhz', None)) + f_clock = "{}={}".format("f_clock_mhz", kwargs.get("f_clock_mhz", None)) else: - f_clock = "{}={}".format('f_clock', kwargs.get('f_clock', None)) - - fd_parameters_values = [f_clock] + ["{}={}".format(key, kwargs.get(key, None)) for key in pcan_fd_parameter_list if kwargs.get(key, None) is not None] + f_clock = "{}={}".format("f_clock", kwargs.get("f_clock", None)) - self.fd_bitrate = ' ,'.join(fd_parameters_values).encode("ascii") + fd_parameters_values = [f_clock] + [ + "{}={}".format(key, kwargs.get(key, None)) + for key in pcan_fd_parameter_list + if kwargs.get(key, None) is not None + ] + self.fd_bitrate = " ,".join(fd_parameters_values).encode("ascii") - result = self.m_objPCANBasic.InitializeFD(self.m_PcanHandle, self.fd_bitrate) + result = self.m_objPCANBasic.InitializeFD( + self.m_PcanHandle, self.fd_bitrate + ) else: - result = self.m_objPCANBasic.Initialize(self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt) + result = self.m_objPCANBasic.Initialize( + self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt + ) if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) @@ -190,7 +217,8 @@ def __init__(self, channel='PCAN_USBBUS1', state=BusState.ACTIVE, bitrate=500000 if HAS_EVENTS: self._recv_event = CreateEvent(None, 0, 0, None) result = self.m_objPCANBasic.SetValue( - self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event) + self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event + ) if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) @@ -212,7 +240,7 @@ def bits(n): """ while n: # Create a mask to mask the lowest set bit in n - mask = (~n + 1) + mask = ~n + 1 masked_value = n & mask yield masked_value # Toggle the lowest set bit @@ -225,15 +253,17 @@ def bits(n): for b in bits(error): stsReturn = self.m_objPCANBasic.GetErrorText(b, 0) if stsReturn[0] != PCAN_ERROR_OK: - text = "An error occurred. Error-code's text ({0:X}h) couldn't be retrieved".format(error) + text = "An error occurred. Error-code's text ({0:X}h) couldn't be retrieved".format( + error + ) else: - text = stsReturn[1].decode('utf-8', errors='replace') + text = stsReturn[1].decode("utf-8", errors="replace") strings.append(text) - complete_text = '\n'.join(strings) + complete_text = "\n".join(strings) else: - complete_text = stsReturn[1].decode('utf-8', errors='replace') + complete_text = stsReturn[1].decode("utf-8", errors="replace") return complete_text @@ -269,7 +299,7 @@ def _recv_internal(self, timeout): # Calculate max time end_time = time.perf_counter() + timeout - #log.debug("Trying to read a msg") + # log.debug("Trying to read a msg") result = None while result is None: @@ -297,39 +327,60 @@ def _recv_internal(self, timeout): theMsg = result[1] itsTimeStamp = result[2] - #log.debug("Received a message") + # log.debug("Received a message") - is_extended_id = (theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value) == PCAN_MESSAGE_EXTENDED.value - is_remote_frame = (theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value) == PCAN_MESSAGE_RTR.value + is_extended_id = ( + theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value + ) == PCAN_MESSAGE_EXTENDED.value + is_remote_frame = ( + theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value + ) == PCAN_MESSAGE_RTR.value is_fd = (theMsg.MSGTYPE & PCAN_MESSAGE_FD.value) == PCAN_MESSAGE_FD.value - bitrate_switch = (theMsg.MSGTYPE & PCAN_MESSAGE_BRS.value) == PCAN_MESSAGE_BRS.value - error_state_indicator = (theMsg.MSGTYPE & PCAN_MESSAGE_ESI.value) == PCAN_MESSAGE_ESI.value - is_error_frame = (theMsg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value) == PCAN_MESSAGE_ERRFRAME.value - + bitrate_switch = ( + theMsg.MSGTYPE & PCAN_MESSAGE_BRS.value + ) == PCAN_MESSAGE_BRS.value + error_state_indicator = ( + theMsg.MSGTYPE & PCAN_MESSAGE_ESI.value + ) == PCAN_MESSAGE_ESI.value + is_error_frame = ( + theMsg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value + ) == PCAN_MESSAGE_ERRFRAME.value if self.fd: dlc = dlc2len(theMsg.DLC) timestamp = boottimeEpoch + (itsTimeStamp.value / (1000.0 * 1000.0)) else: dlc = theMsg.LEN - timestamp = boottimeEpoch + ((itsTimeStamp.micros + 1000 * itsTimeStamp.millis + 0x100000000 * 1000 * itsTimeStamp.millis_overflow) / (1000.0 * 1000.0)) - - - rx_msg = Message(timestamp=timestamp, - arbitration_id=theMsg.ID, - is_extended_id=is_extended_id, - is_remote_frame=is_remote_frame, - is_error_frame=is_error_frame, - dlc=dlc, - data=theMsg.DATA[:dlc], - is_fd=is_fd, - bitrate_switch=bitrate_switch, - error_state_indicator=error_state_indicator) + timestamp = boottimeEpoch + ( + ( + itsTimeStamp.micros + + 1000 * itsTimeStamp.millis + + 0x100000000 * 1000 * itsTimeStamp.millis_overflow + ) + / (1000.0 * 1000.0) + ) + + rx_msg = Message( + timestamp=timestamp, + arbitration_id=theMsg.ID, + is_extended_id=is_extended_id, + is_remote_frame=is_remote_frame, + is_error_frame=is_error_frame, + dlc=dlc, + data=theMsg.DATA[:dlc], + is_fd=is_fd, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator, + ) return rx_msg, False def send(self, msg, timeout=None): - msgType = PCAN_MESSAGE_EXTENDED.value if msg.is_extended_id else PCAN_MESSAGE_STANDARD.value + msgType = ( + PCAN_MESSAGE_EXTENDED.value + if msg.is_extended_id + else PCAN_MESSAGE_STANDARD.value + ) if msg.is_remote_frame: msgType |= PCAN_MESSAGE_RTR.value if msg.is_error_frame: @@ -343,7 +394,7 @@ def send(self, msg, timeout=None): if self.fd: # create a TPCANMsg message structure - if platform.system() == 'Darwin': + if platform.system() == "Darwin": CANMsg = TPCANMsgFDMac() else: CANMsg = TPCANMsgFD() @@ -364,7 +415,7 @@ def send(self, msg, timeout=None): else: # create a TPCANMsg message structure - if platform.system() == 'Darwin': + if platform.system() == "Darwin": CANMsg = TPCANMsgMac() else: CANMsg = TPCANMsg() @@ -395,7 +446,9 @@ def flash(self, flash): Turn on or off flashing of the device's LED for physical identification purposes. """ - self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_CHANNEL_IDENTIFYING, bool(flash)) + self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_CHANNEL_IDENTIFYING, bool(flash) + ) def shutdown(self): super().shutdown() @@ -408,16 +461,20 @@ def state(self): @state.setter def state(self, new_state): # declare here, which is called by __init__() - self._state = new_state # pylint: disable=attribute-defined-outside-init + self._state = new_state # pylint: disable=attribute-defined-outside-init if new_state is BusState.ACTIVE: - self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_OFF) + self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_OFF + ) elif new_state is BusState.PASSIVE: # When this mode is set, the CAN controller does not take part on active events (eg. transmit CAN messages) # but stays in a passive mode (CAN monitor), in which it can analyse the traffic on the CAN bus used by a # PCAN channel. See also the Philips Data Sheet "SJA1000 Stand-alone CAN controller". - self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_ON) + self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_ON + ) class PcanError(CanError): diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 6e1327a15..ba397f7bf 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -12,13 +12,15 @@ from can import BusABC, Message -logger = logging.getLogger('can.serial') +logger = logging.getLogger("can.serial") try: import serial except ImportError: - logger.warning("You won't be able to use the serial can backend without " - "the serial module installed!") + logger.warning( + "You won't be able to use the serial can backend without " + "the serial module installed!" + ) serial = None @@ -31,7 +33,9 @@ class SerialBus(BusABC): """ - def __init__(self, channel, baudrate=115200, timeout=0.1, rtscts=False, *args, **kwargs): + def __init__( + self, channel, baudrate=115200, timeout=0.1, rtscts=False, *args, **kwargs + ): """ :param str channel: The serial device to open. For example "/dev/ttyS1" or @@ -55,7 +59,8 @@ def __init__(self, channel, baudrate=115200, timeout=0.1, rtscts=False, *args, * self.channel_info = "Serial interface: " + channel self.ser = serial.serial_for_url( - channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts) + channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts + ) super().__init__(channel=channel, *args, **kwargs) @@ -84,13 +89,13 @@ def send(self, msg, timeout=None): """ try: - timestamp = struct.pack(' # @@ -152,7 +156,7 @@ def build_can_frame(msg): if msg.error_state_indicator: flags |= CANFD_ESI max_len = 64 if msg.is_fd else 8 - data = bytes(msg.data).ljust(max_len, b'\x00') + data = bytes(msg.data).ljust(max_len, b"\x00") return CAN_FRAME_HEADER_STRUCT.pack(can_id, msg.dlc, flags) + data @@ -186,8 +190,9 @@ def build_bcm_tx_delete_header(can_id, flags): return build_bcm_header(opcode, flags, 0, 0, 0, 0, 0, can_id, 1) -def build_bcm_transmit_header(can_id, count, initial_period, subsequent_period, - msg_flags): +def build_bcm_transmit_header( + can_id, count, initial_period, subsequent_period, msg_flags +): opcode = CAN_BCM_TX_SETUP flags = msg_flags | SETTIMER | STARTTIMER @@ -206,7 +211,17 @@ def split_time(value): ival2_seconds, ival2_usec = split_time(subsequent_period) nframes = 1 - return build_bcm_header(opcode, flags, count, ival1_seconds, ival1_usec, ival2_seconds, ival2_usec, can_id, nframes) + return build_bcm_header( + opcode, + flags, + count, + ival1_seconds, + ival1_usec, + ival2_seconds, + ival2_usec, + can_id, + nframes, + ) def build_bcm_update_header(can_id, msg_flags): @@ -218,7 +233,7 @@ def dissect_can_frame(frame): if len(frame) != CANFD_MTU: # Flags not valid in non-FD frames flags = 0 - return can_id, can_dlc, flags, frame[8:8+can_dlc] + return can_id, can_dlc, flags, frame[8 : 8 + can_dlc] def create_bcm_socket(channel): @@ -235,10 +250,14 @@ def send_bcm(bcm_socket, data): try: return bcm_socket.send(data) except OSError as e: - base = "Couldn't send CAN BCM frame. OS Error {}: {}\n".format(e.errno, e.strerror) + base = "Couldn't send CAN BCM frame. OS Error {}: {}\n".format( + e.errno, e.strerror + ) if e.errno == errno.EINVAL: - raise can.CanError(base + "You are probably referring to a non-existing frame.") + raise can.CanError( + base + "You are probably referring to a non-existing frame." + ) elif e.errno == errno.ENETDOWN: raise can.CanError(base + "The CAN interface appears to be down.") @@ -265,8 +284,9 @@ def _add_flags_to_can_id(message): return can_id -class CyclicSendTask(LimitedDurationCyclicSendTaskABC, - ModifiableCyclicTaskABC, RestartableCyclicTaskABC): +class CyclicSendTask( + LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC +): """ A socketcan cyclic send task supports: @@ -302,8 +322,9 @@ def _tx_setup(self, message): count = 0 ival1 = 0 ival2 = self.period - header = build_bcm_transmit_header(self.can_id_with_flags, count, ival1, - ival2, self.flags) + header = build_bcm_transmit_header( + self.can_id_with_flags, count, ival1, ival2, self.flags + ) frame = build_can_frame(message) log.debug("Sending BCM command") send_bcm(self.bcm_socket, header + frame) @@ -326,7 +347,9 @@ def modify_data(self, message): Note the Message must have the same :attr:`~can.Message.arbitration_id` like the first message. """ - assert message.arbitration_id == self.can_id, "You cannot modify the can identifier" + assert ( + message.arbitration_id == self.can_id + ), "You cannot modify the can identifier" self.message = message header = build_bcm_update_header(self.can_id_with_flags, self.flags) frame = build_can_frame(message) @@ -348,11 +371,8 @@ def __init__(self, channel, message, count, initial_period, subsequent_period): # Create a low level packed frame to pass to the kernel frame = build_can_frame(message) header = build_bcm_transmit_header( - self.can_id_with_flags, - count, - initial_period, - subsequent_period, - self.flags) + self.can_id_with_flags, count, initial_period, subsequent_period, self.flags + ) log.info("Sending BCM TX_SETUP command") send_bcm(self.bcm_socket, header + frame) @@ -364,12 +384,12 @@ def create_socket(): """ sock = socket.socket(PF_CAN, socket.SOCK_RAW, CAN_RAW) - log.info('Created a socket') + log.info("Created a socket") return sock -def bind_socket(sock, channel='can0'): +def bind_socket(sock, channel="can0"): """ Binds the given socket to the given interface. @@ -378,9 +398,9 @@ def bind_socket(sock, channel='can0'): :raises OSError: If the specified interface isn't found. """ - log.debug('Binding socket to channel=%s', channel) + log.debug("Binding socket to channel=%s", channel) sock.bind((channel,)) - log.debug('Bound socket.') + log.debug("Bound socket.") def capture_message(sock, get_channel=False): @@ -406,7 +426,7 @@ def capture_message(sock, get_channel=False): raise can.CanError("Error receiving: %s" % exc) can_id, can_dlc, flags, data = dissect_can_frame(cf) - #log.debug('Received: can_id=%x, can_dlc=%x, data=%s', can_id, can_dlc, data) + # log.debug('Received: can_id=%x, can_dlc=%x, data=%s', can_id, can_dlc, data) # Fetching the timestamp binary_structure = "@LL" @@ -428,26 +448,28 @@ def capture_message(sock, get_channel=False): error_state_indicator = bool(flags & CANFD_ESI) if is_extended_frame_format: - #log.debug("CAN: Extended") + # log.debug("CAN: Extended") # TODO does this depend on SFF or EFF? arbitration_id = can_id & 0x1FFFFFFF else: - #log.debug("CAN: Standard") + # log.debug("CAN: Standard") arbitration_id = can_id & 0x000007FF - msg = Message(timestamp=timestamp, - channel=channel, - arbitration_id=arbitration_id, - is_extended_id=is_extended_frame_format, - is_remote_frame=is_remote_transmission_request, - is_error_frame=is_error_frame, - is_fd=is_fd, - bitrate_switch=bitrate_switch, - error_state_indicator=error_state_indicator, - dlc=can_dlc, - data=data) + msg = Message( + timestamp=timestamp, + channel=channel, + arbitration_id=arbitration_id, + is_extended_id=is_extended_frame_format, + is_remote_frame=is_remote_transmission_request, + is_error_frame=is_error_frame, + is_fd=is_fd, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator, + dlc=can_dlc, + data=data, + ) - #log_rx.debug('Received: %s', msg) + # log_rx.debug('Received: %s', msg) return msg @@ -480,25 +502,21 @@ def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): # set the receive_own_messages parameter try: - self.socket.setsockopt(SOL_CAN_RAW, - CAN_RAW_RECV_OWN_MSGS, - 1 if receive_own_messages else 0) + self.socket.setsockopt( + SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 1 if receive_own_messages else 0 + ) except socket.error as e: log.error("Could not receive own messages (%s)", e) if fd: # TODO handle errors - self.socket.setsockopt(SOL_CAN_RAW, - CAN_RAW_FD_FRAMES, - 1) + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1) # Enable error frames - self.socket.setsockopt(SOL_CAN_RAW, - CAN_RAW_ERR_FILTER, - 0x1FFFFFFF) + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF) bind_socket(self.socket, channel) - kwargs.update({'receive_own_messages': receive_own_messages, 'fd': fd}) + kwargs.update({"receive_own_messages": receive_own_messages, "fd": fd}) super().__init__(channel=channel, **kwargs) def shutdown(self): @@ -522,7 +540,7 @@ def _recv_internal(self, timeout): # something bad happened (e.g. the interface went down) raise can.CanError("Failed to receive: %s" % exc) - if ready_receive_sockets: # not empty or True + if ready_receive_sockets: # not empty or True get_channel = self.channel == "" msg = capture_message(self.socket, get_channel) if not msg.channel and self.channel: @@ -574,7 +592,7 @@ def _send_once(self, data, channel=None): try: if self.channel == "" and channel: # Message must be addressed to a specific channel - sent = self.socket.sendto(data, (channel, )) + sent = self.socket.sendto(data, (channel,)) else: sent = self.socket.send(data) except socket.error as exc: @@ -618,14 +636,15 @@ def _get_bcm_socket(self, channel): def _apply_filters(self, filters): try: - self.socket.setsockopt(SOL_CAN_RAW, - CAN_RAW_FILTER, - pack_filters(filters)) + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters)) except socket.error as err: # fall back to "software filtering" (= not in kernel) self._is_filtered = False # TODO Is this serious enough to raise a CanError exception? - log.error('Setting filters failed; falling back to software filtering (not in kernel): %s', err) + log.error( + "Setting filters failed; falling back to software filtering (not in kernel): %s", + err, + ) else: self._is_filtered = True @@ -634,8 +653,10 @@ def fileno(self): @staticmethod def _detect_available_configs(): - return [{'interface': 'socketcan', 'channel': channel} - for channel in find_available_interfaces()] + return [ + {"interface": "socketcan", "channel": channel} + for channel in find_available_interfaces() + ] if __name__ == "__main__": @@ -653,7 +674,7 @@ def _detect_available_configs(): def receiver(event): receiver_socket = create_socket() - bind_socket(receiver_socket, 'vcan0') + bind_socket(receiver_socket, "vcan0") print("Receiver is waiting for a message...") event.set() print(f"Receiver got: {capture_message(receiver_socket)}") @@ -661,12 +682,13 @@ def receiver(event): def sender(event): event.wait() sender_socket = create_socket() - bind_socket(sender_socket, 'vcan0') - msg = Message(arbitration_id=0x01, data=b'\x01\x02\x03') + bind_socket(sender_socket, "vcan0") + msg = Message(arbitration_id=0x01, data=b"\x01\x02\x03") sender_socket.send(build_can_frame(msg)) print("Sender sent a message.") import threading + e = threading.Event() threading.Thread(target=receiver, args=(e,)).start() threading.Thread(target=sender, args=(e,)).start() diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index f2c1879c5..5733443a1 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -15,23 +15,21 @@ log = logging.getLogger(__name__) + def pack_filters(can_filters=None): if can_filters is None: # Pass all messages - can_filters = [{ - 'can_id': 0, - 'can_mask': 0 - }] + can_filters = [{"can_id": 0, "can_mask": 0}] can_filter_fmt = "={}I".format(2 * len(can_filters)) filter_data = [] for can_filter in can_filters: - can_id = can_filter['can_id'] - can_mask = can_filter['can_mask'] - if 'extended' in can_filter: + can_id = can_filter["can_id"] + can_mask = can_filter["can_mask"] + if "extended" in can_filter: # Match on either 11-bit OR 29-bit messages instead of both can_mask |= CAN_EFF_FLAG - if can_filter['extended']: + if can_filter["extended"]: can_id |= CAN_EFF_FLAG filter_data.append(can_id) filter_data.append(can_mask) @@ -41,6 +39,7 @@ def pack_filters(can_filters=None): _PATTERN_CAN_INTERFACE = re.compile(r"v?can\d+") + def find_available_interfaces(): """Returns the names of all open can/vcan interfaces using the ``ip link list`` command. If the lookup fails, an error @@ -54,12 +53,12 @@ def find_available_interfaces(): command = ["ip", "-o", "link", "list", "up"] output = subprocess.check_output(command, universal_newlines=True) - except Exception as e: # subprocess.CalledProcessError was too specific + except Exception as e: # subprocess.CalledProcessError was too specific log.error("failed to fetch opened can devices: %s", e) return [] else: - #log.debug("find_available_interfaces(): output=\n%s", output) + # log.debug("find_available_interfaces(): output=\n%s", output) # output contains some lines like "1: vcan42: ..." # extract the "vcan42" of each line interface_names = [line.split(": ", 3)[1] for line in output.splitlines()] diff --git a/can/interfaces/systec/constants.py b/can/interfaces/systec/constants.py index 64122dac9..648ae2edc 100644 --- a/can/interfaces/systec/constants.py +++ b/can/interfaces/systec/constants.py @@ -364,6 +364,7 @@ class OutputControl(BYTE): These values are only important for GW-001 and GW-002. They does not have an effect on systec USB-CANmoduls. """ + #: default OCR value for the standard USB-CANmodul GW-001/GW-002 OCR_DEFAULT = 0x1A #: OCR value for RS485 interface and galvanic isolation @@ -433,42 +434,45 @@ class ResetFlags(DWORD): RESET_FIRMWARE = 0xFFFFFFFF #: no reset of all message counters - RESET_NO_COUNTER_ALL = (RESET_NO_TXCOUNTER | RESET_NO_RXCOUNTER) + RESET_NO_COUNTER_ALL = RESET_NO_TXCOUNTER | RESET_NO_RXCOUNTER #: no reset of transmit message buffers at communication level (firmware, kernel and library) - RESET_NO_TXBUFFER_COMM = (RESET_NO_TXBUFFER_DLL | 0x40 | RESET_NO_TXBUFFER_FW) + RESET_NO_TXBUFFER_COMM = RESET_NO_TXBUFFER_DLL | 0x40 | RESET_NO_TXBUFFER_FW #: no reset of receive message buffers at communication level (firmware, kernel and library) - RESET_NO_RXBUFFER_COMM = (RESET_NO_RXBUFFER_DLL | RESET_NO_RXBUFFER_SYS | RESET_NO_RXBUFFER_FW) + RESET_NO_RXBUFFER_COMM = ( + RESET_NO_RXBUFFER_DLL | RESET_NO_RXBUFFER_SYS | RESET_NO_RXBUFFER_FW + ) #: no reset of all transmit message buffers - RESET_NO_TXBUFFER_ALL = (RESET_NO_TXBUFFER_CH | RESET_NO_TXBUFFER_COMM) + RESET_NO_TXBUFFER_ALL = RESET_NO_TXBUFFER_CH | RESET_NO_TXBUFFER_COMM #: no reset of all receive message buffers - RESET_NO_RXBUFFER_ALL = (RESET_NO_RXBUFFER_CH | RESET_NO_RXBUFFER_COMM) + RESET_NO_RXBUFFER_ALL = RESET_NO_RXBUFFER_CH | RESET_NO_RXBUFFER_COMM #: no reset of all message buffers at communication level (firmware, kernel and library) - RESET_NO_BUFFER_COMM = (RESET_NO_TXBUFFER_COMM | RESET_NO_RXBUFFER_COMM) + RESET_NO_BUFFER_COMM = RESET_NO_TXBUFFER_COMM | RESET_NO_RXBUFFER_COMM #: no reset of all message buffers - RESET_NO_BUFFER_ALL = (RESET_NO_TXBUFFER_ALL | RESET_NO_RXBUFFER_ALL) + RESET_NO_BUFFER_ALL = RESET_NO_TXBUFFER_ALL | RESET_NO_RXBUFFER_ALL #: reset of the CAN status only - RESET_ONLY_STATUS = (0xFFFF & ~RESET_NO_STATUS) + RESET_ONLY_STATUS = 0xFFFF & ~RESET_NO_STATUS #: reset of the CAN controller only - RESET_ONLY_CANCTRL = (0xFFFF & ~RESET_NO_CANCTRL) + RESET_ONLY_CANCTRL = 0xFFFF & ~RESET_NO_CANCTRL #: reset of the transmit buffer in firmware only - RESET_ONLY_TXBUFFER_FW = (0xFFFF & ~RESET_NO_TXBUFFER_FW) + RESET_ONLY_TXBUFFER_FW = 0xFFFF & ~RESET_NO_TXBUFFER_FW #: reset of the receive buffer in firmware only - RESET_ONLY_RXBUFFER_FW = (0xFFFF & ~RESET_NO_RXBUFFER_FW) + RESET_ONLY_RXBUFFER_FW = 0xFFFF & ~RESET_NO_RXBUFFER_FW #: reset of the specified channel of the receive buffer only - RESET_ONLY_RXCHANNEL_BUFF = (0xFFFF & ~RESET_NO_RXBUFFER_CH) + RESET_ONLY_RXCHANNEL_BUFF = 0xFFFF & ~RESET_NO_RXBUFFER_CH #: reset of the specified channel of the transmit buffer only - RESET_ONLY_TXCHANNEL_BUFF = (0xFFFF & ~RESET_NO_TXBUFFER_CH) + RESET_ONLY_TXCHANNEL_BUFF = 0xFFFF & ~RESET_NO_TXBUFFER_CH #: reset of the receive buffer and receive message counter only - RESET_ONLY_RX_BUFF = (0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER)) + RESET_ONLY_RX_BUFF = 0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER) #: reset of the receive buffer and receive message counter (for GW-002) only - RESET_ONLY_RX_BUFF_GW002 = (0xFFFF & ~(RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER | - RESET_NO_TXBUFFER_FW)) + RESET_ONLY_RX_BUFF_GW002 = 0xFFFF & ~( + RESET_NO_RXBUFFER_ALL | RESET_NO_RXCOUNTER | RESET_NO_TXBUFFER_FW + ) #: reset of the transmit buffer and transmit message counter only - RESET_ONLY_TX_BUFF = (0xFFFF & ~(RESET_NO_TXBUFFER_ALL | RESET_NO_TXCOUNTER)) + RESET_ONLY_TX_BUFF = 0xFFFF & ~(RESET_NO_TXBUFFER_ALL | RESET_NO_TXCOUNTER) #: reset of all buffers and all message counters only - RESET_ONLY_ALL_BUFF = (RESET_ONLY_RX_BUFF & RESET_ONLY_TX_BUFF) + RESET_ONLY_ALL_BUFF = RESET_ONLY_RX_BUFF & RESET_ONLY_TX_BUFF #: reset of all message counters only - RESET_ONLY_ALL_COUNTER = (0xFFFF & ~RESET_NO_COUNTER_ALL) + RESET_ONLY_ALL_COUNTER = 0xFFFF & ~RESET_NO_COUNTER_ALL PRODCODE_PID_TWO_CHA = 0x1 @@ -480,7 +484,7 @@ class ResetFlags(DWORD): PRODCODE_MASK_DID = 0xFFFF0000 PRODCODE_MASK_PID = 0xFFFF -PRODCODE_MASK_PIDG3 = (PRODCODE_MASK_PID & 0xFFFFFFBF) +PRODCODE_MASK_PIDG3 = PRODCODE_MASK_PID & 0xFFFFFFBF class ProductCode(WORD): @@ -597,11 +601,11 @@ class PendingFlags(BYTE): #: number of pending CAN messages in transmit buffer of firmware PENDING_FLAG_TX_FW = 0x40 #: number of pending CAN messages in all receive buffers - PENDING_FLAG_RX_ALL = (PENDING_FLAG_RX_DLL | PENDING_FLAG_RX_SYS | PENDING_FLAG_RX_FW) + PENDING_FLAG_RX_ALL = PENDING_FLAG_RX_DLL | PENDING_FLAG_RX_SYS | PENDING_FLAG_RX_FW #: number of pending CAN messages in all transmit buffers - PENDING_FLAG_TX_ALL = (PENDING_FLAG_TX_DLL | PENDING_FLAG_TX_SYS | PENDING_FLAG_TX_FW) + PENDING_FLAG_TX_ALL = PENDING_FLAG_TX_DLL | PENDING_FLAG_TX_SYS | PENDING_FLAG_TX_FW #: number of pending CAN messages in all buffers - PENDING_FLAG_ALL = (PENDING_FLAG_RX_ALL | PENDING_FLAG_TX_ALL) + PENDING_FLAG_ALL = PENDING_FLAG_RX_ALL | PENDING_FLAG_TX_ALL class Mode(BYTE): diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index 0d823895b..49a974cac 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -21,6 +21,7 @@ def __str__(self): class UcanError(UcanException): """ Exception class for errors from USB-CAN-library. """ + def __init__(self, result, func, arguments): super().__init__(result, func, arguments) self.return_msgs = { @@ -29,7 +30,7 @@ def __init__(self, result, func, arguments): ReturnCode.ERR_HWINUSE: "the specified module is already in use", ReturnCode.ERR_ILLVERSION: "the software versions of the module and library are incompatible", ReturnCode.ERR_ILLHW: "the module with the specified device number is not connected " - "(or used by an other application)", + "(or used by an other application)", ReturnCode.ERR_ILLHANDLE: "wrong USB-CAN-Handle handed over to the function", ReturnCode.ERR_ILLPARAM: "wrong parameter handed over to the function", ReturnCode.ERR_BUSY: "instruction can not be processed at this time", @@ -60,13 +61,13 @@ def __init__(self, result, func, arguments): ReturnCode.ERRCMD_RESERVED2: "reserved", ReturnCode.ERRCMD_RESERVED3: "reserved", ReturnCode.ERRCMD_ILLBDR: "illegal baud rate value specified in BTR0/BTR1 for systec " - "USB-CANmoduls", + "USB-CANmoduls", ReturnCode.ERRCMD_NOTINIT: "CAN channel is not initialized", ReturnCode.ERRCMD_ALREADYINIT: "CAN channel is already initialized", ReturnCode.ERRCMD_ILLSUBCMD: "illegal sub-command specified", ReturnCode.ERRCMD_ILLIDX: "illegal index specified (e.g. index for cyclic CAN messages)", ReturnCode.ERRCMD_RUNNING: "cyclic CAN message(s) can not be defined because transmission of " - "cyclic CAN messages is already running", + "cyclic CAN messages is already running", } @@ -82,12 +83,12 @@ def __init__(self, result, func, arguments): ReturnCode.WARN_RESERVED1: "reserved", ReturnCode.WARN_RESERVED2: "reserved", ReturnCode.WARN_FW_TXOVERRUN: "overrun in transmit buffer of the firmware (but this CAN message " - "was successfully stored in buffer of the ibrary)", + "was successfully stored in buffer of the ibrary)", ReturnCode.WARN_FW_RXOVERRUN: "overrun in receive buffer of the firmware (but this CAN message " - "was successfully read)", + "was successfully read)", ReturnCode.WARN_FW_TXMSGLOST: "reserved", ReturnCode.WARN_NULL_PTR: "pointer is NULL", ReturnCode.WARN_TXLIMIT: "not all CAN messages could be stored to the transmit buffer in " - "USB-CAN-library", - ReturnCode.WARN_BUSY: "reserved" + "USB-CAN-library", + ReturnCode.WARN_BUSY: "reserved", } diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index 11ffd0e39..9fd542e04 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -1,8 +1,15 @@ # coding: utf-8 from ctypes import Structure, POINTER, sizeof -from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD, c_long as BOOL, c_void_p as LPVOID +from ctypes import ( + c_ubyte as BYTE, + c_ushort as WORD, + c_ulong as DWORD, + c_long as BOOL, + c_void_p as LPVOID, +) import os + # Workaround for Unix based platforms to be able to load structures for testing, etc... if os.name == "nt": from ctypes import WINFUNCTYPE as FUNCTYPE @@ -26,13 +33,17 @@ class CanMsg(Structure): :meth:`UcanServer.read_cyclic_can_msg` """ + _pack_ = 1 _fields_ = [ ("m_dwID", DWORD), # CAN Identifier ("m_bFF", BYTE), # CAN Frame Format (see enum :class:`MsgFrameFormat`) ("m_bDLC", BYTE), # CAN Data Length Code ("m_bData", BYTE * 8), # CAN Data (array of 8 bytes) - ("m_dwTime", DWORD,) # Receive time stamp in ms (for transmit messages no meaning) + ( + "m_dwTime", + DWORD, + ), # Receive time stamp in ms (for transmit messages no meaning) ] def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None): @@ -43,22 +54,31 @@ def __eq__(self, other): if not isinstance(other, CanMsg): return False - return self.id == other.id and self.frame_format == other.frame_format and self.data == other.data + return ( + self.id == other.id + and self.frame_format == other.frame_format + and self.data == other.data + ) @property - def id(self): return self.m_dwID + def id(self): + return self.m_dwID @id.setter - def id(self, id): self.m_dwID = id + def id(self, id): + self.m_dwID = id @property - def frame_format(self): return self.m_bFF + def frame_format(self): + return self.m_bFF @frame_format.setter - def frame_format(self, frame_format): self.m_bFF = frame_format + def frame_format(self, frame_format): + self.m_bFF = frame_format @property - def data(self): return self.m_bData[:self.m_bDLC] + def data(self): + return self.m_bData[: self.m_bDLC] @data.setter def data(self, data): @@ -66,7 +86,8 @@ def data(self, data): self.m_bData((BYTE * 8)(*data)) @property - def time(self): return self.m_dwTime + def time(self): + return self.m_dwTime class Status(Structure): @@ -80,6 +101,7 @@ class Status(Structure): :meth:`UcanServer.get_can_status_message` """ + _pack_ = 1 _fields_ = [ ("m_wCanStatus", WORD), # CAN error status (see enum :class:`CanStatus`) @@ -90,13 +112,17 @@ def __eq__(self, other): if not isinstance(other, Status): return False - return self.can_status == other.can_status and self.usb_status == other.usb_status + return ( + self.can_status == other.can_status and self.usb_status == other.usb_status + ) @property - def can_status(self): return self.m_wCanStatus + def can_status(self): + return self.m_wCanStatus @property - def usb_status(self): return self.m_wUsbStatus + def usb_status(self): + return self.m_wUsbStatus class InitCanParam(Structure): @@ -105,69 +131,114 @@ class InitCanParam(Structure): .. note:: This structure is only used internally. """ + _pack_ = 1 _fields_ = [ ("m_dwSize", DWORD), # size of this structure (only used internally) - ("m_bMode", BYTE), # selects the mode of CAN controller (see enum :class:`Mode`) + ( + "m_bMode", + BYTE, + ), # selects the mode of CAN controller (see enum :class:`Mode`) # Baudrate Registers for GW-001 or GW-002 ("m_bBTR0", BYTE), # Bus Timing Register 0 (see enum :class:`Baudrate`) ("m_bBTR1", BYTE), # Bus Timing Register 1 (see enum :class:`Baudrate`) ("m_bOCR", BYTE), # Output Control Register (see enum :class:`OutputControl`) - ("m_dwAMR", DWORD), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) - ("m_dwACR", DWORD), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) + ( + "m_dwAMR", + DWORD, + ), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) + ( + "m_dwACR", + DWORD, + ), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) ("m_dwBaudrate", DWORD), # Baudrate Register for all systec USB-CANmoduls - # (see enum :class:`BaudrateEx`) - ("m_wNrOfRxBufferEntries", WORD), # number of receive buffer entries (default is 4096) - ("m_wNrOfTxBufferEntries", WORD), # number of transmit buffer entries (default is 4096) + # (see enum :class:`BaudrateEx`) + ( + "m_wNrOfRxBufferEntries", + WORD, + ), # number of receive buffer entries (default is 4096) + ( + "m_wNrOfTxBufferEntries", + WORD, + ), # number of transmit buffer entries (default is 4096) ] - def __init__(self, mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries): - super().__init__(sizeof(InitCanParam), mode, BTR >> 8, BTR, OCR, AMR, ACR, - baudrate, rx_buffer_entries, tx_buffer_entries) + def __init__( + self, mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries + ): + super().__init__( + sizeof(InitCanParam), + mode, + BTR >> 8, + BTR, + OCR, + AMR, + ACR, + baudrate, + rx_buffer_entries, + tx_buffer_entries, + ) def __eq__(self, other): if not isinstance(other, InitCanParam): return False - return self.mode == other.mode and self.BTR == other.BTR and self.OCR == other.OCR and \ - self.baudrate == other.baudrate and self.rx_buffer_entries == other.rx_buffer_entries and \ - self.tx_buffer_entries == other.tx_buffer_entries + return ( + self.mode == other.mode + and self.BTR == other.BTR + and self.OCR == other.OCR + and self.baudrate == other.baudrate + and self.rx_buffer_entries == other.rx_buffer_entries + and self.tx_buffer_entries == other.tx_buffer_entries + ) @property - def mode(self): return self.m_bMode + def mode(self): + return self.m_bMode @mode.setter - def mode(self, mode): self.m_bMode = mode + def mode(self, mode): + self.m_bMode = mode @property - def BTR(self): return self.m_bBTR0 << 8 | self.m_bBTR1 + def BTR(self): + return self.m_bBTR0 << 8 | self.m_bBTR1 @BTR.setter - def BTR(self, BTR): self.m_bBTR0, self.m_bBTR1 = BTR >> 8, BTR + def BTR(self, BTR): + self.m_bBTR0, self.m_bBTR1 = BTR >> 8, BTR @property - def OCR(self): return self.m_bOCR + def OCR(self): + return self.m_bOCR @OCR.setter - def OCR(self, OCR): self.m_bOCR = OCR + def OCR(self, OCR): + self.m_bOCR = OCR @property - def baudrate(self): return self.m_dwBaudrate + def baudrate(self): + return self.m_dwBaudrate @baudrate.setter - def baudrate(self, baudrate): self.m_dwBaudrate = baudrate + def baudrate(self, baudrate): + self.m_dwBaudrate = baudrate @property - def rx_buffer_entries(self): return self.m_wNrOfRxBufferEntries + def rx_buffer_entries(self): + return self.m_wNrOfRxBufferEntries @rx_buffer_entries.setter - def rx_buffer_entries(self, rx_buffer_entries): self.m_wNrOfRxBufferEntries = rx_buffer_entries + def rx_buffer_entries(self, rx_buffer_entries): + self.m_wNrOfRxBufferEntries = rx_buffer_entries @property - def tx_buffer_entries(self): return self.m_wNrOfTxBufferEntries + def tx_buffer_entries(self): + return self.m_wNrOfTxBufferEntries @tx_buffer_entries.setter - def tx_buffer_entries(self, tx_buffer_entries): self.m_wNrOfTxBufferEntries = tx_buffer_entries + def tx_buffer_entries(self, tx_buffer_entries): + self.m_wNrOfTxBufferEntries = tx_buffer_entries class Handle(BYTE): @@ -181,6 +252,7 @@ class HardwareInfoEx(Structure): .. seealso:: :meth:`UcanServer.get_hardware_info` """ + _pack_ = 1 _fields_ = [ ("m_dwSize", DWORD), # size of this structure (only used internally) @@ -204,27 +276,43 @@ def __eq__(self, other): if not isinstance(other, HardwareInfoEx): return False - return self.device_number == other.device_number and self.serial == other.serial and \ - self.fw_version == other.fw_version and self.product_code == other.product_code and \ - self.unique_id == other.unique_id and self.flags == other.flags + return ( + self.device_number == other.device_number + and self.serial == other.serial + and self.fw_version == other.fw_version + and self.product_code == other.product_code + and self.unique_id == other.unique_id + and self.flags == other.flags + ) @property - def device_number(self): return self.m_bDeviceNr + def device_number(self): + return self.m_bDeviceNr @property - def serial(self): return self.m_dwSerialNr + def serial(self): + return self.m_dwSerialNr @property - def fw_version(self): return self.m_dwFwVersionEx + def fw_version(self): + return self.m_dwFwVersionEx @property - def product_code(self): return self.m_dwProductCode + def product_code(self): + return self.m_dwProductCode @property - def unique_id(self): return self.m_dwUniqueId0, self.m_dwUniqueId1, self.m_dwUniqueId2, self.m_dwUniqueId3 + def unique_id(self): + return ( + self.m_dwUniqueId0, + self.m_dwUniqueId1, + self.m_dwUniqueId2, + self.m_dwUniqueId3, + ) @property - def flags(self): return self.m_dwFlags + def flags(self): + return self.m_dwFlags # void PUBLIC UcanCallbackFktEx (Handle UcanHandle_p, DWORD dwEvent_p, @@ -240,13 +328,20 @@ class HardwareInitInfo(Structure): .. note:: This structure is only used internally. """ + _pack_ = 1 _fields_ = [ ("m_dwSize", DWORD), # size of this structure - ("m_fDoInitialize", BOOL), # specifies if the found module should be initialized by the DLL + ( + "m_fDoInitialize", + BOOL, + ), # specifies if the found module should be initialized by the DLL ("m_pUcanHandle", Handle), # pointer to variable receiving the USB-CAN-Handle ("m_fpCallbackFktEx", CallbackFktEx), # pointer to callback function - ("m_pCallbackArg", LPVOID), # pointer to user defined parameter for callback function + ( + "m_pCallbackArg", + LPVOID, + ), # pointer to user defined parameter for callback function ("m_fTryNext", BOOL), # specifies if a further module should be found ] @@ -258,6 +353,7 @@ class ChannelInfo(Structure): .. seealso:: :meth:`UcanServer.get_hardware_info` """ + _pack_ = 1 _fields_ = [ ("m_dwSize", DWORD), # size of this structure @@ -265,12 +361,24 @@ class ChannelInfo(Structure): ("m_bBTR0", BYTE), # Bus Timing Register 0 (see enum :class:`Baudrate`) ("m_bBTR1", BYTE), # Bus Timing Register 1 (see enum :class:`Baudrate`) ("m_bOCR", BYTE), # Output Control Register (see enum :class:`OutputControl`) - ("m_dwAMR", DWORD), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) - ("m_dwACR", DWORD), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) + ( + "m_dwAMR", + DWORD, + ), # Acceptance Mask Register (see method :meth:`UcanServer.set_acceptance`) + ( + "m_dwACR", + DWORD, + ), # Acceptance Code Register (see method :meth:`UcanServer.set_acceptance`) ("m_dwBaudrate", DWORD), # Baudrate Register for all systec USB-CANmoduls - # (see enum :class:`BaudrateEx`) - ("m_fCanIsInit", BOOL), # True if the CAN interface is initialized, otherwise false - ("m_wCanStatus", WORD), # CAN status (same as received by method :meth:`UcanServer.get_status`) + # (see enum :class:`BaudrateEx`) + ( + "m_fCanIsInit", + BOOL, + ), # True if the CAN interface is initialized, otherwise false + ( + "m_wCanStatus", + WORD, + ), # CAN status (same as received by method :meth:`UcanServer.get_status`) ] def __init__(self): @@ -280,33 +388,48 @@ def __eq__(self, other): if not isinstance(other, ChannelInfo): return False - return self.mode == other.mode and self.BTR == other.BTR and self.OCR == other.OCR and \ - self.AMR == other.AMR and self.ACR == other.ACR and self.baudrate == other.baudrate and \ - self.can_is_init == other.can_is_init and self.can_status == other.can_status + return ( + self.mode == other.mode + and self.BTR == other.BTR + and self.OCR == other.OCR + and self.AMR == other.AMR + and self.ACR == other.ACR + and self.baudrate == other.baudrate + and self.can_is_init == other.can_is_init + and self.can_status == other.can_status + ) @property - def mode(self): return self.m_bMode + def mode(self): + return self.m_bMode @property - def BTR(self): return self.m_bBTR0 << 8 | self.m_bBTR1 + def BTR(self): + return self.m_bBTR0 << 8 | self.m_bBTR1 @property - def OCR(self): return self.m_bOCR + def OCR(self): + return self.m_bOCR @property - def AMR(self): return self.m_dwAMR + def AMR(self): + return self.m_dwAMR @property - def ACR(self): return self.m_dwACR + def ACR(self): + return self.m_dwACR @property - def baudrate(self): return self.m_dwBaudrate + def baudrate(self): + return self.m_dwBaudrate @property - def can_is_init(self): return self.m_fCanIsInit + def can_is_init(self): + return self.m_fCanIsInit @property - def can_status(self): return self.m_wCanStatus + def can_status(self): + return self.m_wCanStatus class MsgCountInfo(Structure): @@ -318,16 +441,19 @@ class MsgCountInfo(Structure): .. note:: This structure is only used internally. """ + _fields_ = [ ("m_wSentMsgCount", WORD), # number of sent CAN messages ("m_wRecvdMsgCount", WORD), # number of received CAN messages ] @property - def sent_msg_count(self): return self.m_wSentMsgCount + def sent_msg_count(self): + return self.m_wSentMsgCount @property - def recv_msg_count(self): return self.m_wRecvdMsgCount + def recv_msg_count(self): + return self.m_wRecvdMsgCount # void (PUBLIC *ConnectControlFktEx) (DWORD dwEvent_p, DWORD dwParam_p, void* pArg_p); @@ -335,4 +461,6 @@ def recv_msg_count(self): return self.m_wRecvdMsgCount # typedef void (PUBLIC *EnumCallback) (DWORD dwIndex_p, BOOL fIsUsed_p, # HardwareInfoEx* pHwInfoEx_p, HardwareInitInfo* pInitInfo_p, void* pArg_p); -EnumCallback = FUNCTYPE(None, DWORD, BOOL, POINTER(HardwareInfoEx), POINTER(HardwareInitInfo), LPVOID) +EnumCallback = FUNCTYPE( + None, DWORD, BOOL, POINTER(HardwareInfoEx), POINTER(HardwareInitInfo), LPVOID +) diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index 8aded3804..1b08c51a0 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -21,7 +21,9 @@ def check_valid_rx_can_msg(result): :return: True if a valid CAN messages was received, otherwise False. :rtype: bool """ - return (result.value == ReturnCode.SUCCESSFUL) or (result.value > ReturnCode.WARNING) + return (result.value == ReturnCode.SUCCESSFUL) or ( + result.value > ReturnCode.WARNING + ) def check_tx_ok(result): @@ -37,7 +39,9 @@ def check_tx_ok(result): .. :seealso: :const:`ReturnCode.WARN_TXLIMIT` """ - return (result.value == ReturnCode.SUCCESSFUL) or (result.value > ReturnCode.WARNING) + return (result.value == ReturnCode.SUCCESSFUL) or ( + result.value > ReturnCode.WARNING + ) def check_tx_success(result): @@ -81,7 +85,9 @@ def check_error(result): :return: True if a function returned error, otherwise False. :rtype: bool """ - return (result.value != ReturnCode.SUCCESSFUL) and (result.value < ReturnCode.WARNING) + return (result.value != ReturnCode.SUCCESSFUL) and ( + result.value < ReturnCode.WARNING + ) def check_error_cmd(result): @@ -113,7 +119,7 @@ def check_result(result, func, arguments): try: # Select the proper dll architecture - lib = WinDLL('usbcan64.dll' if sys.maxsize > 2 ** 32 else 'usbcan32.dll') + lib = WinDLL("usbcan64.dll" if sys.maxsize > 2 ** 32 else "usbcan32.dll") # BOOL PUBLIC UcanSetDebugMode (DWORD dwDbgLevel_p, _TCHAR* pszFilePathName_p, DWORD dwFlags_p); UcanSetDebugMode = lib.UcanSetDebugMode @@ -149,7 +155,17 @@ def check_result(result, func, arguments): # DWORD dwProductCodeLow_p, DWORD dwProductCodeHigh_p); UcanEnumerateHardware = lib.UcanEnumerateHardware UcanEnumerateHardware.restype = DWORD - UcanEnumerateHardware.argtypes = [EnumCallback, LPVOID, BOOL, BYTE, BYTE, DWORD, DWORD, DWORD, DWORD] + UcanEnumerateHardware.argtypes = [ + EnumCallback, + LPVOID, + BOOL, + BYTE, + BYTE, + DWORD, + DWORD, + DWORD, + DWORD, + ] # BYTE PUBLIC UcanInitHardwareEx (Handle* pUcanHandle_p, BYTE bDeviceNr_p, # CallbackFktEx fpCallbackFktEx_p, void* pCallbackArg_p); @@ -176,8 +192,12 @@ def check_result(result, func, arguments): # ChannelInfo* pCanInfoCh0_p, ChannelInfo* pCanInfoCh1_p); UcanGetHardwareInfoEx2 = lib.UcanGetHardwareInfoEx2 UcanGetHardwareInfoEx2.restype = ReturnCode - UcanGetHardwareInfoEx2.argtypes = [Handle, POINTER(HardwareInfoEx), POINTER(ChannelInfo), - POINTER(ChannelInfo)] + UcanGetHardwareInfoEx2.argtypes = [ + Handle, + POINTER(HardwareInfoEx), + POINTER(ChannelInfo), + POINTER(ChannelInfo), + ] UcanGetHardwareInfoEx2.errcheck = check_result # BYTE PUBLIC UcanInitCanEx2 (Handle UcanHandle_p, BYTE bChannel_p, tUcaninit_canParam* pinit_canParam_p); @@ -210,7 +230,12 @@ def check_result(result, func, arguments): # CanMsg* pCanMsg_p, DWORD* pdwCount_p); UcanReadCanMsgEx = lib.UcanReadCanMsgEx UcanReadCanMsgEx.restype = ReturnCode - UcanReadCanMsgEx.argtypes = [Handle, POINTER(BYTE), POINTER(CanMsg), POINTER(DWORD)] + UcanReadCanMsgEx.argtypes = [ + Handle, + POINTER(BYTE), + POINTER(CanMsg), + POINTER(DWORD), + ] UcanReadCanMsgEx.errcheck = check_result # BYTE PUBLIC UcanWriteCanMsgEx (Handle UcanHandle_p, BYTE bChannel_p, @@ -295,6 +320,7 @@ class UcanServer: """ UcanServer is a Python wrapper class for using the usbcan32.dll / usbcan64.dll. """ + _modules_found = [] _connect_control_ref = None @@ -304,7 +330,7 @@ def __init__(self): self._hw_is_initialized = False self._ch_is_initialized = { Channel.CHANNEL_CH0: False, - Channel.CHANNEL_CH1: False + Channel.CHANNEL_CH1: False, } self._callback_ref = CallbackFktEx(self._callback) if self._connect_control_ref is None: @@ -343,16 +369,33 @@ def is_can1_initialized(self): @classmethod def _enum_callback(cls, index, is_used, hw_info_ex, init_info, arg): - cls._modules_found.append((index, bool(is_used), hw_info_ex.contents, init_info.contents)) + cls._modules_found.append( + (index, bool(is_used), hw_info_ex.contents, init_info.contents) + ) @classmethod - def enumerate_hardware(cls, device_number_low=0, device_number_high=-1, serial_low=0, serial_high=-1, - product_code_low=0, product_code_high=-1, enum_used_devices=False): + def enumerate_hardware( + cls, + device_number_low=0, + device_number_high=-1, + serial_low=0, + serial_high=-1, + product_code_low=0, + product_code_high=-1, + enum_used_devices=False, + ): cls._modules_found = [] - UcanEnumerateHardware(cls._enum_callback_ref, None, enum_used_devices, - device_number_low, device_number_high, - serial_low, serial_high, - product_code_low, product_code_high) + UcanEnumerateHardware( + cls._enum_callback_ref, + None, + enum_used_devices, + device_number_low, + device_number_high, + serial_low, + serial_high, + product_code_low, + product_code_high, + ) return cls._modules_found def init_hardware(self, serial=None, device_number=ANY_MODULE): @@ -365,14 +408,27 @@ def init_hardware(self, serial=None, device_number=ANY_MODULE): if not self._hw_is_initialized: # initialize hardware either by device number or serial if serial is None: - UcanInitHardwareEx(byref(self._handle), device_number, self._callback_ref, None) + UcanInitHardwareEx( + byref(self._handle), device_number, self._callback_ref, None + ) else: - UcanInitHardwareEx2(byref(self._handle), serial, self._callback_ref, None) + UcanInitHardwareEx2( + byref(self._handle), serial, self._callback_ref, None + ) self._hw_is_initialized = True - def init_can(self, channel=Channel.CHANNEL_CH0, BTR=Baudrate.BAUD_1MBit, baudrate=BaudrateEx.BAUDEX_USE_BTR01, - AMR=AMR_ALL, ACR=ACR_ALL, mode=Mode.MODE_NORMAL, OCR=OutputControl.OCR_DEFAULT, - rx_buffer_entries=DEFAULT_BUFFER_ENTRIES, tx_buffer_entries=DEFAULT_BUFFER_ENTRIES): + def init_can( + self, + channel=Channel.CHANNEL_CH0, + BTR=Baudrate.BAUD_1MBit, + baudrate=BaudrateEx.BAUDEX_USE_BTR01, + AMR=AMR_ALL, + ACR=ACR_ALL, + mode=Mode.MODE_NORMAL, + OCR=OutputControl.OCR_DEFAULT, + rx_buffer_entries=DEFAULT_BUFFER_ENTRIES, + tx_buffer_entries=DEFAULT_BUFFER_ENTRIES, + ): """ Initializes a specific CAN channel of a device. @@ -388,7 +444,9 @@ def init_can(self, channel=Channel.CHANNEL_CH0, BTR=Baudrate.BAUD_1MBit, baudrat :param int tx_buffer_entries: The number of maximum entries in the transmit buffer. """ if not self._ch_is_initialized.get(channel, False): - init_param = InitCanParam(mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries) + init_param = InitCanParam( + mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries + ) UcanInitCanEx2(self._handle, channel, init_param) self._ch_is_initialized[channel] = True @@ -407,7 +465,7 @@ def read_can_msg(self, channel, count): c_can_msg = (CanMsg * count)() c_count = DWORD(count) UcanReadCanMsgEx(self._handle, byref(c_channel), c_can_msg, byref(c_count)) - return c_can_msg[:c_count.value], c_channel.value + return c_can_msg[: c_count.value], c_channel.value def write_can_msg(self, channel, can_msg): """ @@ -493,7 +551,9 @@ def get_hardware_info(self): """ hw_info_ex = HardwareInfoEx() can_info_ch0, can_info_ch1 = ChannelInfo(), ChannelInfo() - UcanGetHardwareInfoEx2(self._handle, byref(hw_info_ex), byref(can_info_ch0), byref(can_info_ch1)) + UcanGetHardwareInfoEx2( + self._handle, byref(hw_info_ex), byref(can_info_ch0), byref(can_info_ch1) + ) return hw_info_ex, can_info_ch0, can_info_ch1 def get_fw_version(self): @@ -534,7 +594,7 @@ def read_cyclic_can_msg(self, channel, count): c_can_msg = (CanMsg * count)() c_count = DWORD(count) UcanReadCyclicCanMsg(self._handle, byref(c_channel), c_can_msg, c_count) - return c_can_msg[:c_count.value] + return c_can_msg[: c_count.value] def enable_cyclic_can_msg(self, channel, flags): """ @@ -570,7 +630,9 @@ def get_can_error_counter(self, channel): """ tx_error_counter = DWORD(0) rx_error_counter = DWORD(0) - UcanGetCanErrorCounter(self._handle, channel, byref(tx_error_counter), byref(rx_error_counter)) + UcanGetCanErrorCounter( + self._handle, channel, byref(tx_error_counter), byref(rx_error_counter) + ) return tx_error_counter, rx_error_counter def set_tx_timeout(self, channel, timeout): @@ -593,7 +655,11 @@ def shutdown(self, channel=Channel.CHANNEL_ALL, shutdown_hardware=True): """ # shutdown each channel if it's initialized for _channel, is_initialized in self._ch_is_initialized.items(): - if is_initialized and (_channel == channel or channel == Channel.CHANNEL_ALL or shutdown_hardware): + if is_initialized and ( + _channel == channel + or channel == Channel.CHANNEL_ALL + or shutdown_hardware + ): UcanDeinitCanEx(self._handle, _channel) self._ch_is_initialized[_channel] = False @@ -651,8 +717,13 @@ def get_can_status_message(can_status): CanStatus.CANERR_OVERRUN: "Rx-buffer is full", CanStatus.CANERR_XMTFULL: "Tx-buffer is full", } - return "OK" if can_status == CanStatus.CANERR_OK \ - else ", ".join(msg for status, msg in status_msgs.items() if can_status & status) + return ( + "OK" + if can_status == CanStatus.CANERR_OK + else ", ".join( + msg for status, msg in status_msgs.items() if can_status & status + ) + ) @staticmethod def get_baudrate_message(baudrate): @@ -729,7 +800,9 @@ def get_product_code_message(product_code): ProductCode.PRODCODE_PID_RESERVED1: "Reserved", ProductCode.PRODCODE_PID_RESERVED2: "Reserved", } - return product_code_msgs.get(product_code & PRODCODE_MASK_PID, "Product code is unknown") + return product_code_msgs.get( + product_code & PRODCODE_MASK_PID, "Product code is unknown" + ) @classmethod def convert_to_major_ver(cls, version): @@ -775,8 +848,10 @@ def check_version_is_equal_or_higher(cls, version, cmp_major, cmp_minor): :return: True if equal or higher, otherwise False. :rtype: bool """ - return (cls.convert_to_major_ver(version) > cmp_major) or \ - (cls.convert_to_major_ver(version) == cmp_major and cls.convert_to_minor_ver(version) >= cmp_minor) + return (cls.convert_to_major_ver(version) > cmp_major) or ( + cls.convert_to_major_ver(version) == cmp_major + and cls.convert_to_minor_ver(version) >= cmp_minor + ) @classmethod def check_is_systec(cls, hw_info_ex): @@ -788,7 +863,9 @@ def check_is_systec(cls, hw_info_ex): :return: True when the module is a systec USB-CANmodul, otherwise False. :rtype: bool """ - return (hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) >= ProductCode.PRODCODE_PID_MULTIPORT + return ( + hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID + ) >= ProductCode.PRODCODE_PID_MULTIPORT @classmethod def check_is_G4(cls, hw_info_ex): @@ -824,8 +901,9 @@ def check_support_cyclic_msg(cls, hw_info_ex): :return: True when the module does support cyclic CAN messages, otherwise False. :rtype: bool """ - return cls.check_is_systec(hw_info_ex) and \ - cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 3, 6) + return cls.check_is_systec(hw_info_ex) and cls.check_version_is_equal_or_higher( + hw_info_ex.m_dwFwVersionEx, 3, 6 + ) @classmethod def check_support_two_channel(cls, hw_info_ex): @@ -837,7 +915,9 @@ def check_support_two_channel(cls, hw_info_ex): :return: True when the module (logical device) does support two CAN channels, otherwise False. :rtype: bool """ - return cls.check_is_systec(hw_info_ex) and (hw_info_ex.m_dwProductCode & PRODCODE_PID_TWO_CHA) + return cls.check_is_systec(hw_info_ex) and ( + hw_info_ex.m_dwProductCode & PRODCODE_PID_TWO_CHA + ) @classmethod def check_support_term_resistor(cls, hw_info_ex): @@ -861,9 +941,17 @@ def check_support_user_port(cls, hw_info_ex): :return: True when the module supports a user I/O port, otherwise False. :rtype: bool """ - return ((hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) != ProductCode.PRODCODE_PID_BASIC) \ - and ((hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) != ProductCode.PRODCODE_PID_RESERVED1) \ - and cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 2, 16) + return ( + ( + (hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) + != ProductCode.PRODCODE_PID_BASIC + ) + and ( + (hw_info_ex.m_dwProductCode & PRODCODE_MASK_PID) + != ProductCode.PRODCODE_PID_RESERVED1 + ) + and cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 2, 16) + ) @classmethod def check_support_rb_user_port(cls, hw_info_ex): @@ -899,8 +987,9 @@ def check_support_ucannet(cls, hw_info_ex): :return: True when the module does support the usage of the USB-CANnetwork driver, otherwise False. :rtype: bool """ - return cls.check_is_systec(hw_info_ex) and \ - cls.check_version_is_equal_or_higher(hw_info_ex.m_dwFwVersionEx, 3, 8) + return cls.check_is_systec(hw_info_ex) and cls.check_version_is_equal_or_higher( + hw_info_ex.m_dwFwVersionEx, 3, 8 + ) @classmethod def calculate_amr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True): @@ -915,8 +1004,14 @@ def calculate_amr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True :return: Value for AMR. :rtype: int """ - return (((from_id ^ to_id) << 3) | (0x7 if rtr_too and not rtr_only else 0x3)) if is_extended else \ - (((from_id ^ to_id) << 21) | (0x1FFFFF if rtr_too and not rtr_only else 0xFFFFF)) + return ( + (((from_id ^ to_id) << 3) | (0x7 if rtr_too and not rtr_only else 0x3)) + if is_extended + else ( + ((from_id ^ to_id) << 21) + | (0x1FFFFF if rtr_too and not rtr_only else 0xFFFFF) + ) + ) @classmethod def calculate_acr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True): @@ -931,8 +1026,11 @@ def calculate_acr(cls, is_extended, from_id, to_id, rtr_only=False, rtr_too=True :return: Value for ACR. :rtype: int """ - return (((from_id & to_id) << 3) | (0x04 if rtr_only else 0)) if is_extended else \ - (((from_id & to_id) << 21) | (0x100000 if rtr_only else 0)) + return ( + (((from_id & to_id) << 3) | (0x04 if rtr_only else 0)) + if is_extended + else (((from_id & to_id) << 21) | (0x100000 if rtr_only else 0)) + ) def _connect_control(self, event, param, arg): """ diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index b4852db91..29a76f468 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -9,7 +9,7 @@ from .structures import * from .ucan import UcanServer -log = logging.getLogger('can.systec') +log = logging.getLogger("can.systec") class Ucan(UcanServer): @@ -46,7 +46,7 @@ class UcanBus(BusABC): 250000: Baudrate.BAUD_250kBit, 500000: Baudrate.BAUD_500kBit, 800000: Baudrate.BAUD_800kBit, - 1000000: Baudrate.BAUD_1MBit + 1000000: Baudrate.BAUD_1MBit, } def __init__(self, channel, can_filters=None, **kwargs): @@ -97,14 +97,14 @@ def __init__(self, channel, can_filters=None, **kwargs): raise ImportError("The SYSTEC ucan library has not been initialized.") self.channel = int(channel) - device_number = int(kwargs.get('device_number', ANY_MODULE)) + device_number = int(kwargs.get("device_number", ANY_MODULE)) # configuration options - bitrate = kwargs.get('bitrate', 500000) + bitrate = kwargs.get("bitrate", 500000) if bitrate not in self.BITRATES: raise ValueError("Invalid bitrate {}".format(bitrate)) - state = kwargs.get('state', BusState.ACTIVE) + state = kwargs.get("state", BusState.ACTIVE) if state is BusState.ACTIVE or state is BusState.PASSIVE: self._state = state else: @@ -112,10 +112,10 @@ def __init__(self, channel, can_filters=None, **kwargs): # get parameters self._params = { - "mode": Mode.MODE_NORMAL | - (Mode.MODE_TX_ECHO if kwargs.get('receive_own_messages') else 0) | - (Mode.MODE_LISTEN_ONLY if state is BusState.PASSIVE else 0), - "BTR": self.BITRATES[bitrate] + "mode": Mode.MODE_NORMAL + | (Mode.MODE_TX_ECHO if kwargs.get("receive_own_messages") else 0) + | (Mode.MODE_LISTEN_ONLY if state is BusState.PASSIVE else 0), + "BTR": self.BITRATES[bitrate], } # get extra parameters if kwargs.get("rx_buffer_entries"): @@ -126,11 +126,11 @@ def __init__(self, channel, can_filters=None, **kwargs): self._ucan.init_hardware(device_number=device_number) self._ucan.init_can(self.channel, **self._params) hw_info_ex, _, _ = self._ucan.get_hardware_info() - self.channel_info = '%s, S/N %s, CH %s, BTR %s' % ( + self.channel_info = "%s, S/N %s, CH %s, BTR %s" % ( self._ucan.get_product_code_message(hw_info_ex.product_code), hw_info_ex.serial, self.channel, - self._ucan.get_baudrate_message(self.BITRATES[bitrate]) + self._ucan.get_baudrate_message(self.BITRATES[bitrate]), ) self._is_filtered = False @@ -141,12 +141,14 @@ def _recv_internal(self, timeout): if not message: return None, False - msg = Message(timestamp=float(message[0].time) / 1000.0, - is_remote_frame=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_RTR), - is_extended_id=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_EXT), - arbitration_id=message[0].id, - dlc=len(message[0].data), - data=message[0].data) + msg = Message( + timestamp=float(message[0].time) / 1000.0, + is_remote_frame=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_RTR), + is_extended_id=bool(message[0].frame_format & MsgFrameFormat.MSG_FF_EXT), + arbitration_id=message[0].id, + dlc=len(message[0].data), + data=message[0].data, + ) return msg, self._is_filtered def send(self, msg, timeout=None): @@ -171,11 +173,13 @@ def send(self, msg, timeout=None): if timeout is not None and timeout >= 0: self._ucan.set_tx_timeout(self.channel, int(timeout * 1000)) - message = CanMsg(msg.arbitration_id, - MsgFrameFormat.MSG_FF_STD | - (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) | - (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), - msg.data) + message = CanMsg( + msg.arbitration_id, + MsgFrameFormat.MSG_FF_STD + | (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) + | (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), + msg.data, + ) self._ucan.write_can_msg(self.channel, [message]) @staticmethod @@ -184,28 +188,36 @@ def _detect_available_configs(): try: # index, is_used, hw_info_ex, init_info for _, _, hw_info_ex, _ in Ucan.enumerate_hardware(): - configs.append({'interface': 'systec', - 'channel': Channel.CHANNEL_CH0, - 'device_number': hw_info_ex.device_number}) + configs.append( + { + "interface": "systec", + "channel": Channel.CHANNEL_CH0, + "device_number": hw_info_ex.device_number, + } + ) if Ucan.check_support_two_channel(hw_info_ex): - configs.append({'interface': 'systec', - 'channel': Channel.CHANNEL_CH1, - 'device_number': hw_info_ex.device_number}) + configs.append( + { + "interface": "systec", + "channel": Channel.CHANNEL_CH1, + "device_number": hw_info_ex.device_number, + } + ) except Exception: log.warning("The SYSTEC ucan library has not been initialized.") return configs def _apply_filters(self, filters): if filters and len(filters) == 1: - can_id = filters[0]['can_id'] - can_mask = filters[0]['can_mask'] + can_id = filters[0]["can_id"] + can_mask = filters[0]["can_mask"] self._ucan.set_acceptance(self.channel, can_mask, can_id) self._is_filtered = True - log.info('Hardware filtering on ID 0x%X, mask 0x%X', can_id, can_mask) + log.info("Hardware filtering on ID 0x%X, mask 0x%X", can_id, can_mask) else: self._ucan.set_acceptance(self.channel) self._is_filtered = False - log.info('Hardware filtering has been disabled') + log.info("Hardware filtering has been disabled") def flush_tx_buffer(self): """ @@ -214,7 +226,7 @@ def flush_tx_buffer(self): :raises can.CanError: If flushing of the transmit buffer failed. """ - log.info('Flushing transmit buffer') + log.info("Flushing transmit buffer") self._ucan.reset_can(self.channel, ResetFlags.RESET_ONLY_TX_BUFF) @staticmethod @@ -239,11 +251,17 @@ def create_filter(extended, from_id, to_id, rtr_only, rtr_too): :return: Returns list with one filter containing a "can_id", a "can_mask" and "extended" key. """ - return [{ - "can_id": Ucan.calculate_acr(extended, from_id, to_id, rtr_only, rtr_too), - "can_mask": Ucan.calculate_amr(extended, from_id, to_id, rtr_only, rtr_too), - "extended": extended - }] + return [ + { + "can_id": Ucan.calculate_acr( + extended, from_id, to_id, rtr_only, rtr_too + ), + "can_mask": Ucan.calculate_amr( + extended, from_id, to_id, rtr_only, rtr_too + ), + "extended": extended, + } + ] @property def state(self): @@ -251,7 +269,9 @@ def state(self): @state.setter def state(self, new_state): - if self._state is not BusState.ERROR and (new_state is BusState.ACTIVE or new_state is BusState.PASSIVE): + if self._state is not BusState.ERROR and ( + new_state is BusState.ACTIVE or new_state is BusState.PASSIVE + ): # close the CAN channel self._ucan.shutdown(self.channel, False) # set mode diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index ddd7ff984..f124df196 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -14,16 +14,30 @@ def WMIDateStringToDate(dtmDate): if dtmDate[4] == 0: - strDateTime = dtmDate[5] + '/' + strDateTime = dtmDate[5] + "/" else: - strDateTime = dtmDate[4] + dtmDate[5] + '/' + strDateTime = dtmDate[4] + dtmDate[5] + "/" if dtmDate[6] == 0: - strDateTime = strDateTime + dtmDate[7] + '/' + strDateTime = strDateTime + dtmDate[7] + "/" else: - strDateTime = strDateTime + dtmDate[6] + dtmDate[7] + '/' - strDateTime = strDateTime + dtmDate[0] + dtmDate[1] + dtmDate[2] + dtmDate[3] + ' ' + dtmDate[8] + dtmDate[9] \ - + ':' + dtmDate[10] + dtmDate[11] + ':' + dtmDate[12] + dtmDate[13] + strDateTime = strDateTime + dtmDate[6] + dtmDate[7] + "/" + strDateTime = ( + strDateTime + + dtmDate[0] + + dtmDate[1] + + dtmDate[2] + + dtmDate[3] + + " " + + dtmDate[8] + + dtmDate[9] + + ":" + + dtmDate[10] + + dtmDate[11] + + ":" + + dtmDate[12] + + dtmDate[13] + ) return strDateTime diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index ffe04979a..81ba027ce 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -12,7 +12,7 @@ from .serial_selector import find_serial_devices # Set up logging -log = logging.getLogger('can.usb2can') +log = logging.getLogger("can.usb2can") def message_convert_tx(msg): @@ -46,13 +46,15 @@ def message_convert_rx(message_rx): is_remote_frame = bool(message_rx.flags & IS_REMOTE_FRAME) is_error_frame = bool(message_rx.flags & IS_ERROR_FRAME) - return Message(timestamp=message_rx.timestamp, - is_remote_frame=is_remote_frame, - is_extended_id=is_extended_id, - is_error_frame=is_error_frame, - arbitration_id=message_rx.id, - dlc=message_rx.sizeData, - data=message_rx.data[:message_rx.sizeData]) + return Message( + timestamp=message_rx.timestamp, + is_remote_frame=is_remote_frame, + is_extended_id=is_extended_id, + is_error_frame=is_error_frame, + arbitration_id=message_rx.id, + dlc=message_rx.sizeData, + data=message_rx.data[: message_rx.sizeData], + ) class Usb2canBus(BusABC): @@ -83,8 +85,15 @@ class Usb2canBus(BusABC): """ - def __init__(self, channel=None, dll="usb2can.dll", flags=0x00000008, *args, - bitrate=500000, **kwargs): + def __init__( + self, + channel=None, + dll="usb2can.dll", + flags=0x00000008, + *args, + bitrate=500000, + **kwargs + ): self.can = Usb2CanAbstractionLayer(dll) @@ -106,8 +115,9 @@ def __init__(self, channel=None, dll="usb2can.dll", flags=0x00000008, *args, connector = "{}; {}".format(device_id, baudrate) self.handle = self.can.open(connector, flags_t) - super().__init__(channel=channel, dll=dll, flags_t=flags_t, bitrate=bitrate, - *args, **kwargs) + super().__init__( + channel=channel, dll=dll, flags_t=flags_t, bitrate=bitrate, *args, **kwargs + ) def send(self, msg, timeout=None): tx = message_convert_tx(msg) @@ -120,7 +130,6 @@ def send(self, msg, timeout=None): if status != CANAL_ERROR_SUCCESS: raise CanError("could not send message: status == {}".format(status)) - def _recv_internal(self, timeout): messagerx = CanalMsg() @@ -137,7 +146,7 @@ def _recv_internal(self, timeout): elif status in (CANAL_ERROR_RCV_EMPTY, CANAL_ERROR_TIMEOUT): rx = None else: - log.error('Canal Error %s', status) + log.error("Canal Error %s", status) rx = None return rx, False @@ -170,4 +179,4 @@ def detect_available_configs(serial_matcher=None): else: channels = find_serial_devices() - return [{'interface': 'usb2can', 'channel': c} for c in channels] + return [{"interface": "usb2can", "channel": c} for c in channels] diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index e17f255a8..0aaf1b4f2 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -11,7 +11,7 @@ import can -log = logging.getLogger('can.usb2can') +log = logging.getLogger("can.usb2can") # type definitions flags_t = c_ulong @@ -31,33 +31,39 @@ class CanalStatistics(Structure): - _fields_ = [('ReceiveFrams', c_ulong), - ('TransmistFrams', c_ulong), - ('ReceiveData', c_ulong), - ('TransmitData', c_ulong), - ('Overruns', c_ulong), - ('BusWarnings', c_ulong), - ('BusOff', c_ulong)] + _fields_ = [ + ("ReceiveFrams", c_ulong), + ("TransmistFrams", c_ulong), + ("ReceiveData", c_ulong), + ("TransmitData", c_ulong), + ("Overruns", c_ulong), + ("BusWarnings", c_ulong), + ("BusOff", c_ulong), + ] stat = CanalStatistics class CanalStatus(Structure): - _fields_ = [('channel_status', c_ulong), - ('lasterrorcode', c_ulong), - ('lasterrorsubcode', c_ulong), - ('lasterrorstr', c_byte * 80)] + _fields_ = [ + ("channel_status", c_ulong), + ("lasterrorcode", c_ulong), + ("lasterrorsubcode", c_ulong), + ("lasterrorstr", c_byte * 80), + ] # data type for the CAN Message class CanalMsg(Structure): - _fields_ = [('flags', c_ulong), - ('obid', c_ulong), - ('id', c_ulong), - ('sizeData', c_ubyte), - ('data', c_ubyte * 8), - ('timestamp', c_ulong)] + _fields_ = [ + ("flags", c_ulong), + ("obid", c_ulong), + ("id", c_ulong), + ("sizeData", c_ubyte), + ("data", c_ubyte * 8), + ("timestamp", c_ulong), + ] class Usb2CanAbstractionLayer: @@ -75,7 +81,7 @@ def __init__(self, dll="usb2can.dll"): self.__m_dllBasic = windll.LoadLibrary(dll) if self.__m_dllBasic is None: - log.warning('DLL failed to load at path: {}'.format(dll)) + log.warning("DLL failed to load at path: {}".format(dll)) def open(self, configuration, flags): """ @@ -90,19 +96,25 @@ def open(self, configuration, flags): try: # we need to convert this into bytes, since the underlying DLL cannot # handle non-ASCII configuration strings - config_ascii = configuration.encode('ascii', 'ignore') + config_ascii = configuration.encode("ascii", "ignore") result = self.__m_dllBasic.CanalOpen(config_ascii, flags) except Exception as ex: # catch any errors thrown by this call and re-raise - raise can.CanError('CanalOpen() failed, configuration: "{}", error: {}' - .format(configuration, ex)) + raise can.CanError( + 'CanalOpen() failed, configuration: "{}", error: {}'.format( + configuration, ex + ) + ) else: # any greater-than-zero return value indicates a success # (see https://grodansparadis.gitbooks.io/the-vscp-daemon/canal_interface_specification.html) # raise an error if the return code is <= 0 if result <= 0: - raise can.CanError('CanalOpen() failed, configuration: "{}", return code: {}' - .format(configuration, result)) + raise can.CanError( + 'CanalOpen() failed, configuration: "{}", return code: {}'.format( + configuration, result + ) + ) else: return result @@ -111,7 +123,7 @@ def close(self, handle): res = self.__m_dllBasic.CanalClose(handle) return res except: - log.warning('Failed to close') + log.warning("Failed to close") raise def send(self, handle, msg): @@ -119,7 +131,7 @@ def send(self, handle, msg): res = self.__m_dllBasic.CanalSend(handle, msg) return res except: - log.warning('Sending error') + log.warning("Sending error") raise can.CanError("Failed to transmit frame") def receive(self, handle, msg): @@ -127,7 +139,7 @@ def receive(self, handle, msg): res = self.__m_dllBasic.CanalReceive(handle, msg) return res except: - log.warning('Receive error') + log.warning("Receive error") raise def blocking_send(self, handle, msg, timeout): @@ -135,7 +147,7 @@ def blocking_send(self, handle, msg, timeout): res = self.__m_dllBasic.CanalBlockingSend(handle, msg, timeout) return res except: - log.warning('Blocking send error') + log.warning("Blocking send error") raise def blocking_receive(self, handle, msg, timeout): @@ -143,7 +155,7 @@ def blocking_receive(self, handle, msg, timeout): res = self.__m_dllBasic.CanalBlockingReceive(handle, msg, timeout) return res except: - log.warning('Blocking Receive Failed') + log.warning("Blocking Receive Failed") raise def get_status(self, handle, status): @@ -151,7 +163,7 @@ def get_status(self, handle, status): res = self.__m_dllBasic.CanalGetStatus(handle, status) return res except: - log.warning('Get status failed') + log.warning("Get status failed") raise def get_statistics(self, handle, statistics): @@ -159,7 +171,7 @@ def get_statistics(self, handle, statistics): res = self.__m_dllBasic.CanalGetStatistics(handle, statistics) return res except: - log.warning('Get Statistics failed') + log.warning("Get Statistics failed") raise def get_version(self): @@ -167,7 +179,7 @@ def get_version(self): res = self.__m_dllBasic.CanalGetVersion() return res except: - log.warning('Failed to get version info') + log.warning("Failed to get version info") raise def get_library_version(self): @@ -175,7 +187,7 @@ def get_library_version(self): res = self.__m_dllBasic.CanalGetDllVersion() return res except: - log.warning('Failed to get DLL version') + log.warning("Failed to get DLL version") raise def get_vendor_string(self): @@ -183,5 +195,5 @@ def get_vendor_string(self): res = self.__m_dllBasic.CanalGetVendorString() return res except: - log.warning('Failed to get vendor string') + log.warning("Failed to get vendor string") raise diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 01ee1a0a8..a34ebe853 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -15,11 +15,13 @@ try: # Try builtin Python 3 Windows API from _winapi import WaitForSingleObject, INFINITE + HAS_EVENTS = True except ImportError: try: # Try pywin32 package from win32event import WaitForSingleObject, INFINITE + HAS_EVENTS = True except ImportError: # Use polling instead @@ -40,17 +42,32 @@ try: from . import vxlapi except Exception as exc: - LOG.warning('Could not import vxlapi: %s', exc) + LOG.warning("Could not import vxlapi: %s", exc) class VectorBus(BusABC): """The CAN Bus implemented for the Vector interface.""" - def __init__(self, channel, can_filters=None, poll_interval=0.01, - receive_own_messages=False, - bitrate=None, rx_queue_size=2**14, app_name="CANalyzer", - serial=None, fd=False, data_bitrate=None, sjwAbr=2, tseg1Abr=6, - tseg2Abr=3, sjwDbr=2, tseg1Dbr=6, tseg2Dbr=3, **kwargs): + def __init__( + self, + channel, + can_filters=None, + poll_interval=0.01, + receive_own_messages=False, + bitrate=None, + rx_queue_size=2 ** 14, + app_name="CANalyzer", + serial=None, + fd=False, + data_bitrate=None, + sjwAbr=2, + tseg1Abr=6, + tseg2Abr=3, + sjwDbr=2, + tseg1Dbr=6, + tseg2Dbr=3, + **kwargs + ): """ :param list channel: The channel indexes to create this bus with. @@ -85,10 +102,12 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.channels = [channel] else: # Assume comma separated string of channels - self.channels = [int(ch.strip()) for ch in channel.split(',')] - self._app_name = app_name.encode() if app_name is not None else '' - self.channel_info = 'Application %s: %s' % ( - app_name, ', '.join('CAN %d' % (ch + 1) for ch in self.channels)) + self.channels = [int(ch.strip()) for ch in channel.split(",")] + self._app_name = app_name.encode() if app_name is not None else "" + self.channel_info = "Application %s: %s" % ( + app_name, + ", ".join("CAN %d" % (ch + 1) for ch in self.channels), + ) if serial is not None: app_name = None @@ -100,11 +119,15 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, channel_index.append(channel_config.channelIndex) if channel_index: if len(channel_index) != len(self.channels): - LOG.info("At least one defined channel wasn't found on the specified hardware.") + LOG.info( + "At least one defined channel wasn't found on the specified hardware." + ) self.channels = channel_index else: # Is there any better way to raise the error? - raise Exception("None of the configured channels could be found on the specified hardware.") + raise Exception( + "None of the configured channels could be found on the specified hardware." + ) vxlapi.xlOpenDriver() self.port_handle = vxlapi.XLportHandle(vxlapi.XL_INVALID_PORTHANDLE) @@ -120,19 +143,28 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, hw_type = ctypes.c_uint(0) hw_index = ctypes.c_uint(0) hw_channel = ctypes.c_uint(0) - vxlapi.xlGetApplConfig(self._app_name, channel, hw_type, hw_index, - hw_channel, vxlapi.XL_BUS_TYPE_CAN) - LOG.debug('Channel index %d found', channel) - idx = vxlapi.xlGetChannelIndex(hw_type.value, hw_index.value, - hw_channel.value) + vxlapi.xlGetApplConfig( + self._app_name, + channel, + hw_type, + hw_index, + hw_channel, + vxlapi.XL_BUS_TYPE_CAN, + ) + LOG.debug("Channel index %d found", channel) + idx = vxlapi.xlGetChannelIndex( + hw_type.value, hw_index.value, hw_channel.value + ) if idx < 0: # Undocumented behavior! See issue #353. # If hardware is unavailable, this function returns -1. # Raise an exception as if the driver # would have signalled XL_ERR_HW_NOT_PRESENT. - raise VectorError(vxlapi.XL_ERR_HW_NOT_PRESENT, - "XL_ERR_HW_NOT_PRESENT", - "xlGetChannelIndex") + raise VectorError( + vxlapi.XL_ERR_HW_NOT_PRESENT, + "XL_ERR_HW_NOT_PRESENT", + "xlGetChannelIndex", + ) else: # Channel already given as global channel idx = channel @@ -146,16 +178,30 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, if bitrate or fd: permission_mask.value = self.mask if fd: - vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, - permission_mask, rx_queue_size, - vxlapi.XL_INTERFACE_VERSION_V4, vxlapi.XL_BUS_TYPE_CAN) + vxlapi.xlOpenPort( + self.port_handle, + self._app_name, + self.mask, + permission_mask, + rx_queue_size, + vxlapi.XL_INTERFACE_VERSION_V4, + vxlapi.XL_BUS_TYPE_CAN, + ) else: - vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, - permission_mask, rx_queue_size, - vxlapi.XL_INTERFACE_VERSION, vxlapi.XL_BUS_TYPE_CAN) + vxlapi.xlOpenPort( + self.port_handle, + self._app_name, + self.mask, + permission_mask, + rx_queue_size, + vxlapi.XL_INTERFACE_VERSION, + vxlapi.XL_BUS_TYPE_CAN, + ) LOG.debug( - 'Open Port: PortHandle: %d, PermissionMask: 0x%X', - self.port_handle.value, permission_mask.value) + "Open Port: PortHandle: %d, PermissionMask: 0x%X", + self.port_handle.value, + permission_mask.value, + ) if permission_mask.value == self.mask: if fd: @@ -175,16 +221,34 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.canFdConf.tseg1Dbr = ctypes.c_uint(tseg1Dbr) self.canFdConf.tseg2Dbr = ctypes.c_uint(tseg2Dbr) - vxlapi.xlCanFdSetConfiguration(self.port_handle, self.mask, self.canFdConf) - LOG.info('SetFdConfig.: ABaudr.=%u, DBaudr.=%u', self.canFdConf.arbitrationBitRate, self.canFdConf.dataBitRate) - LOG.info('SetFdConfig.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u', self.canFdConf.sjwAbr, self.canFdConf.tseg1Abr, self.canFdConf.tseg2Abr) - LOG.info('SetFdConfig.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u', self.canFdConf.sjwDbr, self.canFdConf.tseg1Dbr, self.canFdConf.tseg2Dbr) + vxlapi.xlCanFdSetConfiguration( + self.port_handle, self.mask, self.canFdConf + ) + LOG.info( + "SetFdConfig.: ABaudr.=%u, DBaudr.=%u", + self.canFdConf.arbitrationBitRate, + self.canFdConf.dataBitRate, + ) + LOG.info( + "SetFdConfig.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", + self.canFdConf.sjwAbr, + self.canFdConf.tseg1Abr, + self.canFdConf.tseg2Abr, + ) + LOG.info( + "SetFdConfig.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u", + self.canFdConf.sjwDbr, + self.canFdConf.tseg1Dbr, + self.canFdConf.tseg2Dbr, + ) else: if bitrate: - vxlapi.xlCanSetChannelBitrate(self.port_handle, permission_mask, bitrate) - LOG.info('SetChannelBitrate: baudr.=%u',bitrate) + vxlapi.xlCanSetChannelBitrate( + self.port_handle, permission_mask, bitrate + ) + LOG.info("SetChannelBitrate: baudr.=%u", bitrate) else: - LOG.info('No init access!') + LOG.info("No init access!") # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 @@ -194,11 +258,12 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.event_handle = vxlapi.XLhandle() vxlapi.xlSetNotification(self.port_handle, self.event_handle, 1) else: - LOG.info('Install pywin32 to avoid polling') + LOG.info("Install pywin32 to avoid polling") try: - vxlapi.xlActivateChannel(self.port_handle, self.mask, - vxlapi.XL_BUS_TYPE_CAN, 0) + vxlapi.xlActivateChannel( + self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0 + ) except VectorError: self.shutdown() raise @@ -214,13 +279,21 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, def _apply_filters(self, filters): if filters: # Only up to one filter per ID type allowed - if len(filters) == 1 or (len(filters) == 2 and - filters[0].get("extended") != filters[1].get("extended")): + if len(filters) == 1 or ( + len(filters) == 2 + and filters[0].get("extended") != filters[1].get("extended") + ): try: for can_filter in filters: - vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, - can_filter["can_id"], can_filter["can_mask"], - vxlapi.XL_CAN_EXT if can_filter.get("extended") else vxlapi.XL_CAN_STD) + vxlapi.xlCanSetChannelAcceptance( + self.port_handle, + self.mask, + can_filter["can_id"], + can_filter["can_mask"], + vxlapi.XL_CAN_EXT + if can_filter.get("extended") + else vxlapi.XL_CAN_STD, + ) except VectorError as exc: LOG.warning("Could not set filters: %s", exc) # go to fallback @@ -234,8 +307,12 @@ 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) - vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_STD) + vxlapi.xlCanSetChannelAcceptance( + self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_EXT + ) + vxlapi.xlCanSetChannelAcceptance( + self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_STD + ) except VectorError as exc: LOG.warning("Could not reset filters: %s", exc) @@ -256,7 +333,10 @@ def _recv_internal(self, timeout): if exc.error_code != vxlapi.XL_ERR_QUEUE_IS_EMPTY: raise else: - if event.tag == vxlapi.XL_CAN_EV_TAG_RX_OK or event.tag == vxlapi.XL_CAN_EV_TAG_TX_OK: + if ( + event.tag == vxlapi.XL_CAN_EV_TAG_RX_OK + or event.tag == vxlapi.XL_CAN_EV_TAG_TX_OK + ): msg_id = event.tagData.canRxOkMsg.canId dlc = dlc2len(event.tagData.canRxOkMsg.dlc) flags = event.tagData.canRxOkMsg.msgFlags @@ -269,11 +349,14 @@ def _recv_internal(self, timeout): 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), - error_state_indicator=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_ESI), + error_state_indicator=bool( + flags & vxlapi.XL_CAN_RXMSG_FLAG_ESI + ), bitrate_switch=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_BRS), dlc=dlc, data=event.tagData.canRxOkMsg.data[:dlc], - channel=channel) + channel=channel, + ) return msg, self._is_filtered else: event_count.value = 1 @@ -293,12 +376,17 @@ def _recv_internal(self, timeout): 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_MSG_FLAG_REMOTE_FRAME), - is_error_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_ERROR_FRAME), + is_remote_frame=bool( + flags & vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME + ), + is_error_frame=bool( + flags & vxlapi.XL_CAN_MSG_FLAG_ERROR_FRAME + ), is_fd=False, dlc=dlc, data=event.tagData.msg.data[:dlc], - channel=channel) + channel=channel, + ) return msg, self._is_filtered if end_time is not None and time.time() > end_time: @@ -341,14 +429,16 @@ def send(self, msg, timeout=None): XLcanTxEvent = vxlapi.XLcanTxEvent() XLcanTxEvent.tag = vxlapi.XL_CAN_EV_TAG_TX_MSG - XLcanTxEvent.transId = 0xffff + XLcanTxEvent.transId = 0xFFFF XLcanTxEvent.tagData.canMsg.canId = msg_id XLcanTxEvent.tagData.canMsg.msgFlags = flags XLcanTxEvent.tagData.canMsg.dlc = len2dlc(msg.dlc) for idx, value in enumerate(msg.data): XLcanTxEvent.tagData.canMsg.data[idx] = value - vxlapi.xlCanTransmitEx(self.port_handle, mask, message_count, MsgCntSent, XLcanTxEvent) + vxlapi.xlCanTransmitEx( + self.port_handle, mask, message_count, MsgCntSent, XLcanTxEvent + ) else: if msg.is_remote_frame: @@ -376,23 +466,29 @@ def shutdown(self): def reset(self): vxlapi.xlDeactivateChannel(self.port_handle, self.mask) - vxlapi.xlActivateChannel(self.port_handle, self.mask, - vxlapi.XL_BUS_TYPE_CAN, 0) + vxlapi.xlActivateChannel(self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0) @staticmethod def _detect_available_configs(): configs = [] channel_configs = get_channel_configs() - LOG.info('Found %d channels', len(channel_configs)) + LOG.info("Found %d channels", len(channel_configs)) for channel_config in channel_configs: - LOG.info('Channel index %d: %s', - channel_config.channelIndex, - channel_config.name.decode('ascii')) - configs.append({'interface': 'vector', - 'app_name': None, - 'channel': channel_config.channelIndex}) + LOG.info( + "Channel index %d: %s", + channel_config.channelIndex, + channel_config.name.decode("ascii"), + ) + configs.append( + { + "interface": "vector", + "app_name": None, + "channel": channel_config.channelIndex, + } + ) return configs + def get_channel_configs(): if vxlapi is None: return [] diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index b88f6c527..8e4de1aff 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -7,7 +7,6 @@ class VectorError(CanError): - def __init__(self, error_code, error_string, function): self.error_code = error_code super().__init__(f"{function} failed ({error_string})") diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index e1b93bdc7..5669490ac 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -20,7 +20,7 @@ # Vector XL API Definitions # ========================= # Load Windows DLL -DLL_NAME = 'vxlapi64' if platform.architecture()[0] == '64bit' else 'vxlapi' +DLL_NAME = "vxlapi64" if platform.architecture()[0] == "64bit" else "vxlapi" _xlapi_dll = ctypes.windll.LoadLibrary(DLL_NAME) XL_BUS_TYPE_CAN = 0x00000001 @@ -67,137 +67,207 @@ # 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)] - + _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)] + _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)] + _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)] + _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)] + _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)] + _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)] + _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)] + _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)] + _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)] + _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)] + _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)] + _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)] + _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)] + _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) + ("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) + ("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) +XL_INVALID_PORTHANDLE = -1 XLportHandle = ctypes.c_long @@ -224,8 +294,12 @@ def check_status(result, function, arguments): 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 + 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 @@ -240,8 +314,13 @@ def check_status(result, function, arguments): 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 + 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 @@ -257,22 +336,17 @@ def check_status(result, function, arguments): xlClosePort.errcheck = check_status xlSetNotification = _xlapi_dll.xlSetNotification -xlSetNotification.argtypes = [XLportHandle, ctypes.POINTER(XLhandle), - ctypes.c_int] +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.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.argtypes = [XLportHandle, XLaccess, ctypes.c_uint, ctypes.c_uint] xlActivateChannel.restype = XLstatus xlActivateChannel.errcheck = check_status @@ -288,15 +362,15 @@ def check_status(result, function, arguments): xlReceive = _xlapi_dll.xlReceive xlReceive.argtypes = [ - XLportHandle, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(XLevent) + 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.argtypes = [XLportHandle, ctypes.POINTER(XLcanRxEvent)] xlCanReceive.restype = XLstatus xlCanReceive.errcheck = check_status @@ -311,14 +385,21 @@ def check_status(result, function, arguments): xlCanTransmit = _xlapi_dll.xlCanTransmit xlCanTransmit.argtypes = [ - XLportHandle, XLaccess, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(XLevent) + 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) + XLportHandle, + XLaccess, + ctypes.c_uint, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(XLcanTxEvent), ] xlCanTransmitEx.restype = XLstatus xlCanTransmitEx.errcheck = check_status @@ -330,7 +411,12 @@ def check_status(result, function, arguments): xlCanSetChannelAcceptance = _xlapi_dll.xlCanSetChannelAcceptance xlCanSetChannelAcceptance.argtypes = [ - XLportHandle, XLaccess, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_uint] + XLportHandle, + XLaccess, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_uint, +] xlCanSetChannelAcceptance.restype = XLstatus xlCanSetChannelAcceptance.errcheck = check_status diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 66b951c22..b153150ee 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -44,8 +44,12 @@ class VirtualBus(BusABC): if a message is sent to 5 receivers with the timeout set to 1.0. """ - def __init__(self, channel=None, receive_own_messages=False, rx_queue_size=0, **kwargs): - super().__init__(channel=channel, receive_own_messages=receive_own_messages, **kwargs) + def __init__( + self, channel=None, receive_own_messages=False, rx_queue_size=0, **kwargs + ): + super().__init__( + channel=channel, receive_own_messages=receive_own_messages, **kwargs + ) # the channel identifier may be an arbitrary object self.channel_id = channel @@ -133,6 +137,6 @@ def _detect_available_configs(): available_channels += [extra] return [ - {'interface': 'virtual', 'channel': channel} + {"interface": "virtual", "channel": channel} for channel in available_channels ] diff --git a/can/io/asc.py b/can/io/asc.py index 419b46247..2e25e5c2c 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -21,7 +21,7 @@ CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF -logger = logging.getLogger('can.io.asc') +logger = logging.getLogger("can.io.asc") class ASCReader(BaseIOHandler): @@ -37,11 +37,11 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super().__init__(file, mode='r') + super().__init__(file, mode="r") @staticmethod def _extract_can_id(str_can_id): - if str_can_id[-1:].lower() == 'x': + if str_can_id[-1:].lower() == "x": is_extended = True can_id = int(str_can_id[0:-1], 16) else: @@ -51,14 +51,16 @@ def _extract_can_id(str_can_id): def __iter__(self): for line in self.file: - #logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0]) + # logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0]) temp = line.strip() if not temp or not temp[0].isdigit(): continue try: - timestamp, channel, dummy = temp.split(None, 2) # , frameType, dlc, frameData + timestamp, channel, dummy = temp.split( + None, 2 + ) # , frameType, dlc, frameData except ValueError: # we parsed an empty comment continue @@ -70,22 +72,26 @@ def __iter__(self): except ValueError: pass - if dummy.strip()[0:10].lower() == 'errorframe': - msg = Message(timestamp=timestamp, is_error_frame=True, - channel=channel) + 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:': + elif ( + not isinstance(channel, int) + or dummy.strip()[0:10].lower() == "statistic:" + ): pass - elif dummy[-1:].lower() == 'r': + 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) - msg = Message(timestamp=timestamp, - arbitration_id=can_id_num & CAN_ID_MASK, - is_extended_id=is_extended_id, - is_remote_frame=True, - channel=channel) + msg = Message( + timestamp=timestamp, + arbitration_id=can_id_num & CAN_ID_MASK, + is_extended_id=is_extended_id, + is_remote_frame=True, + channel=channel, + ) yield msg else: @@ -94,9 +100,9 @@ def __iter__(self): can_id_str, _, _, dlc, data = dummy.split(None, 4) except ValueError: # but if not, we only want to get the stuff up to the dlc - can_id_str, _, _, dlc = dummy.split(None, 3) + can_id_str, _, _, dlc = dummy.split(None, 3) # and we set data to an empty sequence manually - data = '' + data = "" dlc = int(dlc) frame = bytearray() @@ -113,7 +119,7 @@ def __iter__(self): is_remote_frame=False, dlc=dlc, data=frame, - channel=channel + channel=channel, ) self.stop() @@ -140,7 +146,7 @@ def __init__(self, file, channel=1): :param channel: a default channel to use when the message does not have a channel set """ - super().__init__(file, mode='w') + super().__init__(file, mode="w") self.channel = channel # write start of file header @@ -166,19 +172,21 @@ def log_event(self, message, timestamp=None): :param float timestamp: the absolute timestamp of the event """ - if not message: # if empty or 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) + self.last_timestamp = timestamp or 0.0 self.started = self.last_timestamp - mlsec = repr(self.last_timestamp).split('.')[1][:3] - formatted_date = time.strftime(self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp)) + mlsec = repr(self.last_timestamp).split(".")[1][:3] + formatted_date = time.strftime( + self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp) + ) 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! + self.log_event("Start of measurement") # caution: this is a recursive call! # Use last known timestamp if unknown if timestamp is None: @@ -198,7 +206,7 @@ def on_message_received(self, msg): return if msg.is_remote_frame: - dtype = 'r' + dtype = "r" data = [] else: dtype = "d {}".format(msg.dlc) @@ -206,7 +214,7 @@ def on_message_received(self, msg): arb_id = "{:X}".format(msg.arbitration_id) if msg.is_extended_id: - arb_id += 'x' + arb_id += "x" channel = channel2int(msg.channel) if channel is None: @@ -215,9 +223,8 @@ def on_message_received(self, msg): # 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)) + 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/blf.py b/can/io/blf.py index ad895297e..06afd6552 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -112,15 +112,29 @@ def timestamp_to_systemtime(timestamp): # Probably not a Unix timestamp return (0, 0, 0, 0, 0, 0, 0, 0) t = datetime.datetime.fromtimestamp(timestamp) - return (t.year, t.month, t.isoweekday() % 7, t.day, - t.hour, t.minute, t.second, int(round(t.microsecond / 1000.0))) + return ( + t.year, + t.month, + t.isoweekday() % 7, + t.day, + t.hour, + t.minute, + t.second, + int(round(t.microsecond / 1000.0)), + ) def systemtime_to_timestamp(systemtime): try: t = datetime.datetime( - systemtime[0], systemtime[1], systemtime[3], - systemtime[4], systemtime[5], systemtime[6], systemtime[7] * 1000) + systemtime[0], + systemtime[1], + systemtime[3], + systemtime[4], + systemtime[5], + systemtime[6], + systemtime[7] * 1000, + ) return time.mktime(t.timetuple()) + systemtime[7] / 1000.0 except ValueError: return 0 @@ -140,7 +154,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in binary read mode, not text read mode. """ - super().__init__(file, mode='rb') + super().__init__(file, mode="rb") data = self.file.read(FILE_HEADER_STRUCT.size) header = FILE_HEADER_STRUCT.unpack(data) if header[0] != b"LOGG": @@ -171,9 +185,8 @@ def __iter__(self): self.file.read(obj_data_size % 4) if obj_type == LOG_CONTAINER: - method, uncompressed_size = LOG_CONTAINER_STRUCT.unpack_from( - obj_data) - container_data = obj_data[LOG_CONTAINER_STRUCT.size:] + method, uncompressed_size = LOG_CONTAINER_STRUCT.unpack_from(obj_data) + container_data = obj_data[LOG_CONTAINER_STRUCT.size :] if method == NO_COMPRESSION: data = container_data elif method == ZLIB_DEFLATE: @@ -188,7 +201,7 @@ def __iter__(self): pos = 0 while pos + OBJ_HEADER_BASE_STRUCT.size < len(data): header = OBJ_HEADER_BASE_STRUCT.unpack_from(data, pos) - #print(header) + # print(header) if header[0] != b"LOBJ": raise BLFParseError() @@ -203,14 +216,20 @@ def __iter__(self): # Read rest of header header_version = header[2] if header_version == 1: - flags, _, _, timestamp = OBJ_HEADER_V1_STRUCT.unpack_from(data, pos) + flags, _, _, timestamp = OBJ_HEADER_V1_STRUCT.unpack_from( + data, pos + ) pos += OBJ_HEADER_V1_STRUCT.size elif header_version == 2: - flags, _, _, timestamp, _ = OBJ_HEADER_V2_STRUCT.unpack_from(data, pos) + flags, _, _, timestamp, _ = OBJ_HEADER_V2_STRUCT.unpack_from( + data, pos + ) pos += OBJ_HEADER_V2_STRUCT.size else: # Unknown header version - LOG.warning("Unknown object header version (%d)", header_version) + LOG.warning( + "Unknown object header version (%d)", header_version + ) pos = next_pos continue @@ -223,40 +242,62 @@ def __iter__(self): obj_type = header[4] # Both CAN message types have the same starting content if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): - (channel, flags, dlc, can_id, - can_data) = CAN_MSG_STRUCT.unpack_from(data, pos) - msg = Message(timestamp=timestamp, - arbitration_id=can_id & 0x1FFFFFFF, - is_extended_id=bool(can_id & CAN_MSG_EXT), - is_remote_frame=bool(flags & REMOTE_FLAG), - dlc=dlc, - data=can_data[:dlc], - channel=channel - 1) + ( + channel, + flags, + dlc, + can_id, + can_data, + ) = CAN_MSG_STRUCT.unpack_from(data, pos) + msg = Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=bool(can_id & CAN_MSG_EXT), + is_remote_frame=bool(flags & REMOTE_FLAG), + dlc=dlc, + data=can_data[:dlc], + channel=channel - 1, + ) yield msg elif obj_type == CAN_FD_MESSAGE: - (channel, flags, dlc, can_id, _, _, fd_flags, - _, can_data) = CAN_FD_MSG_STRUCT.unpack_from(data, pos) + ( + channel, + flags, + dlc, + can_id, + _, + _, + fd_flags, + _, + can_data, + ) = CAN_FD_MSG_STRUCT.unpack_from(data, pos) length = dlc2len(dlc) - msg = Message(timestamp=timestamp, - arbitration_id=can_id & 0x1FFFFFFF, - is_extended_id=bool(can_id & CAN_MSG_EXT), - is_remote_frame=bool(flags & REMOTE_FLAG), - is_fd=bool(fd_flags & EDL), - bitrate_switch=bool(fd_flags & BRS), - error_state_indicator=bool(fd_flags & ESI), - dlc=length, - data=can_data[:length], - channel=channel - 1) + msg = Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=bool(can_id & CAN_MSG_EXT), + is_remote_frame=bool(flags & REMOTE_FLAG), + is_fd=bool(fd_flags & EDL), + bitrate_switch=bool(fd_flags & BRS), + error_state_indicator=bool(fd_flags & ESI), + dlc=length, + data=can_data[:length], + channel=channel - 1, + ) yield msg elif obj_type == CAN_FD_MESSAGE_64: ( - channel, dlc, _, _, can_id, _, fd_flags - ) = CAN_FD_MSG_64_STRUCT.unpack_from(data, pos)[:7] + channel, + dlc, + _, + _, + can_id, + _, + fd_flags, + ) = CAN_FD_MSG_64_STRUCT.unpack_from(data, pos)[:7] length = dlc2len(dlc) can_data = struct.unpack_from( - "<{}s".format(length), - data, - pos + CAN_FD_MSG_64_STRUCT.size + "<{}s".format(length), data, pos + CAN_FD_MSG_64_STRUCT.size )[0] msg = Message( timestamp=timestamp, @@ -268,19 +309,31 @@ def __iter__(self): error_state_indicator=bool(fd_flags & ESI_64), dlc=length, data=can_data[:length], - channel=channel - 1 + channel=channel - 1, ) yield msg elif obj_type == CAN_ERROR_EXT: - (channel, _, _, _, _, dlc, _, can_id, _, - can_data) = CAN_ERROR_EXT_STRUCT.unpack_from(data, pos) - msg = Message(timestamp=timestamp, - is_error_frame=True, - is_extended_id=bool(can_id & CAN_MSG_EXT), - arbitration_id=can_id & 0x1FFFFFFF, - dlc=dlc, - data=can_data[:dlc], - channel=channel - 1) + ( + channel, + _, + _, + _, + _, + dlc, + _, + can_id, + _, + can_data, + ) = CAN_ERROR_EXT_STRUCT.unpack_from(data, pos) + msg = Message( + timestamp=timestamp, + is_error_frame=True, + is_extended_id=bool(can_id & CAN_MSG_EXT), + arbitration_id=can_id & 0x1FFFFFFF, + dlc=dlc, + data=can_data[:dlc], + channel=channel - 1, + ) yield msg # else: # LOG.warning("Unknown object type (%d)", obj_type) @@ -310,7 +363,7 @@ def __init__(self, file, channel=1): If this is a file-like object, is has to opened in binary write mode, not text write mode. """ - super().__init__(file, mode='wb') + super().__init__(file, mode="wb") self.channel = channel # Header will be written after log is done self.file.write(b"\x00" * FILE_HEADER_SIZE) @@ -336,16 +389,18 @@ def on_message_received(self, msg): data = bytes(msg.data) if msg.is_error_frame: - data = CAN_ERROR_EXT_STRUCT.pack(channel, - 0, # length - 0, # flags - 0, # ecc - 0, # position - len2dlc(msg.dlc), - 0, # frame length - arb_id, - 0, # ext flags - data) + data = CAN_ERROR_EXT_STRUCT.pack( + channel, + 0, # length + 0, # flags + 0, # ecc + 0, # position + len2dlc(msg.dlc), + 0, # frame length + arb_id, + 0, # ext flags + data, + ) self._add_object(CAN_ERROR_EXT, data, msg.timestamp) elif msg.is_fd: fd_flags = EDL @@ -353,8 +408,9 @@ def on_message_received(self, msg): fd_flags |= BRS if msg.error_state_indicator: fd_flags |= ESI - data = CAN_FD_MSG_STRUCT.pack(channel, flags, len2dlc(msg.dlc), - arb_id, 0, 0, fd_flags, msg.dlc, data) + data = CAN_FD_MSG_STRUCT.pack( + channel, flags, len2dlc(msg.dlc), arb_id, 0, 0, fd_flags, msg.dlc, data + ) self._add_object(CAN_FD_MESSAGE, data, msg.timestamp) else: data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, data) @@ -377,7 +433,8 @@ def log_event(self, text, timestamp=None): comment = b"Added by python-can" marker = b"python-can" data = GLOBAL_MARKER_STRUCT.pack( - 0, 0xFFFFFF, 0xFF3300, 0, len(text), len(marker), len(comment)) + 0, 0xFFFFFF, 0xFF3300, 0, len(text), len(marker), len(comment) + ) self._add_object(GLOBAL_MARKER, data + text + marker + comment, timestamp) def _add_object(self, obj_type, data, timestamp=None): @@ -390,7 +447,8 @@ def _add_object(self, obj_type, data, timestamp=None): header_size = OBJ_HEADER_BASE_STRUCT.size + OBJ_HEADER_V1_STRUCT.size obj_size = header_size + len(data) base_header = OBJ_HEADER_BASE_STRUCT.pack( - b"LOBJ", header_size, 1, obj_size, obj_type) + b"LOBJ", header_size, 1, obj_size, obj_type + ) obj_header = OBJ_HEADER_V1_STRUCT.pack(TIME_ONE_NANS, 0, 0, max(timestamp, 0)) self.cache.append(base_header) @@ -413,19 +471,21 @@ def _flush(self): if not cache: # Nothing to write return - uncompressed_data = cache[:self.MAX_CACHE_SIZE] + uncompressed_data = cache[: self.MAX_CACHE_SIZE] # Save data that comes after max size to next round - tail = cache[self.MAX_CACHE_SIZE:] + tail = cache[self.MAX_CACHE_SIZE :] self.cache = [tail] self.cache_size = len(tail) - compressed_data = zlib.compress(uncompressed_data, - self.COMPRESSION_LEVEL) - obj_size = (OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size + - len(compressed_data)) + compressed_data = zlib.compress(uncompressed_data, self.COMPRESSION_LEVEL) + obj_size = ( + OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size + len(compressed_data) + ) base_header = OBJ_HEADER_BASE_STRUCT.pack( - b"LOBJ", OBJ_HEADER_BASE_STRUCT.size, 1, obj_size, LOG_CONTAINER) + b"LOBJ", OBJ_HEADER_BASE_STRUCT.size, 1, obj_size, LOG_CONTAINER + ) container_header = LOG_CONTAINER_STRUCT.pack( - ZLIB_DEFLATE, len(uncompressed_data)) + ZLIB_DEFLATE, len(uncompressed_data) + ) self.file.write(base_header) self.file.write(container_header) self.file.write(compressed_data) @@ -441,11 +501,9 @@ def stop(self): super().stop() # Write header in the beginning of the file - header = [b"LOGG", FILE_HEADER_SIZE, - APPLICATION_ID, 0, 0, 0, 2, 6, 8, 1] + header = [b"LOGG", FILE_HEADER_SIZE, APPLICATION_ID, 0, 0, 0, 2, 6, 8, 1] # The meaning of "count of objects read" is unknown - header.extend([filesize, self.uncompressed_size, - self.count_of_objects, 0]) + header.extend([filesize, self.uncompressed_size, self.count_of_objects, 0]) header.extend(timestamp_to_systemtime(self.start_timestamp)) header.extend(timestamp_to_systemtime(self.stop_timestamp)) with open(self.file.name, "r+b") as f: diff --git a/can/io/canutils.py b/can/io/canutils.py index bea1696a1..a151b56e1 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -13,12 +13,12 @@ from .generic import BaseIOHandler -log = logging.getLogger('can.io.canutils') +log = logging.getLogger("can.io.canutils") -CAN_MSG_EXT = 0x80000000 -CAN_ERR_FLAG = 0x20000000 -CAN_ERR_BUSERROR = 0x00000080 -CAN_ERR_DLC = 8 +CAN_MSG_EXT = 0x80000000 +CAN_ERR_FLAG = 0x20000000 +CAN_ERR_BUSERROR = 0x00000080 +CAN_ERR_DLC = 8 class CanutilsLogReader(BaseIOHandler): @@ -37,7 +37,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super().__init__(file, mode='r') + super().__init__(file, mode="r") def __iter__(self): for line in self.file: @@ -49,14 +49,14 @@ def __iter__(self): timestamp, channel, frame = temp.split() timestamp = float(timestamp[1:-1]) - canId, data = frame.split('#') + canId, data = frame.split("#") if channel.isdigit(): channel = int(channel) isExtended = len(canId) > 3 canId = int(canId, 16) - if data and data[0].lower() == 'r': + if data and data[0].lower() == "r": isRemoteFrame = True if len(data) > 1: dlc = int(data[1:]) @@ -68,14 +68,20 @@ def __iter__(self): dlc = len(data) // 2 dataBin = bytearray() for i in range(0, len(data), 2): - dataBin.append(int(data[i:(i + 2)], 16)) + dataBin.append(int(data[i : (i + 2)], 16)) if canId & CAN_ERR_FLAG and canId & CAN_ERR_BUSERROR: msg = Message(timestamp=timestamp, is_error_frame=True) else: - msg = Message(timestamp=timestamp, arbitration_id=canId & 0x1FFFFFFF, - is_extended_id=isExtended, is_remote_frame=isRemoteFrame, - dlc=dlc, data=dataBin, channel=channel) + msg = Message( + timestamp=timestamp, + arbitration_id=canId & 0x1FFFFFFF, + is_extended_id=isExtended, + is_remote_frame=isRemoteFrame, + dlc=dlc, + data=dataBin, + channel=channel, + ) yield msg self.stop() @@ -100,7 +106,7 @@ def __init__(self, file, channel="vcan0", append=False): :param bool append: if set to `True` messages are appended to the file, else the file is truncated """ - mode = 'a' if append else 'w' + mode = "a" if append else "w" super().__init__(file, mode=mode) self.channel = channel @@ -109,7 +115,7 @@ def __init__(self, file, channel="vcan0", append=False): def on_message_received(self, msg): # this is the case for the very first message: if self.last_timestamp is None: - self.last_timestamp = (msg.timestamp or 0.0) + self.last_timestamp = msg.timestamp or 0.0 # figure out the correct timestamp if msg.timestamp is None or msg.timestamp < self.last_timestamp: @@ -120,17 +126,30 @@ def on_message_received(self, msg): channel = msg.channel if msg.channel is not None else self.channel if msg.is_error_frame: - self.file.write("(%f) %s %08X#0000000000000000\n" % (timestamp, channel, CAN_ERR_FLAG | CAN_ERR_BUSERROR)) + self.file.write( + "(%f) %s %08X#0000000000000000\n" + % (timestamp, channel, CAN_ERR_FLAG | CAN_ERR_BUSERROR) + ) elif msg.is_remote_frame: if msg.is_extended_id: - self.file.write("(%f) %s %08X#R\n" % (timestamp, channel, msg.arbitration_id)) + self.file.write( + "(%f) %s %08X#R\n" % (timestamp, channel, msg.arbitration_id) + ) else: - self.file.write("(%f) %s %03X#R\n" % (timestamp, channel, msg.arbitration_id)) + self.file.write( + "(%f) %s %03X#R\n" % (timestamp, channel, msg.arbitration_id) + ) else: data = ["{:02X}".format(byte) for byte in msg.data] if msg.is_extended_id: - self.file.write("(%f) %s %08X#%s\n" % (timestamp, channel, msg.arbitration_id, ''.join(data))) + self.file.write( + "(%f) %s %08X#%s\n" + % (timestamp, channel, msg.arbitration_id, "".join(data)) + ) else: - self.file.write("(%f) %s %03X#%s\n" % (timestamp, channel, msg.arbitration_id, ''.join(data))) + self.file.write( + "(%f) %s %03X#%s\n" + % (timestamp, channel, msg.arbitration_id, "".join(data)) + ) diff --git a/can/io/csv.py b/can/io/csv.py index c755b1c5b..af8fee6d8 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -49,7 +49,7 @@ def __init__(self, file, append=False): the file is truncated and starts with a newly written header line """ - mode = 'a' if append else 'w' + mode = "a" if append else "w" super().__init__(file, mode=mode) # Write a header row @@ -57,17 +57,19 @@ def __init__(self, file, append=False): self.file.write("timestamp,arbitration_id,extended,remote,error,dlc,data\n") def on_message_received(self, msg): - row = ','.join([ - repr(msg.timestamp), # cannot use str() here because that is rounding - hex(msg.arbitration_id), - '1' if msg.is_extended_id else '0', - '1' if msg.is_remote_frame else '0', - '1' if msg.is_error_frame else '0', - str(msg.dlc), - b64encode(msg.data).decode('utf8') - ]) + row = ",".join( + [ + repr(msg.timestamp), # cannot use str() here because that is rounding + hex(msg.arbitration_id), + "1" if msg.is_extended_id else "0", + "1" if msg.is_remote_frame else "0", + "1" if msg.is_error_frame else "0", + str(msg.dlc), + b64encode(msg.data).decode("utf8"), + ] + ) self.file.write(row) - self.file.write('\n') + self.file.write("\n") class CSVReader(BaseIOHandler): @@ -85,7 +87,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super().__init__(file, mode='r') + super().__init__(file, mode="r") def __iter__(self): # skip the header line @@ -97,13 +99,15 @@ def __iter__(self): for line in self.file: - timestamp, arbitration_id, extended, remote, error, dlc, data = line.split(',') + timestamp, arbitration_id, extended, remote, error, dlc, data = line.split( + "," + ) yield Message( timestamp=float(timestamp), - is_remote_frame=(remote == '1'), - is_extended_id=(extended == '1'), - is_error_frame=(error == '1'), + is_remote_frame=(remote == "1"), + is_extended_id=(extended == "1"), + is_error_frame=(error == "1"), arbitration_id=int(arbitration_id, base=16), dlc=int(dlc), data=b64decode(data), diff --git a/can/io/generic.py b/can/io/generic.py index 56ce82860..62bae18d4 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -17,14 +17,14 @@ class BaseIOHandler(metaclass=ABCMeta): was opened """ - def __init__(self, file, mode='rt'): + def __init__(self, file, mode="rt"): """ :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` """ - if file is None or (hasattr(file, 'read') and hasattr(file, 'write')): + if file is None or (hasattr(file, "read") and hasattr(file, "write")): # file is None or some file-like object self.file = file else: diff --git a/can/io/logger.py b/can/io/logger.py index 2863b0ea5..edffe1c78 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -18,7 +18,7 @@ log = logging.getLogger("can.io.logger") -class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method +class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method """ Logs CAN messages to a file. diff --git a/can/io/player.py b/can/io/player.py index 3e856767b..3455fe97a 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -16,7 +16,7 @@ from .csv import CSVReader from .sqlite import SqliteReader -log = logging.getLogger('can.io.player') +log = logging.getLogger("can.io.player") class LogReader(BaseIOHandler): @@ -60,7 +60,9 @@ def __new__(cls, filename, *args, **kwargs): elif filename.endswith(".log"): return CanutilsLogReader(filename, *args, **kwargs) else: - raise NotImplementedError("No read support for this log format: {}".format(filename)) + raise NotImplementedError( + "No read support for this log format: {}".format(filename) + ) class MessageSync: diff --git a/can/io/printer.py b/can/io/printer.py index c67285581..f6a4b28e0 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -9,7 +9,7 @@ from can.listener import Listener from .generic import BaseIOHandler -log = logging.getLogger('can.io.printer') +log = logging.getLogger("can.io.printer") class Printer(BaseIOHandler, Listener): @@ -30,10 +30,10 @@ def __init__(self, file=None): write mode, not binary write mode. """ self.write_to_file = file is not None - super().__init__(file, mode='w') + super().__init__(file, mode="w") def on_message_received(self, msg): if self.write_to_file: - self.file.write(str(msg) + '\n') + self.file.write(str(msg) + "\n") else: print(msg) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index eccba8dc8..b9e5c2673 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -15,7 +15,7 @@ from can.message import Message from .generic import BaseIOHandler -log = logging.getLogger('can.io.sqlite') +log = logging.getLogger("can.io.sqlite") class SqliteReader(BaseIOHandler): @@ -48,7 +48,9 @@ def __init__(self, file, table_name="messages"): self.table_name = table_name def __iter__(self): - for frame_data in self._cursor.execute("SELECT * FROM {}".format(self.table_name)): + for frame_data in self._cursor.execute( + "SELECT * FROM {}".format(self.table_name) + ): yield SqliteReader._assemble_message(frame_data) @staticmethod @@ -61,7 +63,7 @@ def _assemble_message(frame_data): is_error_frame=bool(is_error), arbitration_id=can_id, dlc=dlc, - data=data + data=data, ) def __len__(self): @@ -74,7 +76,9 @@ def read_all(self): :rtype: Generator[can.Message] """ - result = self._cursor.execute("SELECT * FROM {}".format(self.table_name)).fetchall() + result = self._cursor.execute( + "SELECT * FROM {}".format(self.table_name) + ).fetchall() return (SqliteReader._assemble_message(frame) for frame in result) def stop(self): @@ -147,7 +151,9 @@ def __init__(self, file, table_name="messages"): self._writer_thread.start() self.num_frames = 0 self.last_write = time.time() - self._insert_template = f"INSERT INTO {self.table_name} VALUES (?, ?, ?, ?, ?, ?, ?)" + self._insert_template = ( + f"INSERT INTO {self.table_name} VALUES (?, ?, ?, ?, ?, ?, ?)" + ) def _create_db(self): """Creates a new databae or opens a connection to an existing one. @@ -160,7 +166,8 @@ def _create_db(self): self._conn = sqlite3.connect(self._db_filename) # create table structure - self._conn.cursor().execute(""" + self._conn.cursor().execute( + """ CREATE TABLE IF NOT EXISTS {} ( ts REAL, @@ -171,7 +178,10 @@ def _create_db(self): dlc INTEGER, data BLOB ) - """.format(self.table_name)) + """.format( + self.table_name + ) + ) self._conn.commit() def _db_writer_thread(self): @@ -179,24 +189,28 @@ def _db_writer_thread(self): try: while True: - messages = [] # reset buffer + messages = [] # reset buffer msg = self.get_message(self.GET_MESSAGE_TIMEOUT) while msg is not None: - #log.debug("SqliteWriter: buffering message") - - messages.append(( - msg.timestamp, - msg.arbitration_id, - msg.is_extended_id, - msg.is_remote_frame, - msg.is_error_frame, - msg.dlc, - memoryview(msg.data) - )) - - if time.time() - self.last_write > self.MAX_TIME_BETWEEN_WRITES or \ - len(messages) > self.MAX_BUFFER_SIZE_BEFORE_WRITES: + # log.debug("SqliteWriter: buffering message") + + messages.append( + ( + msg.timestamp, + msg.arbitration_id, + msg.is_extended_id, + msg.is_remote_frame, + msg.is_error_frame, + msg.dlc, + memoryview(msg.data), + ) + ) + + if ( + time.time() - self.last_write > self.MAX_TIME_BETWEEN_WRITES + or len(messages) > self.MAX_BUFFER_SIZE_BEFORE_WRITES + ): break else: # just go on @@ -205,9 +219,9 @@ def _db_writer_thread(self): count = len(messages) if count > 0: with self._conn: - #log.debug("Writing %d frames to db", count) + # log.debug("Writing %d frames to db", count) self._conn.executemany(self._insert_template, messages) - self._conn.commit() # make the changes visible to the entire database + self._conn.commit() # make the changes visible to the entire database self.num_frames += count self.last_write = time.time() diff --git a/can/logger.py b/can/logger.py index ec49c7e2c..9b326946c 100644 --- a/can/logger.py +++ b/can/logger.py @@ -28,62 +28,93 @@ def main(): parser = argparse.ArgumentParser( "python -m can.logger", - description="Log CAN traffic, printing messages to stdout or to a given file.") - - parser.add_argument("-f", "--file_name", dest="log_file", - help="""Path and base log filename, for supported types see can.Logger.""", - default=None) - - parser.add_argument("-v", action="count", dest="verbosity", - help='''How much information do you want to see at the command line? - You can add several of these e.g., -vv is DEBUG''', default=2) - - parser.add_argument('-c', '--channel', help='''Most backend interfaces require some sort of channel. + description="Log CAN traffic, printing messages to stdout or to a given file.", + ) + + parser.add_argument( + "-f", + "--file_name", + dest="log_file", + help="""Path and base log filename, for supported types see can.Logger.""", + default=None, + ) + + parser.add_argument( + "-v", + action="count", + dest="verbosity", + help="""How much information do you want to see at the command line? + You can add several of these e.g., -vv is DEBUG""", + default=2, + ) + + parser.add_argument( + "-c", + "--channel", + help='''Most backend interfaces require some sort of channel. For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" - With the socketcan interfaces valid channel examples include: "can0", "vcan0"''') - - parser.add_argument('-i', '--interface', dest="interface", - help='''Specify the backend CAN interface to use. If left blank, - fall back to reading from configuration files.''', - choices=can.VALID_INTERFACES) - - parser.add_argument('--filter', help='''Comma separated filters can be specified for the given CAN interface: + With the socketcan interfaces valid channel examples include: "can0", "vcan0"''', + ) + + parser.add_argument( + "-i", + "--interface", + dest="interface", + help="""Specify the backend CAN interface to use. If left blank, + fall back to reading from configuration files.""", + choices=can.VALID_INTERFACES, + ) + + parser.add_argument( + "--filter", + help="""Comma separated filters can be specified for the given CAN interface: : (matches when & mask == can_id & mask) ~ (matches when & mask != can_id & mask) - ''', nargs=argparse.REMAINDER, default='') + """, + nargs=argparse.REMAINDER, + default="", + ) - parser.add_argument('-b', '--bitrate', type=int, - help='''Bitrate to use for the CAN bus.''') + parser.add_argument( + "-b", "--bitrate", type=int, help="""Bitrate to use for the CAN bus.""" + ) state_group = parser.add_mutually_exclusive_group(required=False) - state_group.add_argument('--active', help="Start the bus as active, this is applied by default.", - action='store_true') - state_group.add_argument('--passive', help="Start the bus as passive.", - action='store_true') + state_group.add_argument( + "--active", + help="Start the bus as active, this is applied by default.", + action="store_true", + ) + state_group.add_argument( + "--passive", help="Start the bus as passive.", action="store_true" + ) # print help message when no arguments wre given if len(sys.argv) < 2: parser.print_help(sys.stderr) import errno + raise SystemExit(errno.EINVAL) results = parser.parse_args() verbosity = results.verbosity - logging_level_name = ['critical', 'error', 'warning', 'info', 'debug', 'subdebug'][min(5, verbosity)] + logging_level_name = ["critical", "error", "warning", "info", "debug", "subdebug"][ + min(5, verbosity) + ] can.set_logging_level(logging_level_name) can_filters = [] if results.filter: print(f"Adding filter(s): {results.filter}") for filt in results.filter: - if ':' in filt: + if ":" in filt: _ = filt.split(":") can_id, can_mask = int(_[0], base=16), int(_[1], base=16) elif "~" in filt: can_id, can_mask = filt.split("~") - can_id = int(can_id, base=16) | 0x20000000 # CAN_INV_FILTER + can_id = int(can_id, base=16) | 0x20000000 # CAN_INV_FILTER can_mask = int(can_mask, base=16) & socket.CAN_ERR_FLAG can_filters.append({"can_id": can_id, "can_mask": can_mask}) diff --git a/can/message.py b/can/message.py index c3be33aa7..b3fd38139 100644 --- a/can/message.py +++ b/can/message.py @@ -43,14 +43,24 @@ class Message: "is_fd", "bitrate_switch", "error_state_indicator", - "__weakref__" # support weak references to messages + "__weakref__", # support weak references to messages ) - 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): + 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, + ): """ To create a message object, simply provide any of the below attributes together with additional parameters as keyword arguments to the constructor. @@ -100,14 +110,16 @@ def __str__(self): arbitration_id_string = "ID: {0:04x}".format(self.arbitration_id) field_strings.append(arbitration_id_string.rjust(12, " ")) - flag_string = " ".join([ - "X" if self.is_extended_id else "S", - "E" if self.is_error_frame else " ", - "R" if self.is_remote_frame else " ", - "F" if self.is_fd else " ", - "BS" if self.bitrate_switch else " ", - "EI" if self.error_state_indicator else " " - ]) + flag_string = " ".join( + [ + "X" if self.is_extended_id else "S", + "E" if self.is_error_frame else " ", + "R" if self.is_remote_frame else " ", + "F" if self.is_fd else " ", + "BS" if self.bitrate_switch else " ", + "EI" if self.error_state_indicator else " ", + ] + ) field_strings.append(flag_string) @@ -122,7 +134,7 @@ def __str__(self): field_strings.append(" " * 24) if (self.data is not None) and (self.data.isalnum()): - field_strings.append("'{}'".format(self.data.decode('utf-8', 'replace'))) + field_strings.append("'{}'".format(self.data.decode("utf-8", "replace"))) if self.channel is not None: try: @@ -140,9 +152,11 @@ def __bool__(self): return True def __repr__(self): - args = ["timestamp={}".format(self.timestamp), - "arbitration_id={:#x}".format(self.arbitration_id), - "is_extended_id={}".format(self.is_extended_id)] + args = [ + "timestamp={}".format(self.timestamp), + "arbitration_id={:#x}".format(self.arbitration_id), + "is_extended_id={}".format(self.is_extended_id), + ] if self.is_remote_frame: args.append("is_remote_frame={}".format(self.is_remote_frame)) @@ -154,8 +168,7 @@ def __repr__(self): args.append("channel={!r}".format(self.channel)) data = ["{:#02x}".format(byte) for byte in self.data] - args += ["dlc={}".format(self.dlc), - "data=[{}]".format(", ".join(data))] + args += ["dlc={}".format(self.dlc), "data=[{}]".format(", ".join(data))] if self.is_fd: args.append("is_fd=True") @@ -185,7 +198,7 @@ def __copy__(self): data=self.data, is_fd=self.is_fd, bitrate_switch=self.bitrate_switch, - error_state_indicator=self.error_state_indicator + error_state_indicator=self.error_state_indicator, ) return new @@ -201,7 +214,7 @@ def __deepcopy__(self, memo): data=deepcopy(self.data, memo), is_fd=self.is_fd, bitrate_switch=self.bitrate_switch, - error_state_indicator=self.error_state_indicator + error_state_indicator=self.error_state_indicator, ) return new @@ -220,7 +233,9 @@ def _check(self): raise ValueError("the timestamp may not be NaN") if self.is_remote_frame and self.is_error_frame: - raise ValueError("a message cannot be a remote and an error frame at the sane time") + raise ValueError( + "a message cannot be a remote and an error frame at the sane time" + ) if self.arbitration_id < 0: raise ValueError("arbitration IDs may not be negative") @@ -235,21 +250,33 @@ def _check(self): raise ValueError("DLC may not be negative") if self.is_fd: if self.dlc > 64: - raise ValueError("DLC was {} but it should be <= 64 for CAN FD frames".format(self.dlc)) + raise ValueError( + "DLC was {} but it should be <= 64 for CAN FD frames".format( + self.dlc + ) + ) elif self.dlc > 8: - raise ValueError("DLC was {} but it should be <= 8 for normal CAN frames".format(self.dlc)) + raise ValueError( + "DLC was {} but it should be <= 8 for normal CAN frames".format( + self.dlc + ) + ) if self.is_remote_frame: if self.data: raise ValueError("remote frames may not carry any data") elif self.dlc != len(self.data): - raise ValueError("the DLC and the length of the data must match up for non remote frames") + raise ValueError( + "the DLC and the length of the data must match up for non remote frames" + ) if not self.is_fd: if self.bitrate_switch: raise ValueError("bitrate switch is only allowed for CAN FD frames") if self.error_state_indicator: - raise ValueError("error state indicator is only allowed for CAN FD frames") + raise ValueError( + "error state indicator is only allowed for CAN FD frames" + ) def equals(self, other, timestamp_delta=1.0e-6): """ @@ -268,22 +295,23 @@ def equals(self, other, timestamp_delta=1.0e-6): # on why a delta of 1.0e-6 was chosen return ( # check for identity first and finish fast - self is other or + self is other + or # then check for equality by value ( ( - timestamp_delta is None or - abs(self.timestamp - other.timestamp) <= timestamp_delta - ) and - self.arbitration_id == other.arbitration_id and - self.is_extended_id == other.is_extended_id and - self.dlc == other.dlc and - self.data == other.data and - self.is_remote_frame == other.is_remote_frame and - self.is_error_frame == other.is_error_frame and - self.channel == other.channel and - self.is_fd == other.is_fd and - self.bitrate_switch == other.bitrate_switch and - self.error_state_indicator == other.error_state_indicator + timestamp_delta is None + or abs(self.timestamp - other.timestamp) <= timestamp_delta + ) + and self.arbitration_id == other.arbitration_id + and self.is_extended_id == other.is_extended_id + and self.dlc == other.dlc + and self.data == other.data + and self.is_remote_frame == other.is_remote_frame + and self.is_error_frame == other.is_error_frame + and self.channel == other.channel + and self.is_fd == other.is_fd + and self.bitrate_switch == other.bitrate_switch + and self.error_state_indicator == other.error_state_indicator ) ) diff --git a/can/notifier.py b/can/notifier.py index 256085a7b..5d0642ee6 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -9,11 +9,10 @@ import time import asyncio -logger = logging.getLogger('can.Notifier') +logger = logging.getLogger("can.Notifier") class Notifier: - def __init__(self, bus, listeners, timeout=1.0, loop=None): """Manages the distribution of :class:`can.Message` instances to listeners. @@ -53,13 +52,16 @@ def add_bus(self, bus): :param can.BusABC 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: # Use file descriptor to watch for messages reader = bus.fileno() self._loop.add_reader(reader, self._on_message_available, bus) else: - reader = threading.Thread(target=self._rx_thread, args=(bus,), - name='can.notifier for bus "{}"'.format(bus.channel_info)) + reader = threading.Thread( + target=self._rx_thread, + args=(bus,), + name='can.notifier for bus "{}"'.format(bus.channel_info), + ) reader.daemon = True reader.start() self._readers.append(reader) @@ -83,7 +85,7 @@ def stop(self, timeout=5): # reader is a file descriptor self._loop.remove_reader(reader) for listener in self.listeners: - if hasattr(listener, 'stop'): + if hasattr(listener, "stop"): listener.stop() def _rx_thread(self, bus): @@ -94,7 +96,8 @@ def _rx_thread(self, bus): with self._lock: if self._loop is not None: self._loop.call_soon_threadsafe( - self._on_message_received, msg) + self._on_message_received, msg + ) else: self._on_message_received(msg) msg = bus.recv(self.timeout) @@ -120,7 +123,7 @@ def _on_message_received(self, msg): def _on_error(self, exc): for listener in self.listeners: - if hasattr(listener, 'on_error'): + if hasattr(listener, "on_error"): listener.on_error(exc) def add_listener(self, listener): diff --git a/can/player.py b/can/player.py index d41396cf5..b279579f7 100644 --- a/can/player.py +++ b/can/player.py @@ -17,53 +17,90 @@ def main(): parser = argparse.ArgumentParser( - "python -m can.player", - description="Replay CAN traffic.") - - parser.add_argument("-f", "--file_name", dest="log_file", - help="""Path and base log filename, for supported types see can.LogReader.""", - default=None) - - parser.add_argument("-v", action="count", dest="verbosity", - help='''Also print can frames to stdout. - You can add several of these to enable debugging''', default=2) - - parser.add_argument('-c', '--channel', - help='''Most backend interfaces require some sort of channel. + "python -m can.player", description="Replay CAN traffic." + ) + + parser.add_argument( + "-f", + "--file_name", + dest="log_file", + help="""Path and base log filename, for supported types see can.LogReader.""", + default=None, + ) + + parser.add_argument( + "-v", + action="count", + dest="verbosity", + help="""Also print can frames to stdout. + You can add several of these to enable debugging""", + default=2, + ) + + parser.add_argument( + "-c", + "--channel", + help='''Most backend interfaces require some sort of channel. For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" - With the socketcan interfaces valid channel examples include: "can0", "vcan0"''') - - parser.add_argument('-i', '--interface', dest="interface", - help='''Specify the backend CAN interface to use. If left blank, - fall back to reading from configuration files.''', - choices=can.VALID_INTERFACES) - - parser.add_argument('-b', '--bitrate', type=int, - help='''Bitrate to use for the CAN bus.''') - - parser.add_argument('--ignore-timestamps', dest='timestamps', - help='''Ignore timestamps (send all frames immediately with minimum gap between frames)''', - action='store_false') - - parser.add_argument('-g', '--gap', type=float, help=''' minimum time between replayed frames''', - default=0.0001) - parser.add_argument('-s', '--skip', type=float, default=60*60*24, - help=''' skip gaps greater than 's' seconds''') - - parser.add_argument('infile', metavar='input-file', type=str, - help='The file to replay. For supported types see can.LogReader.') + With the socketcan interfaces valid channel examples include: "can0", "vcan0"''', + ) + + parser.add_argument( + "-i", + "--interface", + dest="interface", + help="""Specify the backend CAN interface to use. If left blank, + fall back to reading from configuration files.""", + choices=can.VALID_INTERFACES, + ) + + parser.add_argument( + "-b", "--bitrate", type=int, help="""Bitrate to use for the CAN bus.""" + ) + + parser.add_argument( + "--ignore-timestamps", + dest="timestamps", + help="""Ignore timestamps (send all frames immediately with minimum gap between frames)""", + action="store_false", + ) + + parser.add_argument( + "-g", + "--gap", + type=float, + help=""" minimum time between replayed frames""", + default=0.0001, + ) + parser.add_argument( + "-s", + "--skip", + type=float, + default=60 * 60 * 24, + help=""" skip gaps greater than 's' seconds""", + ) + + parser.add_argument( + "infile", + metavar="input-file", + type=str, + help="The file to replay. For supported types see can.LogReader.", + ) # print help message when no arguments were given if len(sys.argv) < 2: parser.print_help(sys.stderr) import errno + raise SystemExit(errno.EINVAL) results = parser.parse_args() verbosity = results.verbosity - logging_level_name = ['critical', 'error', 'warning', 'info', 'debug', 'subdebug'][min(5, verbosity)] + logging_level_name = ["critical", "error", "warning", "info", "debug", "subdebug"][ + min(5, verbosity) + ] can.set_logging_level(logging_level_name) config = {"single_handle": True} @@ -75,8 +112,9 @@ def main(): reader = LogReader(results.infile) - in_sync = MessageSync(reader, timestamps=results.timestamps, - gap=results.gap, skip=results.skip) + in_sync = MessageSync( + reader, timestamps=results.timestamps, gap=results.gap, skip=results.skip + ) print(f"Can LogReader (Started on {datetime.now()})") diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 7e0d058f2..91f7e6d2c 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -6,6 +6,7 @@ # Only raise an exception on instantiation but allow module # to be imported from wrapt import ObjectProxy + import_exc = None except ImportError as exc: ObjectProxy = object @@ -18,6 +19,7 @@ from contextlib import nullcontext except ImportError: + class nullcontext: """A context manager that does nothing at all. A fallback for Python 3.7's :class:`contextlib.nullcontext` manager. @@ -33,7 +35,7 @@ def __exit__(self, *args): pass -class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method +class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method """ Contains a thread safe :class:`can.BusABC` implementation that wraps around an existing interface instance. All public methods diff --git a/can/util.py b/can/util.py index c567d8ada..c04bb70c9 100644 --- a/can/util.py +++ b/can/util.py @@ -14,37 +14,20 @@ import can from can.interfaces import VALID_INTERFACES -log = logging.getLogger('can.util') +log = logging.getLogger("can.util") # List of valid data lengths for a CAN FD message -CAN_FD_DLC = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, - 12, 16, 20, 24, 32, 48, 64 -] +CAN_FD_DLC = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64] -REQUIRED_KEYS = [ - 'interface', - 'channel', -] +REQUIRED_KEYS = ["interface", "channel"] -CONFIG_FILES = ['~/can.conf'] +CONFIG_FILES = ["~/can.conf"] if platform.system() == "Linux": - CONFIG_FILES.extend( - [ - '/etc/can.conf', - '~/.can', - '~/.canrc' - ] - ) + CONFIG_FILES.extend(["/etc/can.conf", "~/.can", "~/.canrc"]) elif platform.system() == "Windows" or platform.python_implementation() == "IronPython": - CONFIG_FILES.extend( - [ - 'can.ini', - os.path.join(os.getenv('APPDATA', ''), 'can.ini') - ] - ) + CONFIG_FILES.extend(["can.ini", os.path.join(os.getenv("APPDATA", ""), "can.ini")]) def load_file_config(path=None, section=None): @@ -69,11 +52,10 @@ def load_file_config(path=None, section=None): _config = {} - section = section if section is not None else 'default' + section = section if section is not None else "default" if config.has_section(section): - if config.has_section('default'): - _config.update( - dict((key, val) for key, val in config.items('default'))) + if config.has_section("default"): + _config.update(dict((key, val) for key, val in config.items("default"))) _config.update(dict((key, val) for key, val in config.items(section))) return _config @@ -89,14 +71,12 @@ def load_environment_config(): """ mapper = { - 'interface': 'CAN_INTERFACE', - 'channel': 'CAN_CHANNEL', - 'bitrate': 'CAN_BITRATE', + "interface": "CAN_INTERFACE", + "channel": "CAN_CHANNEL", + "bitrate": "CAN_BITRATE", } return dict( - (key, os.environ.get(val)) - for key, val in mapper.items() - if val in os.environ + (key, os.environ.get(val)) for key, val in mapper.items() if val in os.environ ) @@ -156,7 +136,7 @@ def load_config(path=None, config=None, context=None): given_config, can.rc, lambda _context: load_environment_config(), # context is not supported - lambda _context: load_file_config(path, _context) + lambda _context: load_file_config(path, _context), ] # Slightly complex here to only search for the file config if required @@ -164,10 +144,10 @@ def load_config(path=None, config=None, context=None): if callable(cfg): cfg = cfg(context) # remove legacy operator (and copy to interface if not already present) - if 'bustype' in cfg: - if 'interface' not in cfg or not cfg['interface']: - cfg['interface'] = cfg['bustype'] - del cfg['bustype'] + if "bustype" in cfg: + if "interface" not in cfg or not cfg["interface"]: + cfg["interface"] = cfg["bustype"] + del cfg["bustype"] # copy all new parameters for key in cfg: if key not in config: @@ -178,11 +158,13 @@ def load_config(path=None, config=None, context=None): if key not in config: config[key] = None - if config['interface'] not in VALID_INTERFACES: - raise NotImplementedError('Invalid CAN Bus Type - {}'.format(config['interface'])) + if config["interface"] not in VALID_INTERFACES: + raise NotImplementedError( + "Invalid CAN Bus Type - {}".format(config["interface"]) + ) - if 'bitrate' in config: - config['bitrate'] = int(config['bitrate']) + if "bitrate" in config: + config["bitrate"] = int(config["bitrate"]) can.log.debug("can config: {}".format(config)) return config @@ -192,7 +174,7 @@ def set_logging_level(level_name=None): """Set the logging level for the "can" logger. Expects one of: 'critical', 'error', 'warning', 'info', 'debug', 'subdebug' """ - can_logger = logging.getLogger('can') + can_logger = logging.getLogger("can") try: can_logger.setLevel(getattr(logging, level_name.upper())) @@ -243,7 +225,7 @@ def channel2int(channel): return channel # String and byte objects have a lower() method if hasattr(channel, "lower"): - match = re.match(r'.*(\d+)$', channel) + match = re.match(r".*(\d+)$", channel) if match: return int(match.group(1)) return None diff --git a/can/viewer.py b/can/viewer.py index 1332766df..5f8d18858 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -32,20 +32,20 @@ import can from can import __version__ -logger = logging.getLogger('can.serial') +logger = logging.getLogger("can.serial") try: import curses from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE except ImportError: # Probably on windows - logger.warning("You won't be able to use the viewer program without " - "curses installed!") + logger.warning( + "You won't be able to use the viewer program without " "curses installed!" + ) curses = None class CanViewer: - def __init__(self, stdscr, bus, data_structs, testing=False): self.stdscr = stdscr self.bus = bus @@ -79,37 +79,37 @@ def run(self): # Do not read the CAN-Bus when in paused mode if not self.paused: # Read the CAN-Bus and draw it in the terminal window - msg = self.bus.recv(timeout=1. / 1000.) + msg = self.bus.recv(timeout=1.0 / 1000.0) if msg is not None: self.draw_can_bus_message(msg) else: # Sleep 1 ms, so the application does not use 100 % of the CPU resources - time.sleep(1. / 1000.) + time.sleep(1.0 / 1000.0) # Read the terminal input key = self.stdscr.getch() # Stop program if the user presses ESC or 'q' - if key == KEY_ESC or key == ord('q'): + if key == KEY_ESC or key == ord("q"): break # Clear by pressing 'c' - elif key == ord('c'): + elif key == ord("c"): self.ids = {} self.start_time = None self.scroll = 0 self.draw_header() # Sort by pressing 's' - elif key == ord('s'): + elif key == ord("s"): # Sort frames based on the CAN-Bus ID self.draw_header() for i, key in enumerate(sorted(self.ids.keys())): # Set the new row index, but skip the header - self.ids[key]['row'] = i + 1 + self.ids[key]["row"] = i + 1 # Do a recursive call, so the frames are repositioned - self.draw_can_bus_message(self.ids[key]['msg'], sorting=True) + self.draw_can_bus_message(self.ids[key]["msg"], sorting=True) # Pause by pressing space elif key == KEY_SPACE: @@ -131,7 +131,7 @@ def run(self): resized = curses.is_term_resized(self.y, self.x) if resized is True: self.y, self.x = self.stdscr.getmaxyx() - if hasattr(curses, 'resizeterm'): # pragma: no cover + if hasattr(curses, "resizeterm"): # pragma: no cover curses.resizeterm(self.y, self.x) self.redraw_screen() @@ -140,7 +140,9 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod - def unpack_data(cmd, cmd_to_struct, data): # type: (int, Dict, bytes) -> List[Union[float, int]] + def unpack_data( + cmd, cmd_to_struct, data + ): # type: (int, Dict, bytes) -> List[Union[float, int]] if not cmd_to_struct or not data: # These messages do not contain a data package return [] @@ -153,8 +155,10 @@ def unpack_data(cmd, cmd_to_struct, data): # type: (int, Dict, bytes) -> List[U struct_t = value[0] # type: struct.Struct # The conversion from raw values to SI-units are given in the rest of the tuple - values = [d // val if isinstance(val, int) else float(d) / val - for d, val in zip(struct_t.unpack(data), value[1:])] + values = [ + d // val if isinstance(val, int) else float(d) / val + for d, val in zip(struct_t.unpack(data), value[1:]) + ] else: # No conversion from SI-units is needed struct_t = value # type: struct.Struct @@ -162,7 +166,7 @@ def unpack_data(cmd, cmd_to_struct, data): # type: (int, Dict, bytes) -> List[U return values else: - raise ValueError('Unknown command: 0x{:02X}'.format(cmd)) + raise ValueError("Unknown command: 0x{:02X}".format(cmd)) def draw_can_bus_message(self, msg, sorting=False): # Use the CAN-Bus ID as the key in the dict @@ -170,7 +174,7 @@ def draw_can_bus_message(self, msg, sorting=False): # Sort the extended IDs at the bottom by setting the 32-bit high if msg.is_extended_id: - key |= (1 << 32) + key |= 1 << 32 new_id_added, length_changed = False, False if not sorting: @@ -180,35 +184,37 @@ def draw_can_bus_message(self, msg, sorting=False): # Set the start time when the first message has been received if not self.start_time: self.start_time = msg.timestamp - elif msg.dlc != self.ids[key]['msg'].dlc: + elif msg.dlc != self.ids[key]["msg"].dlc: length_changed = True if new_id_added or length_changed: # Increment the index if it was just added, but keep it if the length just changed - row = len(self.ids) + 1 if new_id_added else self.ids[key]['row'] + row = len(self.ids) + 1 if new_id_added else self.ids[key]["row"] # It's a new message ID or the length has changed, so add it to the dict # The first index is the row index, the second is the frame counter, # the third is a copy of the CAN-Bus frame # and the forth index is the time since the previous message - self.ids[key] = {'row': row, 'count': 0, 'msg': msg, 'dt': 0} + self.ids[key] = {"row": row, "count": 0, "msg": msg, "dt": 0} else: # Calculate the time since the last message and save the timestamp - self.ids[key]['dt'] = msg.timestamp - self.ids[key]['msg'].timestamp + self.ids[key]["dt"] = msg.timestamp - self.ids[key]["msg"].timestamp # Copy the CAN-Bus frame - this is used for sorting - self.ids[key]['msg'] = msg + self.ids[key]["msg"] = msg # Increment frame counter - self.ids[key]['count'] += 1 + self.ids[key]["count"] += 1 # Format the CAN-Bus ID as a hex value - arbitration_id_string = '0x{0:0{1}X}'.format(msg.arbitration_id, 8 if msg.is_extended_id else 3) + arbitration_id_string = "0x{0:0{1}X}".format( + msg.arbitration_id, 8 if msg.is_extended_id else 3 + ) # Generate data string - data_string = '' + data_string = "" if msg.dlc > 0: - data_string = ' '.join('{:02X}'.format(x) for x in msg.data) + data_string = " ".join("{:02X}".format(x) for x in msg.data) # Use red for error frames if msg.is_error_frame: @@ -217,24 +223,32 @@ def draw_can_bus_message(self, msg, sorting=False): color = curses.color_pair(0) # Now draw the CAN-Bus message on the terminal window - self.draw_line(self.ids[key]['row'], 0, str(self.ids[key]['count']), color) - self.draw_line(self.ids[key]['row'], 8, '{0:.6f}'.format(self.ids[key]['msg'].timestamp - self.start_time), - color) - self.draw_line(self.ids[key]['row'], 23, '{0:.6f}'.format(self.ids[key]['dt']), color) - self.draw_line(self.ids[key]['row'], 35, arbitration_id_string, color) - self.draw_line(self.ids[key]['row'], 47, str(msg.dlc), color) - self.draw_line(self.ids[key]['row'], 52, data_string, color) + self.draw_line(self.ids[key]["row"], 0, str(self.ids[key]["count"]), color) + self.draw_line( + self.ids[key]["row"], + 8, + "{0:.6f}".format(self.ids[key]["msg"].timestamp - self.start_time), + color, + ) + self.draw_line( + self.ids[key]["row"], 23, "{0:.6f}".format(self.ids[key]["dt"]), color + ) + self.draw_line(self.ids[key]["row"], 35, arbitration_id_string, color) + self.draw_line(self.ids[key]["row"], 47, str(msg.dlc), color) + self.draw_line(self.ids[key]["row"], 52, data_string, color) if self.data_structs: try: values_list = [] - for x in self.unpack_data(msg.arbitration_id, self.data_structs, msg.data): + for x in self.unpack_data( + msg.arbitration_id, self.data_structs, msg.data + ): if isinstance(x, float): - values_list.append('{0:.6f}'.format(x)) + values_list.append("{0:.6f}".format(x)) else: values_list.append(str(x)) - values_string = ' '.join(values_list) - self.draw_line(self.ids[key]['row'], 77, values_string, color) + values_string = " ".join(values_list) + self.draw_line(self.ids[key]["row"], 77, values_string, color) except (ValueError, struct.error): pass @@ -253,31 +267,30 @@ def draw_line(self, row, col, txt, *args): def draw_header(self): self.stdscr.erase() - self.draw_line(0, 0, 'Count', curses.A_BOLD) - self.draw_line(0, 8, 'Time', curses.A_BOLD) - self.draw_line(0, 23, 'dt', curses.A_BOLD) - self.draw_line(0, 35, 'ID', curses.A_BOLD) - self.draw_line(0, 47, 'DLC', curses.A_BOLD) - self.draw_line(0, 52, 'Data', curses.A_BOLD) + self.draw_line(0, 0, "Count", curses.A_BOLD) + self.draw_line(0, 8, "Time", curses.A_BOLD) + self.draw_line(0, 23, "dt", curses.A_BOLD) + self.draw_line(0, 35, "ID", curses.A_BOLD) + self.draw_line(0, 47, "DLC", curses.A_BOLD) + self.draw_line(0, 52, "Data", curses.A_BOLD) if self.data_structs: # Only draw if the dictionary is not empty - self.draw_line(0, 77, 'Parsed values', curses.A_BOLD) + self.draw_line(0, 77, "Parsed values", curses.A_BOLD) def redraw_screen(self): # Trigger a complete redraw self.draw_header() for key in self.ids: - self.draw_can_bus_message(self.ids[key]['msg']) + self.draw_can_bus_message(self.ids[key]["msg"]) # noinspection PyProtectedMember class SmartFormatter(argparse.HelpFormatter): - def _get_default_metavar_for_optional(self, action): return action.dest.upper() def _format_usage(self, usage, actions, groups, prefix): # Use uppercase for "Usage:" text - return super()._format_usage(usage, actions, groups, 'Usage: ') + return super()._format_usage(usage, actions, groups, "Usage: ") def _format_args(self, action, default_metavar): if action.nargs != argparse.REMAINDER and action.nargs != argparse.ONE_OR_MORE: @@ -285,7 +298,7 @@ def _format_args(self, action, default_metavar): # Use the metavar if "REMAINDER" or "ONE_OR_MORE" is set get_metavar = self._metavar_formatter(action, default_metavar) - return '%s' % get_metavar(1) + return "%s" % get_metavar(1) def _format_action_invocation(self, action): if not action.option_strings or action.nargs == 0: @@ -298,21 +311,21 @@ def _format_action_invocation(self, action): args_string = self._format_args(action, default) for i, option_string in enumerate(action.option_strings): if i == len(action.option_strings) - 1: - parts.append('%s %s' % (option_string, args_string)) + parts.append("%s %s" % (option_string, args_string)) else: - parts.append('%s' % option_string) - return ', '.join(parts) + parts.append("%s" % option_string) + return ", ".join(parts) def _split_lines(self, text, width): # Allow to manually split the lines - if text.startswith('R|'): + if text.startswith("R|"): return text[2:].splitlines() return super()._split_lines(text, width) def _fill_text(self, text, width, indent): - if text.startswith('R|'): + if text.startswith("R|"): # noinspection PyTypeChecker - return ''.join(indent + line + '\n' for line in text[2:].splitlines()) + return "".join(indent + line + "\n" for line in text[2:].splitlines()) else: return super()._fill_text(text, width, indent) @@ -321,91 +334,126 @@ def parse_args(args): # Python versions >= 3.5 kwargs = {} if sys.version_info[0] * 10 + sys.version_info[1] >= 35: # pragma: no cover - kwargs = {'allow_abbrev': False} + kwargs = {"allow_abbrev": False} # Parse command line arguments - parser = argparse.ArgumentParser('python -m can.viewer', - description='A simple CAN viewer terminal application written in Python', - epilog='R|Shortcuts: ' - '\n +---------+-------------------------+' - '\n | Key | Description |' - '\n +---------+-------------------------+' - '\n | ESQ/q | Exit the viewer |' - '\n | c | Clear the stored frames |' - '\n | s | Sort the stored frames |' - '\n | SPACE | Pause the viewer |' - '\n | UP/DOWN | Scroll the viewer |' - '\n +---------+-------------------------+', - formatter_class=SmartFormatter, add_help=False, **kwargs) - - optional = parser.add_argument_group('Optional arguments') - - optional.add_argument('-h', '--help', action='help', help='Show this help message and exit') - - optional.add_argument('--version', action='version', help="Show program's version number and exit", - version='%(prog)s (version {version})'.format(version=__version__)) + parser = argparse.ArgumentParser( + "python -m can.viewer", + description="A simple CAN viewer terminal application written in Python", + epilog="R|Shortcuts: " + "\n +---------+-------------------------+" + "\n | Key | Description |" + "\n +---------+-------------------------+" + "\n | ESQ/q | Exit the viewer |" + "\n | c | Clear the stored frames |" + "\n | s | Sort the stored frames |" + "\n | SPACE | Pause the viewer |" + "\n | UP/DOWN | Scroll the viewer |" + "\n +---------+-------------------------+", + formatter_class=SmartFormatter, + add_help=False, + **kwargs + ) + + optional = parser.add_argument_group("Optional arguments") + + optional.add_argument( + "-h", "--help", action="help", help="Show this help message and exit" + ) + + optional.add_argument( + "--version", + action="version", + help="Show program's version number and exit", + version="%(prog)s (version {version})".format(version=__version__), + ) # Copied from: https://github.com/hardbyte/python-can/blob/develop/can/logger.py - optional.add_argument('-b', '--bitrate', type=int, help='''Bitrate to use for the given CAN interface''') - - optional.add_argument('-c', '--channel', help='''Most backend interfaces require some sort of channel. + optional.add_argument( + "-b", + "--bitrate", + type=int, + help="""Bitrate to use for the given CAN interface""", + ) + + optional.add_argument( + "-c", + "--channel", + help="""Most backend interfaces require some sort of channel. For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" with the socketcan interfaces valid channel examples include: "can0", "vcan0". - (default: use default for the specified interface)''') - - optional.add_argument('-d', '--decode', dest='decode', - help='R|Specify how to convert the raw bytes into real values.' - '\nThe ID of the frame is given as the first argument and the format as the second.' - '\nThe Python struct package is used to unpack the received data' - '\nwhere the format characters have the following meaning:' - '\n < = little-endian, > = big-endian' - '\n x = pad byte' - '\n c = char' - '\n ? = bool' - '\n b = int8_t, B = uint8_t' - '\n h = int16, H = uint16' - '\n l = int32_t, L = uint32_t' - '\n q = int64_t, Q = uint64_t' - '\n f = float (32-bits), d = double (64-bits)' - '\nFx to convert six bytes with ID 0x100 into uint8_t, uint16 and uint32_t:' - '\n $ python -m can.viewer -d "100:: (matches when & mask == can_id & mask)' - '\n ~ (matches when & mask != can_id & mask)' - '\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:' - '\n python -m can.viewer -f 100:7FC 200:7F0' - '\nNote that the ID and mask are alway interpreted as hex values', - metavar='{:,~}', nargs=argparse.ONE_OR_MORE, default='') - - optional.add_argument('-i', '--interface', dest='interface', - help='R|Specify the backend CAN interface to use.', - choices=sorted(can.VALID_INTERFACES)) + (default: use default for the specified interface)""", + ) + + optional.add_argument( + "-d", + "--decode", + dest="decode", + help="R|Specify how to convert the raw bytes into real values." + "\nThe ID of the frame is given as the first argument and the format as the second." + "\nThe Python struct package is used to unpack the received data" + "\nwhere the format characters have the following meaning:" + "\n < = little-endian, > = big-endian" + "\n x = pad byte" + "\n c = char" + "\n ? = bool" + "\n b = int8_t, B = uint8_t" + "\n h = int16, H = uint16" + "\n l = int32_t, L = uint32_t" + "\n q = int64_t, Q = uint64_t" + "\n f = float (32-bits), d = double (64-bits)" + "\nFx to convert six bytes with ID 0x100 into uint8_t, uint16 and uint32_t:" + '\n $ python -m can.viewer -d "100:: (matches when & mask == can_id & mask)" + "\n ~ (matches when & mask != can_id & mask)" + "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" + "\n python -m can.viewer -f 100:7FC 200:7F0" + "\nNote that the ID and mask are alway interpreted as hex values", + metavar="{:,~}", + nargs=argparse.ONE_OR_MORE, + default="", + ) + + optional.add_argument( + "-i", + "--interface", + dest="interface", + help="R|Specify the backend CAN interface to use.", + choices=sorted(can.VALID_INTERFACES), + ) # Print help message when no arguments are given if not args: parser.print_help(sys.stderr) import errno + raise SystemExit(errno.EINVAL) parsed_args = parser.parse_args(args) @@ -415,16 +463,16 @@ def parse_args(args): # print('Adding filter/s', parsed_args.filter) for flt in parsed_args.filter: # print(filter) - if ':' in flt: - _ = flt.split(':') + if ":" in flt: + _ = flt.split(":") can_id, can_mask = int(_[0], base=16), int(_[1], base=16) - elif '~' in flt: - can_id, can_mask = flt.split('~') + elif "~" in flt: + can_id, can_mask = flt.split("~") can_id = int(can_id, base=16) | 0x20000000 # CAN_INV_FILTER can_mask = int(can_mask, base=16) & 0x20000000 # socket.CAN_ERR_FLAG else: - raise argparse.ArgumentError(None, 'Invalid filter argument') - can_filters.append({'can_id': can_id, 'can_mask': can_mask}) + raise argparse.ArgumentError(None, "Invalid filter argument") + can_filters.append({"can_id": can_id, "can_mask": can_mask}) # Dictionary used to convert between Python values and C structs represented as Python strings. # If the value is 'None' then the message does not contain any data package. @@ -444,16 +492,18 @@ def parse_args(args): # An optional conversion from real units to integers can be given as additional arguments. # In order to convert from raw integer value the real units are multiplied with the values and similarly the values # are divided by the value in order to convert from real units to raw integer values. - data_structs = {} # type: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] + data_structs = ( + {} + ) # type: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] if parsed_args.decode: if os.path.isfile(parsed_args.decode[0]): - with open(parsed_args.decode[0], 'r') as f: + with open(parsed_args.decode[0], "r") as f: structs = f.readlines() else: structs = parsed_args.decode for s in structs: - tmp = s.rstrip('\n').split(':') + tmp = s.rstrip("\n").split(":") # The ID is given as a hex value, the format needs no conversion key, fmt = int(tmp[0], base=16), tmp[1] @@ -479,13 +529,13 @@ def parse_args(args): def main(): # pragma: no cover parsed_args, can_filters, data_structs = parse_args(sys.argv[1:]) - config = {'single_handle': True} + config = {"single_handle": True} if can_filters: - config['can_filters'] = can_filters + config["can_filters"] = can_filters if parsed_args.interface: - config['interface'] = parsed_args.interface + config["interface"] = parsed_args.interface if parsed_args.bitrate: - config['bitrate'] = parsed_args.bitrate + config["bitrate"] = parsed_args.bitrate # Create a CAN-Bus interface bus = can.Bus(parsed_args.channel, **config) @@ -494,7 +544,7 @@ def main(): # pragma: no cover curses.wrapper(CanViewer, bus, data_structs) -if __name__ == '__main__': +if __name__ == "__main__": # Catch ctrl+c try: main() diff --git a/doc/conf.py b/doc/conf.py index 1c409a06a..b95aa6557 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -10,102 +10,100 @@ import os # General information about the project. -project = u'python-can' +project = "python-can" # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) import can + # The version info for the project, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = can.__version__.split('-')[0] +version = can.__version__.split("-")[0] release = can.__version__ # -- General configuration ----------------------------------------------------- -primary_domain = 'py' +primary_domain = "py" # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.extlinks', - 'sphinx.ext.todo', - 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', - 'sphinx.ext.graphviz', - 'sphinxcontrib.programoutput' - ] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.extlinks", + "sphinx.ext.todo", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", + "sphinx.ext.graphviz", + "sphinxcontrib.programoutput", +] # Now, you can use the alias name as a new role, e.g. :issue:`123`. -extlinks = { - 'issue': ('https://github.com/hardbyte/python-can/issues/%s/', 'issue '), -} +extlinks = {"issue": ("https://github.com/hardbyte/python-can/issues/%s/", "issue ")} -intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), -} +intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} # If this is True, todo and todolist produce output, else they produce nothing. # The default is False. todo_include_todos = True # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] -graphviz_output_format = 'png' # 'svg' +graphviz_output_format = "png" # 'svg' # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -language = 'en' +language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # Include documentation from both the class level and __init__ autoclass_content = "both" # The default autodoc directive flags -autodoc_default_flags = ['members', 'show-inheritance'] +autodoc_default_flags = ["members", "show-inheritance"] # Keep cached intersphinx inventories indefinitely intersphinx_cache_limit = -1 @@ -114,77 +112,77 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] +# html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'python-can' +htmlhelp_basename = "python-can" diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index 3e71ae6db..534f47184 100644 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -1,19 +1,21 @@ import asyncio import can + def print_message(msg): """Regular callback function. Can also be a coroutine.""" print(msg) + async def main(): - can0 = can.Bus('vcan0', bustype='virtual', receive_own_messages=True) + can0 = can.Bus("vcan0", bustype="virtual", receive_own_messages=True) reader = can.AsyncBufferedReader() - logger = can.Logger('logfile.asc') + logger = can.Logger("logfile.asc") listeners = [ print_message, # Callback function - reader, # AsyncBufferedReader() listener - logger # Regular Listener object + reader, # AsyncBufferedReader() listener + logger, # Regular Listener object ] # Create Notifier with an explicit loop to use for scheduling of callbacks loop = asyncio.get_event_loop() @@ -21,7 +23,7 @@ async def main(): # Start sending first message can0.send(can.Message(arbitration_id=0)) - print('Bouncing 10 messages...') + print("Bouncing 10 messages...") for _ in range(10): # Wait for next message from AsyncBufferedReader msg = await reader.get_message() @@ -31,12 +33,13 @@ async def main(): can0.send(msg) # Wait for last message to arrive await reader.get_message() - print('Done!') + print("Done!") # Clean-up notifier.stop() can0.shutdown() + # Get the default event loop loop = asyncio.get_event_loop() # Run until main coroutine finishes diff --git a/examples/cyclic.py b/examples/cyclic.py index 021af14bf..25c215dda 100755 --- a/examples/cyclic.py +++ b/examples/cyclic.py @@ -26,7 +26,9 @@ def simple_periodic_send(bus): Sleeps for 2 seconds then stops the task. """ print("Starting to send a message every 200ms for 2s") - msg = can.Message(arbitration_id=0x123, data=[1, 2, 3, 4, 5, 6], is_extended_id=False) + msg = can.Message( + arbitration_id=0x123, data=[1, 2, 3, 4, 5, 6], is_extended_id=False + ) task = bus.send_periodic(msg, 0.20) assert isinstance(task, can.CyclicSendTaskABC) time.sleep(2) @@ -36,7 +38,9 @@ def simple_periodic_send(bus): def limited_periodic_send(bus): print("Starting to send a message every 200ms for 1s") - msg = can.Message(arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], is_extended_id=True) + msg = can.Message( + arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], is_extended_id=True + ) task = bus.send_periodic(msg, 0.20, 1, store_task=False) if not isinstance(task, can.LimitedDurationCyclicSendTaskABC): print("This interface doesn't seem to support a ") @@ -48,12 +52,12 @@ def limited_periodic_send(bus): # Note the (finished) task will still be tracked by the Bus # unless we pass `store_task=False` to bus.send_periodic # alternatively calling stop removes the task from the bus - #task.stop() + # task.stop() def test_periodic_send_with_modifying_data(bus): print("Starting to send a message every 200ms. Initial data is ones") - msg = can.Message(arbitration_id=0x0cf02200, data=[1, 1, 1, 1]) + msg = can.Message(arbitration_id=0x0CF02200, data=[1, 1, 1, 1]) task = bus.send_periodic(msg, 0.20) if not isinstance(task, can.ModifiableCyclicTaskABC): print("This interface doesn't seem to support modification") @@ -68,7 +72,7 @@ def test_periodic_send_with_modifying_data(bus): task.stop() print("stopped cyclic send") print("Changing data of stopped task to single ff byte") - msg.data = bytearray([0xff]) + msg.data = bytearray([0xFF]) msg.dlc = 1 task.modify_data(msg) time.sleep(1) @@ -107,11 +111,13 @@ def test_periodic_send_with_modifying_data(bus): if __name__ == "__main__": - reset_msg = can.Message(arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False) + reset_msg = can.Message( + arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False + ) for interface, channel in [ - ('socketcan', 'vcan0'), - #('ixxat', 0) + ("socketcan", "vcan0"), + # ('ixxat', 0) ]: print("Carrying out cyclic tests with {} interface".format(interface)) @@ -126,9 +132,9 @@ def test_periodic_send_with_modifying_data(bus): test_periodic_send_with_modifying_data(bus) - #print("Carrying out multirate cyclic test for {} interface".format(interface)) - #can.rc['interface'] = interface - #test_dual_rate_periodic_send() + # print("Carrying out multirate cyclic test for {} interface".format(interface)) + # can.rc['interface'] = interface + # test_dual_rate_periodic_send() bus.shutdown() diff --git a/examples/receive_all.py b/examples/receive_all.py index 44a495de7..ced8841bc 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -8,9 +8,9 @@ def receive_all(): - bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) - #bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) - #bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + bus = can.interface.Bus(bustype="pcan", channel="PCAN_USBBUS1", bitrate=250000) + # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) bus.state = BusState.ACTIVE # or BusState.PASSIVE diff --git a/examples/send_one.py b/examples/send_one.py index 2533ca37c..9a3181e35 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -9,6 +9,7 @@ import can + def send_one(): # this uses the default configuration (for example from the config file) @@ -22,9 +23,9 @@ def send_one(): # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) # ... - msg = can.Message(arbitration_id=0xc0ffee, - data=[0, 25, 0, 1, 3, 1, 4, 1], - is_extended_id=True) + msg = can.Message( + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True + ) try: bus.send(msg) @@ -32,5 +33,6 @@ def send_one(): except can.CanError: print("Message NOT sent") -if __name__ == '__main__': + +if __name__ == "__main__": send_one() diff --git a/examples/serial_com.py b/examples/serial_com.py index efa0bcdb5..b0dba4fec 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -46,17 +46,20 @@ def receive(bus, stop_event): print("rx: {}".format(rx_msg)) print("Stopped receiving messages") + if __name__ == "__main__": - server = can.interface.Bus(bustype='serial', channel='/dev/ttyS10') - client = can.interface.Bus(bustype='serial', channel='/dev/ttyS11') + server = can.interface.Bus(bustype="serial", channel="/dev/ttyS10") + client = can.interface.Bus(bustype="serial", channel="/dev/ttyS11") - tx_msg = can.Message(arbitration_id=0x01, data=[0x11, 0x22, 0x33, 0x44, - 0x55, 0x66, 0x77, 0x88]) + tx_msg = can.Message( + arbitration_id=0x01, data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] + ) # Thread for sending and receiving messages stop_event = threading.Event() - t_send_cyclic = threading.Thread(target=send_cyclic, args=(server, tx_msg, - stop_event)) + t_send_cyclic = threading.Thread( + target=send_cyclic, args=(server, tx_msg, stop_event) + ) t_receive = threading.Thread(target=receive, args=(client, stop_event)) t_receive.start() t_send_cyclic.start() diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index cf7e1f8e3..b351db09b 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -9,12 +9,12 @@ import can -if __name__ == '__main__': - bus = can.interface.Bus(bustype='socketcan', - channel='vcan0', - receive_own_messages=True) +if __name__ == "__main__": + bus = can.interface.Bus( + bustype="socketcan", channel="vcan0", receive_own_messages=True + ) - can_filters = [{"can_id": 1, "can_mask": 0xf, "extended": True}] + can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}] bus.set_filters(can_filters) notifier = can.Notifier(bus, [can.Printer()]) bus.send(can.Message(arbitration_id=1, is_extended_id=True)) diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index b69fb28da..a4869d51d 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -17,9 +17,11 @@ def producer(id, message_count=16): :param int id: the id of the thread/process """ - with can.Bus(bustype='socketcan', channel='vcan0') as bus: + with can.Bus(bustype="socketcan", channel="vcan0") as bus: for i in range(message_count): - msg = can.Message(arbitration_id=0x0cf02200+id, data=[id, i, 0, 1, 3, 1, 4, 1]) + msg = can.Message( + arbitration_id=0x0CF02200 + id, data=[id, i, 0, 1, 3, 1, 4, 1] + ) bus.send(msg) sleep(1.0) diff --git a/setup.py b/setup.py index 805b582be..354b63c9c 100644 --- a/setup.py +++ b/setup.py @@ -15,29 +15,27 @@ logging.basicConfig(level=logging.WARNING) -with open('can/__init__.py', 'r') as fd: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - fd.read(), re.MULTILINE).group(1) +with open("can/__init__.py", "r") as fd: + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE + ).group(1) -with open('README.rst', 'r') as f: +with open("README.rst", "r") as f: long_description = f.read() # Dependencies -extras_require = { - 'serial': ['pyserial~=3.0'], - 'neovi': ['python-ics>=2.12'] -} +extras_require = {"serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12"]} tests_require = [ - 'pytest~=4.3', - 'pytest-timeout~=1.3', - 'pytest-cov~=2.6', - 'codecov~=2.0', - 'six', - 'hypothesis' -] + extras_require['serial'] + "pytest~=4.3", + "pytest-timeout~=1.3", + "pytest-cov~=2.6", + "codecov~=2.0", + "six", + "hypothesis", +] + extras_require["serial"] -extras_require['test'] = tests_require +extras_require["test"] = tests_require setup( @@ -69,37 +67,32 @@ "Topic :: System :: Monitoring", "Topic :: System :: Networking", "Topic :: System :: Hardware :: Hardware Drivers", - "Topic :: Utilities" + "Topic :: Utilities", ], - # Code version=version, packages=find_packages(exclude=["test", "doc", "scripts", "examples"]), scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), - # Author author="Brian Thorne", author_email="brian@thorne.link", - # License license="LGPL v3", - # Package data package_data={ "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], "doc": ["*.*"], - "examples": ["*.py"] + "examples": ["*.py"], }, - # Installation # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=3.6", install_requires=[ - 'wrapt~=1.10', - 'aenum', + "wrapt~=1.10", + "aenum", 'windows-curses;platform_system=="Windows"', ], setup_requires=["pytest-runner"], extras_require=extras_require, - tests_require=tests_require + tests_require=tests_require, ) diff --git a/test/back2back_test.py b/test/back2back_test.py index e54ad93e4..b707988ec 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -27,30 +27,35 @@ class Back2BackTestCase(unittest.TestCase): BITRATE = 500000 TIMEOUT = 0.1 - INTERFACE_1 = 'virtual' - CHANNEL_1 = 'virtual_channel_0' - INTERFACE_2 = 'virtual' - CHANNEL_2 = 'virtual_channel_0' + INTERFACE_1 = "virtual" + CHANNEL_1 = "virtual_channel_0" + INTERFACE_2 = "virtual" + CHANNEL_2 = "virtual_channel_0" def setUp(self): - self.bus1 = can.Bus(channel=self.CHANNEL_1, - bustype=self.INTERFACE_1, - bitrate=self.BITRATE, - fd=TEST_CAN_FD, - single_handle=True) - self.bus2 = can.Bus(channel=self.CHANNEL_2, - bustype=self.INTERFACE_2, - bitrate=self.BITRATE, - fd=TEST_CAN_FD, - single_handle=True) + self.bus1 = can.Bus( + channel=self.CHANNEL_1, + bustype=self.INTERFACE_1, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + ) + self.bus2 = can.Bus( + channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + ) def tearDown(self): self.bus1.shutdown() self.bus2.shutdown() def _check_received_message(self, recv_msg, sent_msg): - self.assertIsNotNone(recv_msg, - "No message was received on %s" % self.INTERFACE_2) + self.assertIsNotNone( + recv_msg, "No message was received on %s" % self.INTERFACE_2 + ) self.assertEqual(recv_msg.arbitration_id, sent_msg.arbitration_id) self.assertEqual(recv_msg.is_extended_id, sent_msg.is_extended_id) self.assertEqual(recv_msg.is_remote_frame, sent_msg.is_remote_frame) @@ -79,7 +84,10 @@ def _send_and_receive(self, msg): def test_no_message(self): self.assertIsNone(self.bus1.recv(0.1)) - @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") + @unittest.skipIf( + IS_CI, + "the timing sensitive behaviour cannot be reproduced reliably on a CI server", + ) def test_timestamp(self): self.bus2.send(can.Message()) recv_msg1 = self.bus1.recv(self.TIMEOUT) @@ -87,98 +95,102 @@ def test_timestamp(self): self.bus2.send(can.Message()) recv_msg2 = self.bus1.recv(self.TIMEOUT) delta_time = recv_msg2.timestamp - recv_msg1.timestamp - self.assertTrue(1.75 <= delta_time <= 2.25, - 'Time difference should have been 2s +/- 250ms.' - 'But measured {}'.format(delta_time)) + self.assertTrue( + 1.75 <= delta_time <= 2.25, + "Time difference should have been 2s +/- 250ms." + "But measured {}".format(delta_time), + ) def test_standard_message(self): - msg = can.Message(is_extended_id=False, - arbitration_id=0x100, - data=[1, 2, 3, 4, 5, 6, 7, 8]) + msg = can.Message( + is_extended_id=False, arbitration_id=0x100, data=[1, 2, 3, 4, 5, 6, 7, 8] + ) self._send_and_receive(msg) def test_extended_message(self): - msg = can.Message(is_extended_id=True, - arbitration_id=0x123456, - data=[10, 11, 12, 13, 14, 15, 16, 17]) + msg = can.Message( + is_extended_id=True, + arbitration_id=0x123456, + data=[10, 11, 12, 13, 14, 15, 16, 17], + ) self._send_and_receive(msg) def test_remote_message(self): - msg = can.Message(is_extended_id=False, - arbitration_id=0x200, - is_remote_frame=True, - dlc=4) + msg = can.Message( + is_extended_id=False, arbitration_id=0x200, is_remote_frame=True, dlc=4 + ) self._send_and_receive(msg) def test_dlc_less_than_eight(self): - msg = can.Message(is_extended_id=False, - arbitration_id=0x300, - data=[4, 5, 6]) + msg = can.Message(is_extended_id=False, arbitration_id=0x300, data=[4, 5, 6]) self._send_and_receive(msg) @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message(self): - msg = can.Message(is_fd=True, - is_extended_id=True, - arbitration_id=0x56789, - data=[0xff] * 64) + msg = can.Message( + is_fd=True, is_extended_id=True, arbitration_id=0x56789, data=[0xFF] * 64 + ) self._send_and_receive(msg) @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message_with_brs(self): - msg = can.Message(is_fd=True, - bitrate_switch=True, - is_extended_id=True, - arbitration_id=0x98765, - data=[0xff] * 48) + msg = can.Message( + is_fd=True, + bitrate_switch=True, + is_extended_id=True, + arbitration_id=0x98765, + data=[0xFF] * 48, + ) self._send_and_receive(msg) @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class BasicTestSocketCan(Back2BackTestCase): - INTERFACE_1 = 'socketcan' - CHANNEL_1 = 'vcan0' - INTERFACE_2 = 'socketcan' - CHANNEL_2 = 'vcan0' + INTERFACE_1 = "socketcan" + CHANNEL_1 = "vcan0" + INTERFACE_2 = "socketcan" + CHANNEL_2 = "vcan0" @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class SocketCanBroadcastChannel(unittest.TestCase): - def setUp(self): - self.broadcast_bus = can.Bus(channel='', bustype='socketcan') - self.regular_bus = can.Bus(channel='vcan0', bustype='socketcan') + self.broadcast_bus = can.Bus(channel="", bustype="socketcan") + self.regular_bus = can.Bus(channel="vcan0", bustype="socketcan") def tearDown(self): self.broadcast_bus.shutdown() self.regular_bus.shutdown() def test_broadcast_channel(self): - self.broadcast_bus.send(can.Message(channel='vcan0')) + self.broadcast_bus.send(can.Message(channel="vcan0")) recv_msg = self.regular_bus.recv(1) self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 'vcan0') + self.assertEqual(recv_msg.channel, "vcan0") self.regular_bus.send(can.Message()) recv_msg = self.broadcast_bus.recv(1) self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 'vcan0') + self.assertEqual(recv_msg.channel, "vcan0") class TestThreadSafeBus(Back2BackTestCase): - def setUp(self): - self.bus1 = can.ThreadSafeBus(channel=self.CHANNEL_1, - bustype=self.INTERFACE_1, - bitrate=self.BITRATE, - fd=TEST_CAN_FD, - single_handle=True) - self.bus2 = can.ThreadSafeBus(channel=self.CHANNEL_2, - bustype=self.INTERFACE_2, - bitrate=self.BITRATE, - fd=TEST_CAN_FD, - single_handle=True) + self.bus1 = can.ThreadSafeBus( + channel=self.CHANNEL_1, + bustype=self.INTERFACE_1, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + ) + self.bus2 = can.ThreadSafeBus( + channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + ) @pytest.mark.timeout(5.0) def test_concurrent_writes(self): @@ -190,7 +202,7 @@ def test_concurrent_writes(self): channel=self.CHANNEL_1, is_extended_id=True, timestamp=121334.365, - data=[254, 255, 1, 2] + data=[254, 255, 1, 2], ) workload = 1000 * [message] @@ -221,19 +233,19 @@ def test_filtered_bus(self): channel=self.CHANNEL_1, is_extended_id=True, timestamp=121334.365, - data=[254, 255, 1, 2] + data=[254, 255, 1, 2], ) excluded_message = can.Message( arbitration_id=0x02, channel=self.CHANNEL_1, is_extended_id=True, timestamp=121334.300, - data=[1, 2, 3] + data=[1, 2, 3], ) workload = 500 * [included_message] + 500 * [excluded_message] random.shuffle(workload) - self.bus2.set_filters([{"can_id": 0x123, "can_mask": 0xff, "extended": True}]) + self.bus2.set_filters([{"can_id": 0x123, "can_mask": 0xFF, "extended": True}]) def sender(msg): self.bus1.send(msg) @@ -256,5 +268,5 @@ def receiver(_): receiver_pool.join() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/config.py b/test/config.py index 940ba7cf0..3a9072712 100644 --- a/test/config.py +++ b/test/config.py @@ -12,8 +12,8 @@ from os import environ as environment -def env(name): # type: bool - return environment.get(name, '').lower() in ("yes", "true", "t", "1") +def env(name): # type: bool + return environment.get(name, "").lower() in ("yes", "true", "t", "1") # ############################## Continuos integration @@ -22,13 +22,15 @@ def env(name): # type: bool # - https://docs.travis-ci.com/user/environment-variables/ # - https://www.appveyor.com/docs/environment-variables/ -IS_TRAVIS = env('TRAVIS') -IS_APPVEYOR = env('APPVEYOR') +IS_TRAVIS = env("TRAVIS") +IS_APPVEYOR = env("APPVEYOR") -IS_CI = IS_TRAVIS or IS_APPVEYOR or env('CI') or env('CONTINUOUS_INTEGRATION') +IS_CI = IS_TRAVIS or IS_APPVEYOR or env("CI") or env("CONTINUOUS_INTEGRATION") if IS_APPVEYOR and IS_TRAVIS: - raise EnvironmentError("IS_APPVEYOR and IS_TRAVIS cannot be both True at the same time") + raise EnvironmentError( + "IS_APPVEYOR and IS_TRAVIS cannot be both True at the same time" + ) # ############################## Platforms @@ -41,9 +43,11 @@ def env(name): # type: bool if (IS_WINDOWS and IS_LINUX) or (IS_LINUX and IS_OSX) or (IS_WINDOWS and IS_OSX): raise EnvironmentError( - "only one of IS_WINDOWS ({}), IS_LINUX ({}) and IS_OSX ({}) ".format(IS_WINDOWS, IS_LINUX, IS_OSX) + - "can be True at the same time " + - '(platform.system() == "{}")'.format(platform.system()) + "only one of IS_WINDOWS ({}), IS_LINUX ({}) and IS_OSX ({}) ".format( + IS_WINDOWS, IS_LINUX, IS_OSX + ) + + "can be True at the same time " + + '(platform.system() == "{}")'.format(platform.system()) ) @@ -51,4 +55,4 @@ def env(name): # type: bool TEST_CAN_FD = True -TEST_INTERFACE_SOCKETCAN = IS_LINUX and env('TEST_SOCKETCAN') +TEST_INTERFACE_SOCKETCAN = IS_LINUX and env("TEST_SOCKETCAN") diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py index 35bc045da..95785b128 100644 --- a/test/contextmanager_test.py +++ b/test/contextmanager_test.py @@ -10,13 +10,16 @@ class ContextManagerTest(unittest.TestCase): - def setUp(self): data = [0, 1, 2, 3, 4, 5, 6, 7] - self.msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data) + self.msg_send = can.Message( + is_extended_id=False, arbitration_id=0x100, data=data + ) def test_open_buses(self): - with can.Bus(interface='virtual') as bus_send, can.Bus(interface='virtual') as bus_recv: + with can.Bus(interface="virtual") as bus_send, can.Bus( + interface="virtual" + ) as bus_recv: bus_send.send(self.msg_send) msg_recv = bus_recv.recv() @@ -24,7 +27,9 @@ def test_open_buses(self): self.assertTrue(msg_recv) def test_use_closed_bus(self): - with can.Bus(interface='virtual') as bus_send, can.Bus(interface='virtual') as bus_recv: + with can.Bus(interface="virtual") as bus_send, can.Bus( + interface="virtual" + ) as bus_recv: bus_send.send(self.msg_send) # Receiving a frame after bus has been closed should raise a CanException @@ -32,5 +37,5 @@ def test_use_closed_bus(self): self.assertRaises(can.CanError, bus_send.send, self.msg_send) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/data/example_data.py b/test/data/example_data.py index dd433dc3c..b91fd3bfd 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -22,7 +22,7 @@ def sort_messages(messages): :param Iterable[can.Message] messages: a sequence of messages to sort :rtype: list """ - return list(sorted(messages, key=attrgetter('timestamp'))) + return list(sorted(messages, key=attrgetter("timestamp"))) # some random number @@ -30,125 +30,137 @@ def sort_messages(messages): # List of messages of different types that can be used in tests -TEST_MESSAGES_BASE = sort_messages([ - Message( - # empty - ), - Message( - # only data - data=[0x00, 0x42] - ), - Message( - # no data - arbitration_id=0xAB, is_extended_id=False - ), - Message( - # no data - arbitration_id=0x42, is_extended_id=True - ), - Message( - # no data - arbitration_id=0xABCDEF, - ), - Message( - # empty data - data=[] - ), - Message( - # empty data - data=[0xFF, 0xFE, 0xFD], - ), - Message( - # with channel as integer - channel=0, - ), - Message( - # with channel as integer - channel=42, - ), - Message( - # with channel as string - channel="vcan0", - ), - Message( - # with channel as string - channel="awesome_channel", - ), - Message( - arbitration_id=0xABCDEF, is_extended_id=True, - timestamp=TEST_TIME, - data=[1, 2, 3, 4, 5, 6, 7, 8] - ), - Message( - arbitration_id=0x123, is_extended_id=False, - timestamp=TEST_TIME + 42.42, - data=[0xff, 0xff] - ), - Message( - arbitration_id=0xDADADA, is_extended_id=True, - timestamp=TEST_TIME + .165, - data=[1, 2, 3, 4, 5, 6, 7, 8] - ), - Message( - arbitration_id=0x123, is_extended_id=False, - timestamp=TEST_TIME + .365, - data=[254, 255] - ), - Message( - arbitration_id=0x768, is_extended_id=False, - timestamp=TEST_TIME + 3.165 - ), -]) - - -TEST_MESSAGES_REMOTE_FRAMES = sort_messages([ - Message( - arbitration_id=0xDADADA, is_extended_id=True, is_remote_frame=True, - timestamp=TEST_TIME + .165, - ), - Message( - arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, - timestamp=TEST_TIME + .365, - ), - Message( - arbitration_id=0x768, is_extended_id=False, is_remote_frame=True, - timestamp=TEST_TIME + 3.165 - ), - Message( - arbitration_id=0xABCDEF, is_extended_id=True, is_remote_frame=True, - timestamp=TEST_TIME + 7858.67 - ), -]) - - -TEST_MESSAGES_ERROR_FRAMES = sort_messages([ - Message( - is_error_frame=True - ), - Message( - is_error_frame=True, - timestamp=TEST_TIME + 0.170 - ), - Message( - is_error_frame=True, - timestamp=TEST_TIME + 17.157 - ) -]) - - -TEST_ALL_MESSAGES = sort_messages(TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + \ - TEST_MESSAGES_ERROR_FRAMES) +TEST_MESSAGES_BASE = sort_messages( + [ + Message( + # empty + ), + Message( + # only data + data=[0x00, 0x42] + ), + Message( + # no data + arbitration_id=0xAB, + is_extended_id=False, + ), + Message( + # no data + arbitration_id=0x42, + is_extended_id=True, + ), + Message( + # no data + arbitration_id=0xABCDEF + ), + Message( + # empty data + data=[] + ), + Message( + # empty data + data=[0xFF, 0xFE, 0xFD] + ), + Message( + # with channel as integer + channel=0 + ), + Message( + # with channel as integer + channel=42 + ), + Message( + # with channel as string + channel="vcan0" + ), + Message( + # with channel as string + channel="awesome_channel" + ), + Message( + arbitration_id=0xABCDEF, + is_extended_id=True, + timestamp=TEST_TIME, + data=[1, 2, 3, 4, 5, 6, 7, 8], + ), + Message( + arbitration_id=0x123, + is_extended_id=False, + timestamp=TEST_TIME + 42.42, + data=[0xFF, 0xFF], + ), + Message( + arbitration_id=0xDADADA, + is_extended_id=True, + timestamp=TEST_TIME + 0.165, + data=[1, 2, 3, 4, 5, 6, 7, 8], + ), + Message( + arbitration_id=0x123, + is_extended_id=False, + timestamp=TEST_TIME + 0.365, + data=[254, 255], + ), + Message( + arbitration_id=0x768, is_extended_id=False, timestamp=TEST_TIME + 3.165 + ), + ] +) + + +TEST_MESSAGES_REMOTE_FRAMES = sort_messages( + [ + Message( + arbitration_id=0xDADADA, + is_extended_id=True, + is_remote_frame=True, + timestamp=TEST_TIME + 0.165, + ), + Message( + arbitration_id=0x123, + is_extended_id=False, + is_remote_frame=True, + timestamp=TEST_TIME + 0.365, + ), + Message( + arbitration_id=0x768, + is_extended_id=False, + is_remote_frame=True, + timestamp=TEST_TIME + 3.165, + ), + Message( + arbitration_id=0xABCDEF, + is_extended_id=True, + is_remote_frame=True, + timestamp=TEST_TIME + 7858.67, + ), + ] +) + + +TEST_MESSAGES_ERROR_FRAMES = sort_messages( + [ + Message(is_error_frame=True), + Message(is_error_frame=True, timestamp=TEST_TIME + 0.170), + Message(is_error_frame=True, timestamp=TEST_TIME + 17.157), + ] +) + + +TEST_ALL_MESSAGES = sort_messages( + TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + TEST_MESSAGES_ERROR_FRAMES +) TEST_COMMENTS = [ "This is the first comment", - "", # empty comment + "", # empty comment "This third comment contains some strange characters: 'ä\"§$%&/()=?__::_Öüßêè and ends here.", ( - "This fourth comment is quite long! " \ - "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. " \ - "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. " \ - "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi." \ + "This fourth comment is quite long! " + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. " + "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. " + "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi." ), ] @@ -159,4 +171,9 @@ def generate_message(arbitration_id): and a non-extended ID. """ data = bytearray([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) - return Message(arbitration_id=arbitration_id, data=data, is_extended_id=False, timestamp=TEST_TIME) + return Message( + arbitration_id=arbitration_id, + data=data, + is_extended_id=False, + timestamp=TEST_TIME, + ) diff --git a/test/listener_test.py b/test/listener_test.py index 840f687fb..c44acc7a6 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -17,8 +17,8 @@ from .data.example_data import generate_message -channel = 'virtual_channel_0' -can.rc['interface'] = 'virtual' +channel = "virtual_channel_0" +can.rc["interface"] = "virtual" logging.basicConfig(level=logging.DEBUG) @@ -27,37 +27,35 @@ class ListenerImportTest(unittest.TestCase): - def testClassesImportable(self): - self.assertTrue(hasattr(can, 'Listener')) - self.assertTrue(hasattr(can, 'BufferedReader')) - self.assertTrue(hasattr(can, 'Notifier')) - self.assertTrue(hasattr(can, 'Logger')) + self.assertTrue(hasattr(can, "Listener")) + self.assertTrue(hasattr(can, "BufferedReader")) + self.assertTrue(hasattr(can, "Notifier")) + self.assertTrue(hasattr(can, "Logger")) - self.assertTrue(hasattr(can, 'ASCWriter')) - self.assertTrue(hasattr(can, 'ASCReader')) + self.assertTrue(hasattr(can, "ASCWriter")) + self.assertTrue(hasattr(can, "ASCReader")) - self.assertTrue(hasattr(can, 'BLFReader')) - self.assertTrue(hasattr(can, 'BLFWriter')) + self.assertTrue(hasattr(can, "BLFReader")) + self.assertTrue(hasattr(can, "BLFWriter")) - self.assertTrue(hasattr(can, 'CSVReader')) - self.assertTrue(hasattr(can, 'CSVWriter')) + self.assertTrue(hasattr(can, "CSVReader")) + self.assertTrue(hasattr(can, "CSVWriter")) - self.assertTrue(hasattr(can, 'CanutilsLogWriter')) - self.assertTrue(hasattr(can, 'CanutilsLogReader')) + self.assertTrue(hasattr(can, "CanutilsLogWriter")) + self.assertTrue(hasattr(can, "CanutilsLogReader")) - self.assertTrue(hasattr(can, 'SqliteReader')) - self.assertTrue(hasattr(can, 'SqliteWriter')) + self.assertTrue(hasattr(can, "SqliteReader")) + self.assertTrue(hasattr(can, "SqliteWriter")) - self.assertTrue(hasattr(can, 'Printer')) + self.assertTrue(hasattr(can, "Printer")) - self.assertTrue(hasattr(can, 'LogReader')) + self.assertTrue(hasattr(can, "LogReader")) - self.assertTrue(hasattr(can, 'MessageSync')) + self.assertTrue(hasattr(can, "MessageSync")) class BusTest(unittest.TestCase): - def setUp(self): self.bus = can.interface.Bus() @@ -66,7 +64,6 @@ def tearDown(self): class ListenerTest(BusTest): - def testBasicListenerCanBeAddedToNotifier(self): a_listener = can.Printer() notifier = can.Notifier(self.bus, [a_listener], 0.1) @@ -98,7 +95,9 @@ def test_filetype_to_instance(extension, klass): file_handler = open(join(dirname(__file__), "data/logfile.blf")) else: delete = True - file_handler = tempfile.NamedTemporaryFile(suffix=extension, delete=False) + file_handler = tempfile.NamedTemporaryFile( + suffix=extension, delete=False + ) with file_handler as my_file: filename = my_file.name @@ -111,7 +110,7 @@ def test_filetype_to_instance(extension, klass): test_filetype_to_instance(".asc", can.ASCReader) test_filetype_to_instance(".blf", can.BLFReader) test_filetype_to_instance(".csv", can.CSVReader) - test_filetype_to_instance(".db" , can.SqliteReader) + test_filetype_to_instance(".db", can.SqliteReader) test_filetype_to_instance(".log", can.CanutilsLogReader) # test file extensions that are not supported @@ -122,7 +121,9 @@ def testLoggerTypeResolution(self): def test_filetype_to_instance(extension, klass): print("testing: {}".format(extension)) try: - with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as my_file: + with tempfile.NamedTemporaryFile( + suffix=extension, delete=False + ) as my_file: filename = my_file.name with can.Logger(filename) as writer: self.assertIsInstance(writer, klass) @@ -132,7 +133,7 @@ def test_filetype_to_instance(extension, klass): test_filetype_to_instance(".asc", can.ASCWriter) test_filetype_to_instance(".blf", can.BLFWriter) test_filetype_to_instance(".csv", can.CSVWriter) - test_filetype_to_instance(".db" , can.SqliteWriter) + test_filetype_to_instance(".db", can.SqliteWriter) test_filetype_to_instance(".log", can.CanutilsLogWriter) test_filetype_to_instance(".txt", can.Printer) @@ -152,5 +153,5 @@ def testBufferedListenerReceives(self): self.assertIsNotNone(a_listener.get_message(0.1)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/logformats_test.py b/test/logformats_test.py index c393f4ea0..46eded869 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -23,9 +23,13 @@ import can -from .data.example_data import TEST_MESSAGES_BASE, TEST_MESSAGES_REMOTE_FRAMES, \ - TEST_MESSAGES_ERROR_FRAMES, TEST_COMMENTS, \ - sort_messages +from .data.example_data import ( + TEST_MESSAGES_BASE, + TEST_MESSAGES_REMOTE_FRAMES, + TEST_MESSAGES_ERROR_FRAMES, + TEST_COMMENTS, + sort_messages, +) from .message_helper import ComparingMessagesTestCase logging.basicConfig(level=logging.DEBUG) @@ -51,12 +55,20 @@ def _setup_instance(self): """Hook for subclasses.""" raise NotImplementedError() - def _setup_instance_helper(self, - writer_constructor, reader_constructor, binary_file=False, - check_remote_frames=True, check_error_frames=True, check_fd=True, - check_comments=False, test_append=False, - allowed_timestamp_delta=0.0, - preserves_channel=True, adds_default_channel=None): + def _setup_instance_helper( + self, + writer_constructor, + reader_constructor, + binary_file=False, + check_remote_frames=True, + check_error_frames=True, + check_fd=True, + check_comments=False, + test_append=False, + allowed_timestamp_delta=0.0, + preserves_channel=True, + adds_default_channel=None, + ): """ :param Callable writer_constructor: the constructor of the writer class :param Callable reader_constructor: the constructor of the reader class @@ -83,7 +95,7 @@ def _setup_instance_helper(self, if check_error_frames: self.original_messages += TEST_MESSAGES_ERROR_FRAMES if check_fd: - self.original_messages += [] # TODO: add TEST_MESSAGES_CAN_FD + self.original_messages += [] # TODO: add TEST_MESSAGES_CAN_FD # sort them so that for example ASCWriter does not "fix" any messages with timestamp 0.0 self.original_messages = sort_messages(self.original_messages) @@ -92,9 +104,12 @@ def _setup_instance_helper(self, # we check this because of the lack of a common base class # we filter for not starts with '__' so we do not get all the builtin # methods when logging to the console - attrs = [attr for attr in dir(writer_constructor) if not attr.startswith('__')] - assert 'log_event' in attrs, \ - "cannot check comments with this writer: {}".format(writer_constructor) + attrs = [ + attr for attr in dir(writer_constructor) if not attr.startswith("__") + ] + assert ( + "log_event" in attrs + ), "cannot check comments with this writer: {}".format(writer_constructor) # get all test comments self.original_comments = TEST_COMMENTS if check_comments else () @@ -104,13 +119,15 @@ def _setup_instance_helper(self, self.binary_file = binary_file self.test_append_enabled = test_append - ComparingMessagesTestCase.__init__(self, + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=allowed_timestamp_delta, - preserves_channel=preserves_channel) - #adds_default_channel=adds_default_channel # TODO inlcude in tests + preserves_channel=preserves_channel, + ) + # adds_default_channel=adds_default_channel # TODO inlcude in tests def setUp(self): - with tempfile.NamedTemporaryFile('w+', delete=False) as test_file: + with tempfile.NamedTemporaryFile("w+", delete=False) as test_file: self.test_file_name = test_file.name def tearDown(self): @@ -126,7 +143,7 @@ def test_path_like_explicit_stop(self): self._write_all(writer) self._ensure_fsync(writer) writer.stop() - if hasattr(writer.file, 'closed'): + if hasattr(writer.file, "closed"): self.assertTrue(writer.file.closed) print("reading all messages") @@ -134,13 +151,16 @@ def test_path_like_explicit_stop(self): read_messages = list(reader) # redundant, but this checks if stop() can be called multiple times reader.stop() - if hasattr(writer.file, 'closed'): + if hasattr(writer.file, "closed"): self.assertTrue(writer.file.closed) # check if at least the number of messages matches # could use assertCountEqual in later versions of Python and in the other methods - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) self.assertIncludesComments(self.test_file_name) @@ -154,7 +174,7 @@ def test_path_like_context_manager(self): self._write_all(writer) self._ensure_fsync(writer) w = writer - if hasattr(w.file, 'closed'): + if hasattr(w.file, "closed"): self.assertTrue(w.file.closed) # read all written messages @@ -162,12 +182,15 @@ def test_path_like_context_manager(self): with self.reader_constructor(self.test_file_name) as reader: read_messages = list(reader) r = reader - if hasattr(r.file, 'closed'): + if hasattr(r.file, "closed"): self.assertTrue(r.file.closed) - # check if at least the number of messages matches; - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + # check if at least the number of messages matches; + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) self.assertIncludesComments(self.test_file_name) @@ -177,27 +200,30 @@ def test_file_like_explicit_stop(self): # create writer print("writing all messages/comments") - my_file = open(self.test_file_name, 'wb' if self.binary_file else 'w') + my_file = open(self.test_file_name, "wb" if self.binary_file else "w") writer = self.writer_constructor(my_file) self._write_all(writer) self._ensure_fsync(writer) writer.stop() - if hasattr(my_file, 'closed'): + if hasattr(my_file, "closed"): self.assertTrue(my_file.closed) print("reading all messages") - my_file = open(self.test_file_name, 'rb' if self.binary_file else 'r') + my_file = open(self.test_file_name, "rb" if self.binary_file else "r") reader = self.reader_constructor(my_file) read_messages = list(reader) # redundant, but this checks if stop() can be called multiple times reader.stop() - if hasattr(my_file, 'closed'): + if hasattr(my_file, "closed"): self.assertTrue(my_file.closed) # check if at least the number of messages matches # could use assertCountEqual in later versions of Python and in the other methods - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) self.assertIncludesComments(self.test_file_name) @@ -207,26 +233,29 @@ def test_file_like_context_manager(self): # create writer print("writing all messages/comments") - my_file = open(self.test_file_name, 'wb' if self.binary_file else 'w') + my_file = open(self.test_file_name, "wb" if self.binary_file else "w") with self.writer_constructor(my_file) as writer: self._write_all(writer) self._ensure_fsync(writer) w = writer - if hasattr(my_file, 'closed'): + if hasattr(my_file, "closed"): self.assertTrue(my_file.closed) # read all written messages print("reading all messages") - my_file = open(self.test_file_name, 'rb' if self.binary_file else 'r') + my_file = open(self.test_file_name, "rb" if self.binary_file else "r") with self.reader_constructor(my_file) as reader: read_messages = list(reader) r = reader - if hasattr(my_file, 'closed'): + if hasattr(my_file, "closed"): self.assertTrue(my_file.closed) - # check if at least the number of messages matches; - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + # check if at least the number of messages matches; + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) self.assertIncludesComments(self.test_file_name) @@ -239,8 +268,8 @@ def test_append_mode(self): raise unittest.SkipTest("do not test append mode") count = len(self.original_messages) - first_part = self.original_messages[:count // 2] - second_part = self.original_messages[count // 2:] + first_part = self.original_messages[: count // 2] + second_part = self.original_messages[count // 2 :] # write first half with self.writer_constructor(self.test_file_name) as writer: @@ -270,17 +299,19 @@ def test_append_mode(self): def _write_all(self, writer): """Writes messages and insert comments here and there.""" # Note: we make no assumptions about the length of original_messages and original_comments - for msg, comment in zip_longest(self.original_messages, self.original_comments, fillvalue=None): + for msg, comment in zip_longest( + self.original_messages, self.original_comments, fillvalue=None + ): # msg and comment might be None if comment is not None: print("writing comment: ", comment) - writer.log_event(comment) # we already know that this method exists + writer.log_event(comment) # we already know that this method exists if msg is not None: print("writing message: ", msg) writer(msg) def _ensure_fsync(self, io_handler): - if hasattr(io_handler.file, 'fileno'): + if hasattr(io_handler.file, "fileno"): io_handler.file.flush() os.fsync(io_handler.file.fileno()) @@ -292,7 +323,7 @@ def assertIncludesComments(self, filename): """ if self.original_comments: # read the entire outout file - with open(filename, 'rb' if self.binary_file else 'r') as file: + with open(filename, "rb" if self.binary_file else "r") as file: output_contents = file.read() # check each, if they can be found in there literally for comment in self.original_comments: @@ -304,10 +335,12 @@ class TestAscFileFormat(ReaderWriterTest): def _setup_instance(self): super()._setup_instance_helper( - can.ASCWriter, can.ASCReader, + can.ASCWriter, + can.ASCReader, check_fd=False, check_comments=True, - preserves_channel=False, adds_default_channel=0 + preserves_channel=False, + adds_default_channel=0, ) @@ -316,12 +349,14 @@ class TestBlfFileFormat(ReaderWriterTest): def _setup_instance(self): super()._setup_instance_helper( - can.BLFWriter, can.BLFReader, + can.BLFWriter, + can.BLFReader, binary_file=True, check_fd=False, check_comments=False, allowed_timestamp_delta=1.0e-6, - preserves_channel=False, adds_default_channel=0 + preserves_channel=False, + adds_default_channel=0, ) def test_read_known_file(self): @@ -334,12 +369,14 @@ def test_read_known_file(self): timestamp=1.0, is_extended_id=False, arbitration_id=0x64, - data=[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]), + data=[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8], + ), can.Message( timestamp=73.0, is_extended_id=True, arbitration_id=0x1FFFFFFF, - is_error_frame=True,) + is_error_frame=True, + ), ] self.assertMessagesEqual(messages, expected) @@ -350,10 +387,13 @@ class TestCanutilsFileFormat(ReaderWriterTest): def _setup_instance(self): super()._setup_instance_helper( - can.CanutilsLogWriter, can.CanutilsLogReader, + can.CanutilsLogWriter, + can.CanutilsLogReader, check_fd=False, - test_append=True, check_comments=False, - preserves_channel=False, adds_default_channel='vcan0' + test_append=True, + check_comments=False, + preserves_channel=False, + adds_default_channel="vcan0", ) @@ -362,10 +402,13 @@ class TestCsvFileFormat(ReaderWriterTest): def _setup_instance(self): super()._setup_instance_helper( - can.CSVWriter, can.CSVReader, + can.CSVWriter, + can.CSVReader, check_fd=False, - test_append=True, check_comments=False, - preserves_channel=False, adds_default_channel=None + test_append=True, + check_comments=False, + preserves_channel=False, + adds_default_channel=None, ) @@ -374,10 +417,13 @@ class TestSqliteDatabaseFormat(ReaderWriterTest): def _setup_instance(self): super()._setup_instance_helper( - can.SqliteWriter, can.SqliteReader, + can.SqliteWriter, + can.SqliteReader, check_fd=False, - test_append=True, check_comments=False, - preserves_channel=False, adds_default_channel=None + test_append=True, + check_comments=False, + preserves_channel=False, + adds_default_channel=None, ) @unittest.skip("not implemented") @@ -402,9 +448,12 @@ def test_read_all(self): with self.reader_constructor(self.test_file_name) as reader: read_messages = list(reader.read_all()) - # check if at least the number of messages matches; - self.assertEqual(len(read_messages), len(self.original_messages), - "the number of written messages does not match the number of read messages") + # check if at least the number of messages matches; + self.assertEqual( + len(read_messages), + len(self.original_messages), + "the number of written messages does not match the number of read messages", + ) self.assertMessagesEqual(self.original_messages, read_messages) @@ -413,7 +462,9 @@ class TestPrinter(unittest.TestCase): """Tests that can.Printer does not crash""" # TODO add CAN FD messages - messages = TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + TEST_MESSAGES_ERROR_FRAMES + messages = ( + TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + TEST_MESSAGES_ERROR_FRAMES + ) def test_not_crashes_with_stdout(self): with can.Printer() as printer: @@ -421,15 +472,15 @@ def test_not_crashes_with_stdout(self): printer(message) def test_not_crashes_with_file(self): - with tempfile.NamedTemporaryFile('w', delete=False) as temp_file: + with tempfile.NamedTemporaryFile("w", delete=False) as temp_file: with can.Printer(temp_file) as printer: for message in self.messages: printer(message) # this excludes the base class from being executed as a test case itself -del(ReaderWriterTest) +del ReaderWriterTest -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/message_helper.py b/test/message_helper.py index 8fb94f48b..5f437dbde 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -34,24 +34,35 @@ def assertMessageEqual(self, message_1, message_2): elif self.preserves_channel: print("Comparing: message 1: {!r}".format(message_1)) print(" message 2: {!r}".format(message_2)) - self.fail("messages are unequal with allowed timestamp delta {}".format(self.allowed_timestamp_delta)) + self.fail( + "messages are unequal with allowed timestamp delta {}".format( + self.allowed_timestamp_delta + ) + ) else: - message_2 = copy(message_2) # make sure this method is pure + message_2 = copy(message_2) # make sure this method is pure message_2.channel = message_1.channel - if message_1.equals(message_2, timestamp_delta=self.allowed_timestamp_delta): + if message_1.equals( + message_2, timestamp_delta=self.allowed_timestamp_delta + ): return else: print("Comparing: message 1: {!r}".format(message_1)) print(" message 2: {!r}".format(message_2)) - self.fail("messages are unequal with allowed timestamp delta {} even when ignoring channels" \ - .format(self.allowed_timestamp_delta)) + self.fail( + "messages are unequal with allowed timestamp delta {} even when ignoring channels".format( + self.allowed_timestamp_delta + ) + ) def assertMessagesEqual(self, messages_1, messages_2): """ Checks the order and content of the individual messages pairwise. Raises an error if the lengths of the sequences are not equal. """ - self.assertEqual(len(messages_1), len(messages_2), "the number of messages differs") + self.assertEqual( + len(messages_1), len(messages_2), "the number of messages differs" + ) for message_1, message_2 in zip(messages_1, messages_2): self.assertMessageEqual(message_1, message_2) diff --git a/test/network_test.py b/test/network_test.py index 1af102ec3..2ee4795fb 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -15,11 +15,11 @@ import can -channel = 'vcan0' -can.rc['interface'] = 'virtual' +channel = "vcan0" +can.rc["interface"] = "virtual" -@unittest.skipIf('interface' not in can.rc, "Need a CAN interface") +@unittest.skipIf("interface" not in can.rc, "Need a CAN interface") class ControllerAreaNetworkTestCase(unittest.TestCase): """ This test ensures that what messages go in to the bus is what comes out. @@ -39,9 +39,10 @@ class ControllerAreaNetworkTestCase(unittest.TestCase): extended_flags = [rbool() for _ in range(num_messages)] ids = list(range(num_messages)) - data = list(bytearray([random.randrange(0, 2 ** 8 - 1) - for a in range(random.randrange(9))]) - for b in range(num_messages)) + data = list( + bytearray([random.randrange(0, 2 ** 8 - 1) for a in range(random.randrange(9))]) + for b in range(num_messages) + ) def producer(self, ready_event, msg_read): self.client_bus = can.interface.Bus(channel=channel) @@ -52,9 +53,9 @@ def producer(self, ready_event, msg_read): is_remote_frame=self.remote_flags[i], is_error_frame=self.error_flags[i], is_extended_id=self.extended_flags[i], - data=self.data[i] + data=self.data[i], ) - #logging.debug("writing message: {}".format(m)) + # logging.debug("writing message: {}".format(m)) if msg_read is not None: # Don't send until the other thread is ready msg_read.wait() @@ -95,7 +96,7 @@ def testProducerConsumer(self): msg_read.set() msg = self.server_bus.recv(timeout=0.5) self.assertIsNotNone(msg, "Didn't receive a message") - #logging.debug("Received message {} with data: {}".format(i, msg.data)) + # logging.debug("Received message {} with data: {}".format(i, msg.data)) self.assertEqual(msg.is_extended_id, self.extended_flags[i]) if not msg.is_remote_frame: @@ -111,5 +112,6 @@ def testProducerConsumer(self): self.server_bus.flush_tx_buffer() self.server_bus.shutdown() -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/test/notifier_test.py b/test/notifier_test.py index 71ac7a944..0a60bd25d 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -9,9 +9,8 @@ class NotifierTest(unittest.TestCase): - def test_single_bus(self): - bus = can.Bus('test', bustype='virtual', receive_own_messages=True) + bus = can.Bus("test", bustype="virtual", receive_own_messages=True) reader = can.BufferedReader() notifier = can.Notifier(bus, [reader], 0.1) msg = can.Message() @@ -21,8 +20,8 @@ def test_single_bus(self): bus.shutdown() def test_multiple_bus(self): - bus1 = can.Bus(0, bustype='virtual', receive_own_messages=True) - bus2 = can.Bus(1, bustype='virtual', receive_own_messages=True) + bus1 = can.Bus(0, bustype="virtual", receive_own_messages=True) + bus2 = can.Bus(1, bustype="virtual", receive_own_messages=True) reader = can.BufferedReader() notifier = can.Notifier([bus1, bus2], [reader], 0.1) msg = can.Message() @@ -41,10 +40,9 @@ def test_multiple_bus(self): class AsyncNotifierTest(unittest.TestCase): - def test_asyncio_notifier(self): loop = asyncio.get_event_loop() - bus = can.Bus('test', bustype='virtual', receive_own_messages=True) + bus = can.Bus("test", bustype="virtual", receive_own_messages=True) reader = can.AsyncBufferedReader() notifier = can.Notifier(bus, [reader], 0.1, loop=loop) msg = can.Message() @@ -56,5 +54,5 @@ def test_asyncio_notifier(self): bus.shutdown() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/serial_test.py b/test/serial_test.py index a29379511..d0e11f974 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -20,6 +20,7 @@ class SerialDummy: """ Dummy to mock the serial communication """ + msg = None def __init__(self): @@ -43,7 +44,9 @@ class SimpleSerialTestBase(ComparingMessagesTestCase): MAX_TIMESTAMP = 0xFFFFFFFF / 1000 def __init__(self): - ComparingMessagesTestCase.__init__(self, allowed_timestamp_delta=None, preserves_channel=True) + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=None, preserves_channel=True + ) def test_rx_tx_min_max_data(self): """ @@ -109,7 +112,7 @@ def test_rx_tx_max_timestamp_error(self): """ Tests for an exception with an out of range timestamp (max + 1) """ - msg = can.Message(timestamp=self.MAX_TIMESTAMP+1) + msg = can.Message(timestamp=self.MAX_TIMESTAMP + 1) self.assertRaises(ValueError, self.bus.send, msg) def test_rx_tx_min_timestamp(self): @@ -131,36 +134,34 @@ def test_rx_tx_min_timestamp_error(self): class SimpleSerialTest(unittest.TestCase, SimpleSerialTestBase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) SimpleSerialTestBase.__init__(self) def setUp(self): - self.patcher = patch('serial.Serial') + self.patcher = patch("serial.Serial") self.mock_serial = self.patcher.start() self.serial_dummy = SerialDummy() self.mock_serial.return_value.write = self.serial_dummy.write self.mock_serial.return_value.read = self.serial_dummy.read self.addCleanup(self.patcher.stop) - self.bus = SerialBus('bus') + self.bus = SerialBus("bus") def tearDown(self): self.serial_dummy.reset() class SimpleSerialLoopTest(unittest.TestCase, SimpleSerialTestBase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) SimpleSerialTestBase.__init__(self) def setUp(self): - self.bus = SerialBus('loop://') + self.bus = SerialBus("loop://") def tearDown(self): self.bus.shutdown() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 06712e921..95bbd0a99 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -16,17 +16,23 @@ class SimpleCyclicSendTaskTest(unittest.TestCase, ComparingMessagesTestCase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) - ComparingMessagesTestCase.__init__(self, allowed_timestamp_delta=0.016, preserves_channel=True) - - @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=0.016, preserves_channel=True + ) + + @unittest.skipIf( + IS_CI, + "the timing sensitive behaviour cannot be reproduced reliably on a CI server", + ) def test_cycle_time(self): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message( + is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] + ) - with can.interface.Bus(bustype='virtual') as bus1: - with can.interface.Bus(bustype='virtual') as bus2: + with can.interface.Bus(bustype="virtual") as bus1: + with can.interface.Bus(bustype="virtual") as bus2: # disabling the garbage collector makes the time readings more reliable gc.disable() @@ -37,8 +43,12 @@ def test_cycle_time(self): sleep(2) size = bus2.queue.qsize() # About 100 messages should have been transmitted - self.assertTrue(80 <= size <= 120, - '100 +/- 20 messages should have been transmitted. But queue contained {}'.format(size)) + self.assertTrue( + 80 <= size <= 120, + "100 +/- 20 messages should have been transmitted. But queue contained {}".format( + size + ), + ) last_msg = bus2.recv() next_last_msg = bus2.recv() @@ -57,10 +67,14 @@ def test_cycle_time(self): self.assertMessageEqual(msg, last_msg) def test_removing_bus_tasks(self): - bus = can.interface.Bus(bustype='virtual') + bus = can.interface.Bus(bustype="virtual") tasks = [] for task_i in range(10): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 1) tasks.append(task) @@ -76,10 +90,14 @@ def test_removing_bus_tasks(self): bus.shutdown() def test_managed_tasks(self): - bus = can.interface.Bus(bustype='virtual', receive_own_messages=True) + bus = can.interface.Bus(bustype="virtual", receive_own_messages=True) tasks = [] for task_i in range(3): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 10, store_task=False) tasks.append(task) @@ -102,10 +120,14 @@ def test_managed_tasks(self): bus.shutdown() def test_stopping_perodic_tasks(self): - bus = can.interface.Bus(bustype='virtual') + bus = can.interface.Bus(bustype="virtual") tasks = [] for task_i in range(10): - msg = can.Message(is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7]) + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) msg.arbitration_id = task_i task = bus.send_periodic(msg, 0.1, 1) tasks.append(task) @@ -130,5 +152,5 @@ def test_stopping_perodic_tasks(self): bus.shutdown() -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py index 69addafb3..0d94e31f1 100644 --- a/test/test_detect_available_configs.py +++ b/test/test_detect_available_configs.py @@ -15,40 +15,41 @@ class TestDetectAvailableConfigs(unittest.TestCase): - def test_count_returned(self): # At least virtual has to always return at least one interface - self.assertGreaterEqual (len(detect_available_configs() ), 1) - self.assertEqual (len(detect_available_configs(interfaces=[]) ), 0) - self.assertGreaterEqual (len(detect_available_configs(interfaces='virtual') ), 1) - self.assertGreaterEqual (len(detect_available_configs(interfaces=['virtual']) ), 1) - self.assertGreaterEqual (len(detect_available_configs(interfaces=None) ), 1) + self.assertGreaterEqual(len(detect_available_configs()), 1) + self.assertEqual(len(detect_available_configs(interfaces=[])), 0) + self.assertGreaterEqual(len(detect_available_configs(interfaces="virtual")), 1) + self.assertGreaterEqual( + len(detect_available_configs(interfaces=["virtual"])), 1 + ) + self.assertGreaterEqual(len(detect_available_configs(interfaces=None)), 1) def test_general_values(self): configs = detect_available_configs() for config in configs: - self.assertIn('interface', config) - self.assertIn('channel', config) - self.assertIsInstance(config['interface'], str) + self.assertIn("interface", config) + self.assertIn("channel", config) + self.assertIsInstance(config["interface"], str) def test_content_virtual(self): - configs = detect_available_configs(interfaces='virtual') + configs = detect_available_configs(interfaces="virtual") for config in configs: - self.assertEqual(config['interface'], 'virtual') + self.assertEqual(config["interface"], "virtual") def test_content_socketcan(self): - configs = detect_available_configs(interfaces='socketcan') + configs = detect_available_configs(interfaces="socketcan") for config in configs: - self.assertEqual(config['interface'], 'socketcan') + self.assertEqual(config["interface"], "socketcan") @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "socketcan is not tested") def test_socketcan_on_ci_server(self): - configs = detect_available_configs(interfaces='socketcan') + configs = detect_available_configs(interfaces="socketcan") self.assertGreaterEqual(len(configs), 1) - self.assertIn('vcan0', [config['channel'] for config in configs]) + self.assertIn("vcan0", [config["channel"] for config in configs]) # see TestSocketCanHelpers.test_find_available_interfaces() too -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 106ce7dc5..733bbc367 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -18,7 +18,6 @@ class KvaserTest(unittest.TestCase): - def setUp(self): canlib.canGetNumberOfChannels = KvaserTest.canGetNumberOfChannels canlib.canOpenChannel = Mock(return_value=0) @@ -40,7 +39,7 @@ def setUp(self): self.msg = {} self.msg_in_cue = None - self.bus = can.Bus(channel=0, bustype='kvaser') + self.bus = can.Bus(channel=0, bustype="kvaser") def tearDown(self): if self.bus: @@ -60,67 +59,58 @@ def test_bus_shutdown(self): def test_filter_setup(self): # No filter in constructor expected_args = [ - ((0, 0, 0, 0),), # Disable filtering STD on read handle - ((0, 0, 0, 1),), # Disable filtering EXT on read handle - ((0, 0, 0, 0),), # Disable filtering STD on write handle - ((0, 0, 0, 1),), # Disable filtering EXT on write handle + ((0, 0, 0, 0),), # Disable filtering STD on read handle + ((0, 0, 0, 1),), # Disable filtering EXT on read handle + ((0, 0, 0, 0),), # Disable filtering STD on write handle + ((0, 0, 0, 1),), # Disable filtering EXT on write handle ] - self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, - expected_args) + self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, expected_args) # One filter, will be handled by canlib canlib.canSetAcceptanceFilter.reset_mock() - self.bus.set_filters([ - {'can_id': 0x8, 'can_mask': 0xff, 'extended': True} - ]) + self.bus.set_filters([{"can_id": 0x8, "can_mask": 0xFF, "extended": True}]) expected_args = [ - ((0, 0x8, 0xff, 1),), # Enable filtering EXT on read handle - ((0, 0x8, 0xff, 1),), # Enable filtering EXT on write handle + ((0, 0x8, 0xFF, 1),), # Enable filtering EXT on read handle + ((0, 0x8, 0xFF, 1),), # Enable filtering EXT on write handle ] - self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, - expected_args) + self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, expected_args) # Multiple filters, will be handled in Python canlib.canSetAcceptanceFilter.reset_mock() multiple_filters = [ - {'can_id': 0x8, 'can_mask': 0xff}, - {'can_id': 0x9, 'can_mask': 0xff} + {"can_id": 0x8, "can_mask": 0xFF}, + {"can_id": 0x9, "can_mask": 0xFF}, ] self.bus.set_filters(multiple_filters) expected_args = [ - ((0, 0, 0, 0),), # Disable filtering STD on read handle - ((0, 0, 0, 1),), # Disable filtering EXT on read handle - ((0, 0, 0, 0),), # Disable filtering STD on write handle - ((0, 0, 0, 1),), # Disable filtering EXT on write handle + ((0, 0, 0, 0),), # Disable filtering STD on read handle + ((0, 0, 0, 1),), # Disable filtering EXT on read handle + ((0, 0, 0, 0),), # Disable filtering STD on write handle + ((0, 0, 0, 1),), # Disable filtering EXT on write handle ] - self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, - expected_args) + self.assertEqual(canlib.canSetAcceptanceFilter.call_args_list, expected_args) def test_send_extended(self): msg = can.Message( - arbitration_id=0xc0ffee, - data=[0, 25, 0, 1, 3, 1, 4], - is_extended_id=True) + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4], is_extended_id=True + ) self.bus.send(msg) - self.assertEqual(self.msg['arb_id'], 0xc0ffee) - self.assertEqual(self.msg['dlc'], 7) - self.assertEqual(self.msg['flags'], constants.canMSG_EXT) - self.assertSequenceEqual(self.msg['data'], [0, 25, 0, 1, 3, 1, 4]) + self.assertEqual(self.msg["arb_id"], 0xC0FFEE) + self.assertEqual(self.msg["dlc"], 7) + self.assertEqual(self.msg["flags"], constants.canMSG_EXT) + self.assertSequenceEqual(self.msg["data"], [0, 25, 0, 1, 3, 1, 4]) def test_send_standard(self): - msg = can.Message( - arbitration_id=0x321, - data=[50, 51], - is_extended_id=False) + msg = can.Message(arbitration_id=0x321, data=[50, 51], is_extended_id=False) self.bus.send(msg) - self.assertEqual(self.msg['arb_id'], 0x321) - self.assertEqual(self.msg['dlc'], 2) - self.assertEqual(self.msg['flags'], constants.canMSG_STD) - self.assertSequenceEqual(self.msg['data'], [50, 51]) + self.assertEqual(self.msg["arb_id"], 0x321) + self.assertEqual(self.msg["dlc"], 2) + self.assertEqual(self.msg["flags"], constants.canMSG_STD) + self.assertSequenceEqual(self.msg["data"], [50, 51]) @pytest.mark.timeout(3.0) def test_recv_no_message(self): @@ -128,13 +118,12 @@ def test_recv_no_message(self): def test_recv_extended(self): self.msg_in_cue = can.Message( - arbitration_id=0xc0ffef, - data=[1, 2, 3, 4, 5, 6, 7, 8], - is_extended_id=True) + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) now = time.time() msg = self.bus.recv() - self.assertEqual(msg.arbitration_id, 0xc0ffef) + self.assertEqual(msg.arbitration_id, 0xC0FFEF) self.assertEqual(msg.dlc, 8) self.assertEqual(msg.is_extended_id, True) self.assertSequenceEqual(msg.data, self.msg_in_cue.data) @@ -142,53 +131,54 @@ def test_recv_extended(self): def test_recv_standard(self): self.msg_in_cue = can.Message( - arbitration_id=0x123, - data=[100, 101], - is_extended_id=False) + arbitration_id=0x123, data=[100, 101], is_extended_id=False + ) msg = self.bus.recv() self.assertEqual(msg.arbitration_id, 0x123) self.assertEqual(msg.dlc, 2) self.assertEqual(msg.is_extended_id, False) self.assertSequenceEqual(msg.data, [100, 101]) - + def test_available_configs(self): configs = canlib.KvaserBus._detect_available_configs() expected = [ - {'interface': 'kvaser', 'channel': 0}, - {'interface': 'kvaser', 'channel': 1} + {"interface": "kvaser", "channel": 0}, + {"interface": "kvaser", "channel": 1}, ] self.assertListEqual(configs, expected) def test_canfd_default_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() - can.Bus(channel=0, bustype='kvaser', fd=True) + can.Bus(channel=0, bustype="kvaser", fd=True) canlib.canSetBusParams.assert_called_once_with( - 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 + ) canlib.canSetBusParamsFd.assert_called_once_with( - 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0) + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0 + ) def test_canfd_nondefault_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() data_bitrate = 2000000 - can.Bus(channel=0, bustype='kvaser', fd=True, data_bitrate=data_bitrate) + can.Bus(channel=0, bustype="kvaser", fd=True, data_bitrate=data_bitrate) bitrate_constant = canlib.BITRATE_FD[data_bitrate] canlib.canSetBusParams.assert_called_once_with( - 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) - canlib.canSetBusParamsFd.assert_called_once_with( - 0, bitrate_constant, 0, 0, 0) + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 + ) + canlib.canSetBusParamsFd.assert_called_once_with(0, bitrate_constant, 0, 0, 0) def test_canfd_custom_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() data_bitrate = 123456 - can.Bus(channel=0, bustype='kvaser', fd=True, data_bitrate=data_bitrate) + can.Bus(channel=0, bustype="kvaser", fd=True, data_bitrate=data_bitrate) canlib.canSetBusParams.assert_called_once_with( - 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0) - canlib.canSetBusParamsFd.assert_called_once_with( - 0, data_bitrate, 0, 0, 0) + 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 + ) + canlib.canSetBusParamsFd.assert_called_once_with(0, data_bitrate, 0, 0, 0) def test_bus_get_stats(self): stats = self.bus.get_stats() @@ -201,10 +191,10 @@ def canGetNumberOfChannels(count): count._obj.value = 2 def canWrite(self, handle, arb_id, buf, dlc, flags): - self.msg['arb_id'] = arb_id - self.msg['dlc'] = dlc - self.msg['flags'] = flags - self.msg['data'] = bytearray(buf._obj) + self.msg["arb_id"] = arb_id + self.msg["dlc"] = dlc + self.msg["flags"] = flags + self.msg["data"] = bytearray(buf._obj) def canReadWait(self, handle, arb_id, data, dlc, flags, timestamp, timeout): if not self.msg_in_cue: @@ -227,5 +217,6 @@ def canReadWait(self, handle, arb_id, data, dlc, flags, timestamp, timeout): return constants.canOK -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py index dbd7673cb..d2f5fc3f2 100644 --- a/test/test_load_file_config.py +++ b/test/test_load_file_config.py @@ -11,10 +11,10 @@ class LoadFileConfigTest(unittest.TestCase): configuration = { - 'default': {'interface': 'virtual', 'channel': '0'}, - 'one': {'interface': 'virtual', 'channel': '1'}, - 'two': {'channel': '2'}, - 'three': {'extra': 'extra value'}, + "default": {"interface": "virtual", "channel": "0"}, + "one": {"interface": "virtual", "channel": "1"}, + "two": {"channel": "2"}, + "three": {"extra": "extra value"}, } def setUp(self): @@ -26,65 +26,65 @@ def tearDown(self): shutil.rmtree(self.test_dir) def _gen_configration_file(self, sections): - with NamedTemporaryFile(mode='w', dir=self.test_dir, - delete=False) as tmp_config_file: + with NamedTemporaryFile( + mode="w", dir=self.test_dir, delete=False + ) as tmp_config_file: content = [] for section in sections: content.append("[{}]".format(section)) for k, v in self.configuration[section].items(): content.append("{} = {}".format(k, v)) - tmp_config_file.write('\n'.join(content)) + tmp_config_file.write("\n".join(content)) return tmp_config_file.name def test_config_file_with_default(self): - tmp_config = self._gen_configration_file(['default']) + tmp_config = self._gen_configration_file(["default"]) config = can.util.load_file_config(path=tmp_config) - self.assertEqual(config, self.configuration['default']) + self.assertEqual(config, self.configuration["default"]) def test_config_file_with_default_and_section(self): - tmp_config = self._gen_configration_file(['default', 'one']) + tmp_config = self._gen_configration_file(["default", "one"]) default = can.util.load_file_config(path=tmp_config) - self.assertEqual(default, self.configuration['default']) + self.assertEqual(default, self.configuration["default"]) - one = can.util.load_file_config(path=tmp_config, section='one') - self.assertEqual(one, self.configuration['one']) + one = can.util.load_file_config(path=tmp_config, section="one") + self.assertEqual(one, self.configuration["one"]) def test_config_file_with_section_only(self): - tmp_config = self._gen_configration_file(['one']) - config = can.util.load_file_config(path=tmp_config, section='one') - self.assertEqual(config, self.configuration['one']) + tmp_config = self._gen_configration_file(["one"]) + config = can.util.load_file_config(path=tmp_config, section="one") + self.assertEqual(config, self.configuration["one"]) def test_config_file_with_section_and_key_in_default(self): - expected = self.configuration['default'].copy() - expected.update(self.configuration['two']) + expected = self.configuration["default"].copy() + expected.update(self.configuration["two"]) - tmp_config = self._gen_configration_file(['default', 'two']) - config = can.util.load_file_config(path=tmp_config, section='two') + tmp_config = self._gen_configration_file(["default", "two"]) + config = can.util.load_file_config(path=tmp_config, section="two") self.assertEqual(config, expected) def test_config_file_with_section_missing_interface(self): - expected = self.configuration['two'].copy() - tmp_config = self._gen_configration_file(['two']) - config = can.util.load_file_config(path=tmp_config, section='two') + expected = self.configuration["two"].copy() + tmp_config = self._gen_configration_file(["two"]) + config = can.util.load_file_config(path=tmp_config, section="two") self.assertEqual(config, expected) def test_config_file_extra(self): - expected = self.configuration['default'].copy() - expected.update(self.configuration['three']) + expected = self.configuration["default"].copy() + expected.update(self.configuration["three"]) - tmp_config = self._gen_configration_file(['default', 'three']) - config = can.util.load_file_config(path=tmp_config, section='three') + tmp_config = self._gen_configration_file(["default", "three"]) + config = can.util.load_file_config(path=tmp_config, section="three") self.assertEqual(config, expected) def test_config_file_with_non_existing_section(self): expected = {} - tmp_config = self._gen_configration_file([ - 'default', 'one', 'two', 'three']) - config = can.util.load_file_config(path=tmp_config, section='zero') + tmp_config = self._gen_configration_file(["default", "one", "two", "three"]) + config = can.util.load_file_config(path=tmp_config, section="zero") self.assertEqual(config, expected) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_message_class.py b/test/test_message_class.py index 85dbe8560..760a848bd 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -30,47 +30,53 @@ class TestMessageClass(unittest.TestCase): data=st.one_of(st.binary(min_size=0, max_size=8), st.none()), is_fd=st.booleans(), bitrate_switch=st.booleans(), - error_state_indicator=st.booleans() + error_state_indicator=st.booleans(), ) @settings(max_examples=2000) def test_methods(self, **kwargs): is_valid = not ( - (not kwargs['is_remote_frame'] and (len(kwargs['data'] or []) != kwargs['dlc'])) or - (kwargs['arbitration_id'] >= 0x800 and not kwargs['is_extended_id']) or - kwargs['arbitration_id'] >= 0x20000000 or - kwargs['arbitration_id'] < 0 or - (kwargs['is_remote_frame'] and kwargs['is_error_frame']) or - (kwargs['is_remote_frame'] and len(kwargs['data'] or []) > 0) or - ((kwargs['bitrate_switch'] or kwargs['error_state_indicator']) and not kwargs['is_fd']) or - isnan(kwargs['timestamp']) or - isinf(kwargs['timestamp']) + ( + not kwargs["is_remote_frame"] + and (len(kwargs["data"] or []) != kwargs["dlc"]) + ) + or (kwargs["arbitration_id"] >= 0x800 and not kwargs["is_extended_id"]) + or kwargs["arbitration_id"] >= 0x20000000 + or kwargs["arbitration_id"] < 0 + or (kwargs["is_remote_frame"] and kwargs["is_error_frame"]) + or (kwargs["is_remote_frame"] and len(kwargs["data"] or []) > 0) + or ( + (kwargs["bitrate_switch"] or kwargs["error_state_indicator"]) + and not kwargs["is_fd"] + ) + or isnan(kwargs["timestamp"]) + or isinf(kwargs["timestamp"]) ) # this should return normally and not throw an exception message = Message(check=is_valid, **kwargs) - if kwargs['data'] is None or kwargs['is_remote_frame']: - kwargs['data'] = bytearray() + if kwargs["data"] is None or kwargs["is_remote_frame"]: + kwargs["data"] = bytearray() - if not is_valid and not kwargs['is_remote_frame']: + if not is_valid and not kwargs["is_remote_frame"]: with self.assertRaises(ValueError): Message(check=True, **kwargs) self.assertGreater(len(str(message)), 0) self.assertGreater(len(message.__repr__()), 0) if is_valid: - self.assertEqual(len(message), kwargs['dlc']) + self.assertEqual(len(message), kwargs["dlc"]) self.assertTrue(bool(message)) self.assertGreater(len("{}".format(message)), 0) _ = "{}".format(message) with self.assertRaises(Exception): _ = "{somespec}".format(message) if sys.version_info.major > 2: - self.assertEqual(bytearray(bytes(message)), kwargs['data']) + self.assertEqual(bytearray(bytes(message)), kwargs["data"]) # check copies and equalities if is_valid: - self.assertEqual(message, message) + self.assertEqual(message, message) normal_copy = copy(message) deep_copy = deepcopy(message) for other in (normal_copy, deep_copy, message): @@ -79,5 +85,5 @@ def test_methods(self, **kwargs): self.assertTrue(message.equals(other, timestamp_delta=0)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index 1b498b95e..18ddf9e19 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -15,23 +15,14 @@ EXAMPLE_MSG = Message(arbitration_id=0x123, is_extended_id=True) HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, is_extended_id=True) -MATCH_EXAMPLE = [{ - "can_id": 0x123, - "can_mask": 0x1FFFFFFF, - "extended": True -}] +MATCH_EXAMPLE = [{"can_id": 0x123, "can_mask": 0x1FFFFFFF, "extended": True}] -MATCH_ONLY_HIGHEST = [{ - "can_id": 0xFFFFFFFF, - "can_mask": 0x1FFFFFFF, - "extended": True -}] +MATCH_ONLY_HIGHEST = [{"can_id": 0xFFFFFFFF, "can_mask": 0x1FFFFFFF, "extended": True}] class TestMessageFiltering(unittest.TestCase): - def setUp(self): - self.bus = Bus(bustype='virtual', channel='testy') + self.bus = Bus(bustype="virtual", channel="testy") def tearDown(self): self.bus.shutdown() @@ -58,5 +49,5 @@ def test_match_example_message(self): self.assertTrue(self.bus._matches_filters(HIGHEST_MSG)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_message_sync.py b/test/test_message_sync.py index ba2332776..b7b911bd0 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -30,10 +30,11 @@ def inc(value): return value -@unittest.skipIf(IS_APPVEYOR or (IS_TRAVIS and IS_OSX), - "this environment's timings are too unpredictable") +@unittest.skipIf( + IS_APPVEYOR or (IS_TRAVIS and IS_OSX), + "this environment's timings are too unpredictable", +) class TestMessageSync(unittest.TestCase, ComparingMessagesTestCase): - def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) ComparingMessagesTestCase.__init__(self) @@ -53,7 +54,7 @@ def test_general(self): Message(timestamp=50.0), Message(timestamp=50.0 + 0.05), Message(timestamp=50.0 + 0.05 + 0.08), - Message(timestamp=50.0) # back in time + Message(timestamp=50.0), # back in time ] sync = MessageSync(messages, gap=0.0) @@ -75,7 +76,7 @@ def test_general(self): self.assertTrue(0.075 <= timings[3] < inc(0.085), str(timings[3])) self.assertTrue(0.0 <= timings[4] < inc(0.005), str(timings[4])) - @pytest.mark.timeout(inc(0.1) * len(TEST_FEWER_MESSAGES)) # very conservative + @pytest.mark.timeout(inc(0.1) * len(TEST_FEWER_MESSAGES)) # very conservative def test_skip(self): messages = copy(TEST_FEWER_MESSAGES) sync = MessageSync(messages, skip=0.005, gap=0.0) @@ -92,19 +93,17 @@ def test_skip(self): self.assertMessagesEqual(messages, collected) -if not IS_APPVEYOR: # this environment's timings are too unpredictable +if not IS_APPVEYOR: # this environment's timings are too unpredictable @pytest.mark.timeout(inc(0.3)) - @pytest.mark.parametrize("timestamp_1,timestamp_2", [ - (0.0, 0.0), - (0.0, 0.01), - (0.01, 0.0), - ]) + @pytest.mark.parametrize( + "timestamp_1,timestamp_2", [(0.0, 0.0), (0.0, 0.01), (0.01, 0.0)] + ) def test_gap(timestamp_1, timestamp_2): """This method is alone so it can be parameterized.""" messages = [ Message(arbitration_id=0x1, timestamp=timestamp_1), - Message(arbitration_id=0x2, timestamp=timestamp_2) + Message(arbitration_id=0x2, timestamp=timestamp_2), ] sync = MessageSync(messages, gap=0.1) @@ -119,5 +118,5 @@ def test_gap(timestamp_1, timestamp_2): assert messages == collected -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_scripts.py b/test/test_scripts.py index 11dc06991..9504cf707 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -15,7 +15,6 @@ class CanScriptTest(unittest.TestCase, metaclass=ABCMeta): - @classmethod def setUpClass(cls): # clean up the argument list so the call to the main() functions @@ -38,9 +37,13 @@ def test_do_commands_exist(self): output = "-- NO OUTPUT --" allowed = [0, errno.EINVAL] - self.assertIn(return_code, allowed, - 'Calling "{}" failed (exit code was {} and not SUCCESS/0 or EINVAL/22):\n{}' - .format(command, return_code, output)) + self.assertIn( + return_code, + allowed, + 'Calling "{}" failed (exit code was {} and not SUCCESS/0 or EINVAL/22):\n{}'.format( + command, return_code, output + ), + ) def test_does_not_crash(self): # test import @@ -65,11 +68,10 @@ def _import(self): class TestLoggerScript(CanScriptTest): - def _commands(self): commands = [ "python -m can.logger --help", - "python scripts/can_logger.py --help" + "python scripts/can_logger.py --help", ] if IS_UNIX: commands += ["can_logger.py --help"] @@ -77,15 +79,15 @@ def _commands(self): def _import(self): import can.logger as module + return module class TestPlayerScript(CanScriptTest): - def _commands(self): commands = [ "python -m can.player --help", - "python scripts/can_player.py --help" + "python scripts/can_player.py --help", ] if IS_UNIX: commands += ["can_player.py --help"] @@ -93,6 +95,7 @@ def _commands(self): def _import(self): import can.player as module + return module @@ -100,8 +103,8 @@ def _import(self): # this excludes the base class from being executed as a test case itself -del(CanScriptTest) +del CanScriptTest -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_slcan.py b/test/test_slcan.py index 43703b3f0..c5d0d47e0 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -6,9 +6,8 @@ class slcanTestCase(unittest.TestCase): - def setUp(self): - self.bus = can.Bus('loop://', bustype='slcan', sleep_after_open=0) + self.bus = can.Bus("loop://", bustype="slcan", sleep_after_open=0) self.serial = self.bus.serialPortOrig self.serial.read(self.serial.in_waiting) @@ -16,7 +15,7 @@ def tearDown(self): self.bus.shutdown() def test_recv_extended(self): - self.serial.write(b'T12ABCDEF2AA55\r') + self.serial.write(b"T12ABCDEF2AA55\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -26,15 +25,15 @@ def test_recv_extended(self): self.assertSequenceEqual(msg.data, [0xAA, 0x55]) def test_send_extended(self): - msg = can.Message(arbitration_id=0x12ABCDEF, - is_extended_id=True, - data=[0xAA, 0x55]) + msg = can.Message( + arbitration_id=0x12ABCDEF, is_extended_id=True, data=[0xAA, 0x55] + ) self.bus.send(msg) data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b'T12ABCDEF2AA55\r') + self.assertEqual(data, b"T12ABCDEF2AA55\r") def test_recv_standard(self): - self.serial.write(b't4563112233\r') + self.serial.write(b"t4563112233\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x456) @@ -44,15 +43,15 @@ def test_recv_standard(self): self.assertSequenceEqual(msg.data, [0x11, 0x22, 0x33]) def test_send_standard(self): - msg = can.Message(arbitration_id=0x456, - is_extended_id=False, - data=[0x11, 0x22, 0x33]) + msg = can.Message( + arbitration_id=0x456, is_extended_id=False, data=[0x11, 0x22, 0x33] + ) self.bus.send(msg) data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b't4563112233\r') + self.assertEqual(data, b"t4563112233\r") def test_recv_standard_remote(self): - self.serial.write(b'r1238\r') + self.serial.write(b"r1238\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x123) @@ -61,16 +60,15 @@ def test_recv_standard_remote(self): self.assertEqual(msg.dlc, 8) def test_send_standard_remote(self): - msg = can.Message(arbitration_id=0x123, - is_extended_id=False, - is_remote_frame=True, - dlc=8) + msg = can.Message( + arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, dlc=8 + ) self.bus.send(msg) data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b'r1238\r') + self.assertEqual(data, b"r1238\r") def test_recv_extended_remote(self): - self.serial.write(b'R12ABCDEF6\r') + self.serial.write(b"R12ABCDEF6\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -79,20 +77,19 @@ def test_recv_extended_remote(self): self.assertEqual(msg.dlc, 6) def test_send_extended_remote(self): - msg = can.Message(arbitration_id=0x12ABCDEF, - is_extended_id=True, - is_remote_frame=True, - dlc=6) + msg = can.Message( + arbitration_id=0x12ABCDEF, is_extended_id=True, is_remote_frame=True, dlc=6 + ) self.bus.send(msg) data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b'R12ABCDEF6\r') + self.assertEqual(data, b"R12ABCDEF6\r") def test_partial_recv(self): - self.serial.write(b'T12ABCDEF') + self.serial.write(b"T12ABCDEF") msg = self.bus.recv(0) self.assertIsNone(msg) - self.serial.write(b'2AA55\rT12') + self.serial.write(b"2AA55\rT12") msg = self.bus.recv(0) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -104,10 +101,10 @@ def test_partial_recv(self): msg = self.bus.recv(0) self.assertIsNone(msg) - self.serial.write(b'ABCDEF2AA55\r') + self.serial.write(b"ABCDEF2AA55\r") msg = self.bus.recv(0) self.assertIsNotNone(msg) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index cc12eb415..311398657 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -7,14 +7,12 @@ import unittest -from can.interfaces.socketcan.utils import \ - find_available_interfaces, error_code_to_str +from can.interfaces.socketcan.utils import find_available_interfaces, error_code_to_str from .config import * class TestSocketCanHelpers(unittest.TestCase): - @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_error_code_to_str(self): """ @@ -27,7 +25,7 @@ def test_error_code_to_str(self): for error_code in test_data: string = error_code_to_str(error_code) - self.assertTrue(string) # not None or empty + self.assertTrue(string) # not None or empty @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_find_available_interfaces(self): @@ -40,5 +38,5 @@ def test_find_available_interfaces(self): self.assertIn("vcan0", result) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_systec.py b/test/test_systec.py index 0cd38da0d..6910653fc 100644 --- a/test/test_systec.py +++ b/test/test_systec.py @@ -10,10 +10,12 @@ class SystecTest(unittest.TestCase): - def compare_message(self, first, second, msg): - if first.arbitration_id != second.arbitration_id or first.data != second.data or \ - first.is_extended_id != second.is_extended_id: + if ( + first.arbitration_id != second.arbitration_id + or first.data != second.data + or first.is_extended_id != second.is_extended_id + ): raise self.failureException(msg) def setUp(self): @@ -30,12 +32,14 @@ def setUp(self): ucan.UcanDeinitHardware = Mock() ucan.UcanWriteCanMsgEx = Mock() ucan.UcanResetCanEx = Mock() - self.bus = can.Bus(bustype='systec', channel=0, bitrate=125000) + self.bus = can.Bus(bustype="systec", channel=0, bitrate=125000) def test_bus_creation(self): self.assertIsInstance(self.bus, ucanbus.UcanBus) self.assertTrue(ucan.UcanInitHwConnectControlEx.called) - self.assertTrue(ucan.UcanInitHardwareEx.called or ucan.UcanInitHardwareEx2.called) + self.assertTrue( + ucan.UcanInitHardwareEx.called or ucan.UcanInitHardwareEx2.called + ) self.assertTrue(ucan.UcanInitCanEx2.called) self.assertTrue(ucan.UcanGetHardwareInfoEx2.called) self.assertTrue(ucan.UcanSetAcceptanceEx.called) @@ -47,9 +51,7 @@ def test_bus_shutdown(self): def test_filter_setup(self): # no filter in the constructor - expected_args = ( - (self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL), - ) + expected_args = ((self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL),) self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) # one filter is handled by the driver @@ -57,33 +59,30 @@ def test_filter_setup(self): can_filter = (True, 0x123, 0x123, False, False) self.bus.set_filters(ucanbus.UcanBus.create_filter(*can_filter)) expected_args = ( - (self.bus._ucan._handle, - 0, - ucan.UcanServer.calculate_amr(*can_filter), - ucan.UcanServer.calculate_acr(*can_filter) - ), + ( + self.bus._ucan._handle, + 0, + ucan.UcanServer.calculate_amr(*can_filter), + ucan.UcanServer.calculate_acr(*can_filter), + ), ) self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) # multiple filters are handled by the bus ucan.UcanSetAcceptanceEx.reset_mock() - can_filter = ( - (False, 0x8, 0x8, False, False), - (False, 0x9, 0x9, False, False) - ) - self.bus.set_filters(ucanbus.UcanBus.create_filter(*can_filter[0]) + - ucanbus.UcanBus.create_filter(*can_filter[1])) - expected_args = ( - (self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL), + can_filter = ((False, 0x8, 0x8, False, False), (False, 0x9, 0x9, False, False)) + self.bus.set_filters( + ucanbus.UcanBus.create_filter(*can_filter[0]) + + ucanbus.UcanBus.create_filter(*can_filter[1]) ) + expected_args = ((self.bus._ucan._handle, 0, AMR_ALL, ACR_ALL),) self.assertEqual(ucan.UcanSetAcceptanceEx.call_args, expected_args) - @patch('can.interfaces.systec.ucan.UcanServer.write_can_msg') + @patch("can.interfaces.systec.ucan.UcanServer.write_can_msg") def test_send_extended(self, mock_write_can_msg): msg = can.Message( - arbitration_id=0xc0ffee, - data=[0, 25, 0, 1, 3, 1, 4], - is_extended_id=True) + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4], is_extended_id=True + ) self.bus.send(msg) expected_args = ( @@ -91,12 +90,9 @@ def test_send_extended(self, mock_write_can_msg): ) self.assertEqual(mock_write_can_msg.call_args, expected_args) - @patch('can.interfaces.systec.ucan.UcanServer.write_can_msg') + @patch("can.interfaces.systec.ucan.UcanServer.write_can_msg") def test_send_standard(self, mock_write_can_msg): - msg = can.Message( - arbitration_id=0x321, - data=[50, 51], - is_extended_id=False) + msg = can.Message(arbitration_id=0x321, data=[50, 51], is_extended_id=False) self.bus.send(msg) expected_args = ( @@ -104,41 +100,43 @@ def test_send_standard(self, mock_write_can_msg): ) self.assertEqual(mock_write_can_msg.call_args, expected_args) - @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') + @patch("can.interfaces.systec.ucan.UcanServer.get_msg_pending") def test_recv_no_message(self, mock_get_msg_pending): mock_get_msg_pending.return_value = 0 self.assertEqual(self.bus.recv(timeout=0.5), None) - @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') - @patch('can.interfaces.systec.ucan.UcanServer.read_can_msg') + @patch("can.interfaces.systec.ucan.UcanServer.get_msg_pending") + @patch("can.interfaces.systec.ucan.UcanServer.read_can_msg") def test_recv_extended(self, mock_read_can_msg, mock_get_msg_pending): - mock_read_can_msg.return_value = [CanMsg(0xc0ffef, MsgFrameFormat.MSG_FF_EXT, [1, 2, 3, 4, 5, 6, 7, 8])], 0 + mock_read_can_msg.return_value = ( + [CanMsg(0xC0FFEF, MsgFrameFormat.MSG_FF_EXT, [1, 2, 3, 4, 5, 6, 7, 8])], + 0, + ) mock_get_msg_pending.return_value = 1 msg = can.Message( - arbitration_id=0xc0ffef, - data=[1, 2, 3, 4, 5, 6, 7, 8], - is_extended_id=True) + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) can_msg = self.bus.recv() self.assertEqual(can_msg, msg) - @patch('can.interfaces.systec.ucan.UcanServer.get_msg_pending') - @patch('can.interfaces.systec.ucan.UcanServer.read_can_msg') + @patch("can.interfaces.systec.ucan.UcanServer.get_msg_pending") + @patch("can.interfaces.systec.ucan.UcanServer.read_can_msg") def test_recv_standard(self, mock_read_can_msg, mock_get_msg_pending): - mock_read_can_msg.return_value = [CanMsg(0x321, MsgFrameFormat.MSG_FF_STD, [50, 51])], 0 + mock_read_can_msg.return_value = ( + [CanMsg(0x321, MsgFrameFormat.MSG_FF_STD, [50, 51])], + 0, + ) mock_get_msg_pending.return_value = 1 - msg = can.Message( - arbitration_id=0x321, - data=[50, 51], - is_extended_id=False) + msg = can.Message(arbitration_id=0x321, data=[50, 51], is_extended_id=False) can_msg = self.bus.recv() self.assertEqual(can_msg, msg) @staticmethod def test_bus_defaults(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0) + bus = can.Bus(bustype="systec", channel=0) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -150,14 +148,14 @@ def test_bus_defaults(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_bus_channel(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=1) + bus = can.Bus(bustype="systec", channel=1) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 1, @@ -169,14 +167,14 @@ def test_bus_channel(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_bus_bitrate(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, bitrate=125000) + bus = can.Bus(bustype="systec", channel=0, bitrate=125000) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -188,18 +186,18 @@ def test_bus_bitrate(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) def test_bus_custom_bitrate(self): with self.assertRaises(ValueError): - can.Bus(bustype='systec', channel=0, bitrate=123456) + can.Bus(bustype="systec", channel=0, bitrate=123456) @staticmethod def test_receive_own_messages(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, receive_own_messages=True) + bus = can.Bus(bustype="systec", channel=0, receive_own_messages=True) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -211,14 +209,14 @@ def test_receive_own_messages(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_bus_passive_state(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, state=can.BusState.PASSIVE) + bus = can.Bus(bustype="systec", channel=0, state=can.BusState.PASSIVE) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -230,14 +228,14 @@ def test_bus_passive_state(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_rx_buffer_entries(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, rx_buffer_entries=1024) + bus = can.Bus(bustype="systec", channel=0, rx_buffer_entries=1024) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -249,14 +247,14 @@ def test_rx_buffer_entries(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, 1024, - DEFAULT_BUFFER_ENTRIES - ) + DEFAULT_BUFFER_ENTRIES, + ), ) @staticmethod def test_tx_buffer_entries(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype='systec', channel=0, tx_buffer_entries=1024) + bus = can.Bus(bustype="systec", channel=0, tx_buffer_entries=1024) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -268,14 +266,16 @@ def test_tx_buffer_entries(): ACR_ALL, BaudrateEx.BAUDEX_USE_BTR01, DEFAULT_BUFFER_ENTRIES, - 1024 - ) + 1024, + ), ) def test_flush_tx_buffer(self): self.bus.flush_tx_buffer() - ucan.UcanResetCanEx.assert_called_once_with(self.bus._ucan._handle, 0, ResetFlags.RESET_ONLY_TX_BUFF) + ucan.UcanResetCanEx.assert_called_once_with( + self.bus._ucan._handle, 0, ResetFlags.RESET_ONLY_TX_BUFF + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_viewer.py b/test/test_viewer.py index 9420b6d37..01f3b7c07 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -42,7 +42,6 @@ # noinspection SpellCheckingInspection,PyUnusedLocal class StdscrDummy: - def __init__(self): self.key_counter = 0 @@ -65,7 +64,7 @@ def addstr(row, col, txt, *args): assert col >= 0 assert txt is not None # Raise an exception 50 % of the time, so we can make sure the code handles it - if random.random() < .5: + if random.random() < 0.5: raise curses.error @staticmethod @@ -78,13 +77,13 @@ def getch(self): # Send invalid key return -1 elif self.key_counter == 2: - return ord('c') # Clear + return ord("c") # Clear elif self.key_counter == 3: return KEY_SPACE # Pause elif self.key_counter == 4: return KEY_SPACE # Unpause elif self.key_counter == 5: - return ord('s') # Sort + return ord("s") # Sort # Keep scrolling until it exceeds the number of messages elif self.key_counter <= 100: @@ -97,7 +96,6 @@ def getch(self): class CanViewerTest(unittest.TestCase): - @classmethod def setUpClass(cls): # Set seed, so the tests are not affected @@ -105,33 +103,33 @@ def setUpClass(cls): def setUp(self): stdscr = StdscrDummy() - config = {'interface': 'virtual', 'receive_own_messages': True} + config = {"interface": "virtual", "receive_own_messages": True} bus = can.Bus(**config) data_structs = None - patch_curs_set = patch('curses.curs_set') + patch_curs_set = patch("curses.curs_set") patch_curs_set.start() self.addCleanup(patch_curs_set.stop) - patch_use_default_colors = patch('curses.use_default_colors') + patch_use_default_colors = patch("curses.use_default_colors") patch_use_default_colors.start() self.addCleanup(patch_use_default_colors.stop) - patch_init_pair = patch('curses.init_pair') + patch_init_pair = patch("curses.init_pair") patch_init_pair.start() self.addCleanup(patch_init_pair.stop) - patch_color_pair = patch('curses.color_pair') + patch_color_pair = patch("curses.color_pair") patch_color_pair.start() self.addCleanup(patch_color_pair.stop) - patch_is_term_resized = patch('curses.is_term_resized') + patch_is_term_resized = patch("curses.is_term_resized") mock_is_term_resized = patch_is_term_resized.start() - mock_is_term_resized.return_value = True if random.random() < .5 else False + mock_is_term_resized.return_value = True if random.random() < 0.5 else False self.addCleanup(patch_is_term_resized.stop) - if hasattr(curses, 'resizeterm'): - patch_resizeterm = patch('curses.resizeterm') + if hasattr(curses, "resizeterm"): + patch_resizeterm = patch("curses.resizeterm") patch_resizeterm.start() self.addCleanup(patch_resizeterm.stop) @@ -174,7 +172,7 @@ def test_send(self): # self.assertTupleEqual(self.can_viewer.parse_canopen_message(msg), (None, None)) # Send the same message again to make sure that resending works and dt is correct - time.sleep(.1) + time.sleep(0.1) self.can_viewer.bus.send(msg) # Send error message @@ -187,41 +185,46 @@ def test_receive(self): data_structs = { # For converting the EMCY and HEARTBEAT messages - 0x080 + 0x01: struct.Struct('ff'), + 0x123456: struct.Struct(">ff"), } # Receive the messages we just sent in 'test_canopen' while 1: msg = self.can_viewer.bus.recv(timeout=0) if msg is not None: - self.can_viewer.data_structs = data_structs if msg.arbitration_id != 0x101 else None + self.can_viewer.data_structs = ( + data_structs if msg.arbitration_id != 0x101 else None + ) _id = self.can_viewer.draw_can_bus_message(msg) - if _id['msg'].arbitration_id == 0x101: + if _id["msg"].arbitration_id == 0x101: # Check if the counter is reset when the length has changed - self.assertEqual(_id['count'], 1) - elif _id['msg'].arbitration_id == 0x123456: + self.assertEqual(_id["count"], 1) + elif _id["msg"].arbitration_id == 0x123456: # Check if the counter is incremented - if _id['dt'] == 0: - self.assertEqual(_id['count'], 1) + if _id["dt"] == 0: + self.assertEqual(_id["count"], 1) else: - self.assertTrue(pytest.approx(_id['dt'], 0.1)) # dt should be ~0.1 s - self.assertEqual(_id['count'], 2) + self.assertTrue( + pytest.approx(_id["dt"], 0.1) + ) # dt should be ~0.1 s + self.assertEqual(_id["count"], 2) else: # Make sure dt is 0 - if _id['count'] == 1: - self.assertEqual(_id['dt'], 0) + if _id["count"] == 1: + self.assertEqual(_id["dt"], 0) else: break # Convert it into raw integer values and then pack the data @staticmethod - def pack_data(cmd, cmd_to_struct, *args): # type: (int, Dict, Union[*float, *int]) -> bytes + def pack_data( + cmd, cmd_to_struct, *args + ): # type: (int, Dict, Union[*float, *int]) -> bytes if not cmd_to_struct or len(args) == 0: # If no arguments are given, then the message does not contain a data package - return b'' + return b"" for key in cmd_to_struct.keys(): if cmd == key if isinstance(key, int) else cmd in key: @@ -237,12 +240,14 @@ def pack_data(cmd, cmd_to_struct, *args): # type: (int, Dict, Union[*float, *in fmt = six.b(fmt) # Make sure the endian is given as the first argument - assert six.byte2int(fmt) == ord('<') or six.byte2int(fmt) == ord('>') + assert six.byte2int(fmt) == ord("<") or six.byte2int(fmt) == ord( + ">" + ) # Disable rounding if the format is a float data = [] for c, arg, val in zip(six.iterbytes(fmt[1:]), args, value[1:]): - if c == ord('f'): + if c == ord("f"): data.append(arg * val) else: data.append(round(arg * val)) @@ -253,7 +258,7 @@ def pack_data(cmd, cmd_to_struct, *args): # type: (int, Dict, Union[*float, *in return struct_t.pack(*data) else: - raise ValueError('Unknown command: 0x{:02X}'.format(cmd)) + raise ValueError("Unknown command: 0x{:02X}".format(cmd)) def test_pack_unpack(self): CANOPEN_TPDO1 = 0x180 @@ -281,16 +286,18 @@ def test_pack_unpack(self): # are divided by the value in order to convert from real units to raw integer values. data_structs = { # CANopen node 1 - CANOPEN_TPDO1 + 1: struct.Struct('lL'), - (CANOPEN_TPDO3 + 2, CANOPEN_TPDO4 + 2): struct.Struct('>LL'), + CANOPEN_TPDO2 + 2: struct.Struct(">lL"), + (CANOPEN_TPDO3 + 2, CANOPEN_TPDO4 + 2): struct.Struct(">LL"), } # type: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] - raw_data = self.pack_data(CANOPEN_TPDO1 + 1, data_structs, -7, 13, -1024, 2048, 0xFFFF) + raw_data = self.pack_data( + CANOPEN_TPDO1 + 1, data_structs, -7, 13, -1024, 2048, 0xFFFF + ) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO1 + 1, data_structs, raw_data) self.assertListEqual(parsed_data, [-7, 13, -1024, 2048, 0xFFFF]) self.assertTrue(all(isinstance(d, int) for d in parsed_data)) @@ -298,17 +305,22 @@ def test_pack_unpack(self): raw_data = self.pack_data(CANOPEN_TPDO2 + 1, data_structs, 12.34, 4.5, 6) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO2 + 1, data_structs, raw_data) self.assertTrue(pytest.approx(parsed_data, [12.34, 4.5, 6])) - self.assertTrue(isinstance(parsed_data[0], float) and isinstance(parsed_data[1], float) and - isinstance(parsed_data[2], int)) + self.assertTrue( + isinstance(parsed_data[0], float) + and isinstance(parsed_data[1], float) + and isinstance(parsed_data[2], int) + ) raw_data = self.pack_data(CANOPEN_TPDO3 + 1, data_structs, 123.45, 67.89) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO3 + 1, data_structs, raw_data) self.assertTrue(pytest.approx(parsed_data, [123.45, 67.89])) self.assertTrue(all(isinstance(d, float) for d in parsed_data)) - raw_data = self.pack_data(CANOPEN_TPDO4 + 1, data_structs, math.pi / 2., math.pi) + raw_data = self.pack_data( + CANOPEN_TPDO4 + 1, data_structs, math.pi / 2.0, math.pi + ) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO4 + 1, data_structs, raw_data) - self.assertTrue(pytest.approx(parsed_data, [math.pi / 2., math.pi])) + self.assertTrue(pytest.approx(parsed_data, [math.pi / 2.0, math.pi])) self.assertTrue(all(isinstance(d, float) for d in parsed_data)) raw_data = self.pack_data(CANOPEN_TPDO1 + 2, data_structs) @@ -316,7 +328,9 @@ def test_pack_unpack(self): self.assertListEqual(parsed_data, []) self.assertIsInstance(parsed_data, list) - raw_data = self.pack_data(CANOPEN_TPDO2 + 2, data_structs, -2147483648, 0xFFFFFFFF) + raw_data = self.pack_data( + CANOPEN_TPDO2 + 2, data_structs, -2147483648, 0xFFFFFFFF + ) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO2 + 2, data_structs, raw_data) self.assertListEqual(parsed_data, [-2147483648, 0xFFFFFFFF]) @@ -335,50 +349,56 @@ def test_pack_unpack(self): self.pack_data(0x101, data_structs, 1, 2, 3, 4) with self.assertRaises(ValueError): - CanViewer.unpack_data(0x102, data_structs, b'\x01\x02\x03\x04\x05\x06\x07\x08') + CanViewer.unpack_data( + 0x102, data_structs, b"\x01\x02\x03\x04\x05\x06\x07\x08" + ) def test_parse_args(self): - parsed_args, _, _ = parse_args(['-b', '250000']) + parsed_args, _, _ = parse_args(["-b", "250000"]) self.assertEqual(parsed_args.bitrate, 250000) - parsed_args, _, _ = parse_args(['--bitrate', '500000']) + parsed_args, _, _ = parse_args(["--bitrate", "500000"]) self.assertEqual(parsed_args.bitrate, 500000) - parsed_args, _, _ = parse_args(['-c', 'can0']) - self.assertEqual(parsed_args.channel, 'can0') + parsed_args, _, _ = parse_args(["-c", "can0"]) + self.assertEqual(parsed_args.channel, "can0") - parsed_args, _, _ = parse_args(['--channel', 'PCAN_USBBUS1']) - self.assertEqual(parsed_args.channel, 'PCAN_USBBUS1') + parsed_args, _, _ = parse_args(["--channel", "PCAN_USBBUS1"]) + self.assertEqual(parsed_args.channel, "PCAN_USBBUS1") - parsed_args, _, data_structs = parse_args(['-d', '100: Date: Thu, 23 May 2019 22:10:45 -0700 Subject: [PATCH 0202/1235] Clean up CANalystII interface formatting Clean up formatting of the CANalystII interface after running Black formatter. --- can/interfaces/canalystii.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 11f4acfbd..10de8b170 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -115,12 +115,10 @@ def __init__( logger.error("VCI_OpenDevice Error") for channel in self.channels: - if ( - CANalystII.VCI_InitCAN( - VCI_USBCAN2, self.device, channel, byref(self.init_config) - ) - == STATUS_ERR - ): + status = CANalystII.VCI_InitCAN( + VCI_USBCAN2, self.device, channel, byref(self.init_config) + ) + if status == STATUS_ERR: logger.error("VCI_InitCAN Error") self.shutdown() return @@ -171,17 +169,10 @@ def _recv_internal(self, timeout=None): timeout = -1 if timeout is None else int(timeout * 1000) - if ( - CANalystII.VCI_Receive( - VCI_USBCAN2, - self.device, - self.channels[0], - byref(raw_message), - 1, - timeout, - ) - <= STATUS_ERR - ): + status = CANalystII.VCI_Receive( + VCI_USBCAN2, self.device, self.channels[0], byref(raw_message), 1, timeout + ) + if status <= STATUS_ERR: return None, False else: return ( From c79bba80701954ff36a08dddf17900af4bb6f9bc Mon Sep 17 00:00:00 2001 From: Karl Date: Thu, 23 May 2019 22:22:06 -0700 Subject: [PATCH 0203/1235] Change CI command Switch to using: black --check --verbose . Providing the diff isn't too valuable when the formatting is automatically handled by the formatter. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c24c1cd8..50909340f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -98,8 +98,7 @@ jobs: before_install: - travis_retry pip install -r requirements-lint.txt script: - - black --diff . - - black --check . + - black --check --verbose . - stage: deploy name: "PyPi Deployment" python: "3.7" From 4e95890d67ce995647c1826e806034a68d38d8de Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sat, 1 Jun 2019 20:09:47 +0200 Subject: [PATCH 0204/1235] add set_bitrate_reg to slcanBus --- can/interfaces/slcan.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index b63b7d57a..270f54791 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -103,8 +103,7 @@ def __init__( self.set_bitrate(self, bitrate) if btr is not None: - self.close() - self.write("s" + btr) + self.set_bitrate_reg(self, btr) self.open() @@ -123,6 +122,11 @@ def set_bitrate(self, bitrate): ) self.open() + def set_bitrate_reg(self, btr): + self.close() + self.write("s" + btr) + self.open() + def write(self, string): self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() From a7a9c096134b6fb0b2308c870bb379c6e5d8f4f6 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Sun, 2 Jun 2019 04:05:08 +0200 Subject: [PATCH 0205/1235] Remove dependency on Six (#607) Fixes #600 --- can/viewer.py | 3 ++- setup.py | 1 - test/test_viewer.py | 20 +++++++++----------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/can/viewer.py b/can/viewer.py index 5f8d18858..b6ecbfe36 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -23,12 +23,13 @@ # e-mail : lauszus@gmail.com import argparse +import logging import os import struct import sys import time -import logging from typing import Dict, List, Tuple, Union + import can from can import __version__ diff --git a/setup.py b/setup.py index 354b63c9c..1918b644b 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,6 @@ "pytest-timeout~=1.3", "pytest-cov~=2.6", "codecov~=2.0", - "six", "hypothesis", ] + extras_require["serial"] diff --git a/test/test_viewer.py b/test/test_viewer.py index 01f3b7c07..a0873d02b 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -24,19 +24,19 @@ # e-mail : lauszus@gmail.com import argparse -import can import curses import math -import pytest +import os import random import struct import time import unittest -import os -import six from typing import Dict, Tuple, Union -from unittest.mock import Mock, patch +from unittest.mock import patch +import pytest + +import can from can.viewer import KEY_ESC, KEY_SPACE, CanViewer, parse_args @@ -235,18 +235,16 @@ def pack_data( # The conversion from SI-units to raw values are given in the rest of the tuple fmt = struct_t.format - if isinstance(fmt, six.string_types): # pragma: no cover + if isinstance(fmt, str): # pragma: no cover # Needed for Python 3.7 - fmt = six.b(fmt) + fmt = fmt.encode() # Make sure the endian is given as the first argument - assert six.byte2int(fmt) == ord("<") or six.byte2int(fmt) == ord( - ">" - ) + assert fmt[0] == ord("<") or fmt[0] == ord(">") # Disable rounding if the format is a float data = [] - for c, arg, val in zip(six.iterbytes(fmt[1:]), args, value[1:]): + for c, arg, val in zip(fmt[1:], args, value[1:]): if c == ord("f"): data.append(arg * val) else: From dc3ab215e1a55b5ac4b280d2ee2e6edabf1b3c7b Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sun, 2 Jun 2019 09:13:54 +0200 Subject: [PATCH 0206/1235] unnecessary to specify 1 byte in serial read --- can/interfaces/slcan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 270f54791..7102eb414 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -135,14 +135,14 @@ def read(self, timeout): # first read what is already in receive buffer while self.serialPortOrig.in_waiting: - self._buffer += self.serialPortOrig.read(1) + self._buffer += self.serialPortOrig.read() # if we still don't have a complete message, do a blocking read start = time.time() time_left = timeout while not (self._OK in self._buffer or self._ERROR in self._buffer): self.serialPortOrig.timeout = time_left - byte = self.serialPortOrig.read(1) + byte = self.serialPortOrig.read() if byte: self._buffer += byte @@ -171,7 +171,7 @@ def read(self, timeout): def flush(self): del self._buffer[:] while self.serialPortOrig.in_waiting: - self.serialPortOrig.read(1) + self.serialPortOrig.read() def open(self): self.write("O") From dcc60ac3ea41d9ee060b77e2d4ad0a7f76f6e476 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sun, 2 Jun 2019 09:16:56 +0200 Subject: [PATCH 0207/1235] fix faulty if in read of slcanBus --- can/interfaces/slcan.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 7102eb414..07e5494c0 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -140,7 +140,10 @@ def read(self, timeout): # if we still don't have a complete message, do a blocking read start = time.time() time_left = timeout - while not (self._OK in self._buffer or self._ERROR in self._buffer): + while not ( + ord(self._OK) in self._buffer or + ord(self._ERROR) in self._buffer + ): self.serialPortOrig.timeout = time_left byte = self.serialPortOrig.read() if byte: @@ -160,8 +163,8 @@ def read(self, timeout): # return first message for i in range(len(self._buffer)): - if ( chr(self._buffer[i]) == self._OK or - chr(self._buffer[i]) == self._ERROR ): + if ( self._buffer[i] == ord(self._OK) or + self._buffer[i] == ord(self._ERROR) ): string = self._buffer[:i+1].decode() del self._buffer[:i+1] break From 807e288622c221e709ce050f42a1ba6c0f21c576 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sun, 2 Jun 2019 09:24:01 +0200 Subject: [PATCH 0208/1235] rename get_serial to get_serial_number --- can/interfaces/slcan.py | 6 +++--- test/test_slcan.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 07e5494c0..8831b52cb 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -287,7 +287,7 @@ def get_version(self, timeout): else: return None, None - def get_serial(self, timeout): + def get_serial_number(self, timeout): cmd = "N" self.write(cmd) @@ -299,8 +299,8 @@ def get_serial(self, timeout): if not string: pass elif string[0] == cmd and len(string) == 6: - serial = string[1:-1] - return serial + serial_number = string[1:-1] + return serial_number # if timeout is None, try indefinitely if timeout is None: diff --git a/test/test_slcan.py b/test/test_slcan.py index bff016656..178996c93 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -115,12 +115,12 @@ def test_version(self): self.assertIsNone(hw_ver) self.assertIsNone(sw_ver) - def test_serial(self): + def test_serial_number(self): self.serial.write(b'NA123\r') - sn = self.bus.get_serial(0) + sn = self.bus.get_serial_number(0) self.assertEqual(sn, "A123") - sn = self.bus.get_serial(0) + sn = self.bus.get_serial_number(0) self.assertIsNone(sn) From ba2a4570ac55eb375c7c40fcf894e383bcda391a Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Sun, 2 Jun 2019 10:16:27 +0200 Subject: [PATCH 0209/1235] reformat using black --- can/interfaces/slcan.py | 29 +++++------------------------ test/test_slcan.py | 4 ++-- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 8831b52cb..658a8a6fa 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -84,10 +84,8 @@ def __init__( if not channel: # if None or empty raise TypeError("Must specify a serial port.") - if "@" in channel: (channel, ttyBaudrate) = channel.split("@") - self.serialPortOrig = serial.serial_for_url( channel, baudrate=ttyBaudrate, rtscts=rtscts ) @@ -98,13 +96,10 @@ 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) - if btr is not None: self.set_bitrate_reg(self, btr) - self.open() super().__init__( @@ -117,8 +112,7 @@ def set_bitrate(self, bitrate): self.write(self._BITRATES[bitrate]) else: raise ValueError( - "Invalid bitrate, choose one of " + - (", ".join(self._BITRATES)) + "." + "Invalid bitrate, choose one of " + (", ".join(self._BITRATES)) + "." ) self.open() @@ -136,19 +130,14 @@ def read(self, timeout): # first read what is already in receive buffer while self.serialPortOrig.in_waiting: self._buffer += self.serialPortOrig.read() - # if we still don't have a complete message, do a blocking read start = time.time() time_left = timeout - while not ( - ord(self._OK) in self._buffer or - ord(self._ERROR) in self._buffer - ): + while not (ord(self._OK) in self._buffer or ord(self._ERROR) in self._buffer): self.serialPortOrig.timeout = time_left byte = self.serialPortOrig.read() if byte: self._buffer += byte - # if timeout is None, try indefinitely if timeout is None: continue @@ -160,15 +149,12 @@ def read(self, timeout): continue else: return None - # return first message for i in range(len(self._buffer)): - if ( self._buffer[i] == ord(self._OK) or - self._buffer[i] == ord(self._ERROR) ): - string = self._buffer[:i+1].decode() - del self._buffer[:i+1] + if self._buffer[i] == ord(self._OK) or self._buffer[i] == ord(self._ERROR): + string = self._buffer[: i + 1].decode() + del self._buffer[: i + 1] break - return string def flush(self): @@ -217,7 +203,6 @@ def _recv_internal(self, timeout): dlc = int(string[9]) extended = True remote = True - if canId is not None: msg = Message( arbitration_id=canId, @@ -233,7 +218,6 @@ def _recv_internal(self, timeout): def send(self, msg, timeout=None): if timeout != self.serialPortOrig.write_timeout: self.serialPortOrig.write_timeout = timeout - if msg.is_remote_frame: if msg.is_extended_id: sendStr = "R%08X%d" % (msg.arbitration_id, msg.dlc) @@ -244,7 +228,6 @@ def send(self, msg, timeout=None): sendStr = "T%08X%d" % (msg.arbitration_id, msg.dlc) else: sendStr = "t%03X%d" % (msg.arbitration_id, msg.dlc) - sendStr += "".join(["%02X" % b for b in msg.data]) self.write(sendStr) @@ -274,7 +257,6 @@ def get_version(self, timeout): hw_version = int(string[1:3]) sw_version = int(string[3:5]) return hw_version, sw_version - # if timeout is None, try indefinitely if timeout is None: continue @@ -301,7 +283,6 @@ def get_serial_number(self, timeout): elif string[0] == cmd and len(string) == 6: serial_number = string[1:-1] return serial_number - # if timeout is None, try indefinitely if timeout is None: continue diff --git a/test/test_slcan.py b/test/test_slcan.py index 178996c93..781fa75df 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -106,7 +106,7 @@ def test_partial_recv(self): self.assertIsNotNone(msg) def test_version(self): - self.serial.write(b'V1013\r') + self.serial.write(b"V1013\r") hw_ver, sw_ver = self.bus.get_version(0) self.assertEqual(hw_ver, 10) self.assertEqual(sw_ver, 13) @@ -116,7 +116,7 @@ def test_version(self): self.assertIsNone(sw_ver) def test_serial_number(self): - self.serial.write(b'NA123\r') + self.serial.write(b"NA123\r") sn = self.bus.get_serial_number(0) self.assertEqual(sn, "A123") From bca1fa4785e96fd7a55c343244c0570570a8f014 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 3 Jun 2019 10:40:26 +0200 Subject: [PATCH 0210/1235] Add wrongly removed statement back to codebase (#612) * Add wrongly removed statement back to codebase This was removed in the process of making the linter happy. But it is required since it changes `num_channels`. * remove unused commented out logging --- can/interfaces/kvaser/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index f16004c3b..89a05af1e 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -424,7 +424,7 @@ def __init__(self, channel, can_filters=None, **kwargs): self.single_handle = single_handle num_channels = ctypes.c_int(0) - # log.debug("Res: %d", canGetNumberOfChannels(ctypes.byref(num_channels))) + canGetNumberOfChannels(ctypes.byref(num_channels)) num_channels = int(num_channels.value) log.info("Found %d available channels", num_channels) for idx in range(num_channels): From 0c7a18e1ee850d5ffafb74cd0d7fba27c8947478 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Tue, 4 Jun 2019 21:40:58 +0200 Subject: [PATCH 0211/1235] document public methods that take arguments --- can/interfaces/slcan.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 658a8a6fa..b581a0f13 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -107,6 +107,12 @@ def __init__( ) def set_bitrate(self, bitrate): + """ + :raise ValueError: if both *bitrate* is not among the possible values + + :param int bitrate: + Bitrate in bit/s + """ self.close() if bitrate in self._BITRATES: self.write(self._BITRATES[bitrate]) @@ -117,6 +123,10 @@ def set_bitrate(self, bitrate): self.open() def set_bitrate_reg(self, btr): + """ + :param str btr: + BTR register value to set custom can speed + """ self.close() self.write("s" + btr) self.open() From 1d7701e50619a9ec5d880ef49545db8002dc0949 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Tue, 4 Jun 2019 22:07:25 +0200 Subject: [PATCH 0212/1235] make read hidden method --- can/interfaces/slcan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index b581a0f13..e1e9a7498 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -135,7 +135,7 @@ def write(self, string): self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() - def read(self, timeout): + def _read(self, timeout): # first read what is already in receive buffer while self.serialPortOrig.in_waiting: @@ -185,7 +185,7 @@ def _recv_internal(self, timeout): extended = False frame = [] - string = self.read(timeout) + string = self._read(timeout) if not string: pass @@ -258,7 +258,7 @@ def get_version(self, timeout): start = time.time() time_left = timeout while True: - string = self.read(time_left) + string = self._read(time_left) if not string: pass @@ -286,7 +286,7 @@ def get_serial_number(self, timeout): start = time.time() time_left = timeout while True: - string = self.read(time_left) + string = self._read(time_left) if not string: pass From f7866d097ae62cfd30164d66d58ce784d47b7d6c Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 4 Jun 2019 22:12:44 +0200 Subject: [PATCH 0213/1235] First shot at a BitTiming class --- can/__init__.py | 1 + can/bit_timing.py | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 can/bit_timing.py diff --git a/can/__init__.py b/can/__init__.py index e23f5a9b8..f134ea8af 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -37,6 +37,7 @@ class CanError(IOError): from .interfaces import VALID_INTERFACES from . import interface from .interface import Bus, detect_available_configs +from .bit_timing import BitTiming from .broadcastmanager import ( CyclicSendTaskABC, diff --git a/can/bit_timing.py b/can/bit_timing.py new file mode 100644 index 000000000..b0477227d --- /dev/null +++ b/can/bit_timing.py @@ -0,0 +1,111 @@ + + +class BitTiming: + + # Is this always 1? + sync_seg = 1 + + def __init__( + self, + bitrate=None, + brp=None, + sjw=None, + tseg1=None, + tseg2=None, + nof_samples=1, + f_clock=None, + btr0=None, + btr1=None + ): + if brp is not None and (brp < 1 or brp > 64): + raise ValueError("brp must be 1 - 64") + if sjw is not None and (sjw < 1 or sjw > 4): + raise ValueError("sjw must be 1 - 4") + if tseg1 is not None and (tseg1 < 1 or tseg1 > 16): + raise ValueError("tseg1 must be 1 - 16") + if tseg2 is not None and (tseg2 < 1 or tseg2 > 8): + raise ValueError("tseg2 must be 1 - 8") + if nof_samples is not None and nof_samples not in (1, 3): + raise ValueError("nof_samples must be 1 or 3") + if btr0 is not None and (btr0 < 0 or btr0 > 255): + raise ValueError("btr0 must be 0 - 255") + if btr1 is not None and (btr1 < 0 or btr1 > 255): + raise ValueError("btr1 must be 0 - 255") + + self._bitrate = bitrate + self._brp = brp + self._sjw = sjw + self._tseg1 = tseg1 + self._tseg2 = tseg2 + self._nof_samples = nof_samples + self._f_clock = f_clock + self._btr0 = btr0 + self._btr1 = btr1 + + @property + def nbt(self): + return self.sync_seg + self.tseg1 + self.tseg2 + + @property + def bitrate(self): + if self._bitrate: + return self._bitrate + raise ValueError("bitrate must be specified") + + @property + def brp(self): + if self._brp: + return self._brp + return (2 * self.bitrate * self.nbt) // self.f_clock + + @property + def sjw(self): + if not self._sjw: + raise ValueError("sjw must be specified") + return self._sjw + + @property + def tseg1(self): + if not self._tseg1: + raise ValueError("tseg1 must be specified") + return self._tseg1 + + @property + def tseg2(self): + if not self._tseg2: + raise ValueError("tseg2 must be specified") + return self._tseg2 + + @property + def nof_samples(self): + if not self._nof_samples: + raise ValueError("nof_samples must be specified") + return self._nof_samples + + @property + def f_clock(self): + if not self._f_clock: + raise ValueError("f_clock must be specified") + return self._f_clock + + @property + def btr0(self): + if self._btr0 is not None: + return self._btr0 + btr0 = (self.sjw - 1) << 5 + btr0 |= self.brp - 1 + return btr0 + + @property + def btr1(self): + if self._btr1 is not None: + return self._btr1 + sam = 1 if self.nof_samples == 3 else 0 + btr1 = sam << 7 + btr1 |= (self.tseg2 - 1) << 4 + btr1 |= self.tseg1 - 1 + return btr1 + + @property + def btr(self): + return self.btr0 << 8 | self.btr1 From 86a5f343c03015b89d729963fe5047f95bc91c08 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Tue, 4 Jun 2019 22:29:43 +0200 Subject: [PATCH 0214/1235] document get_version and get_serial_number --- can/interfaces/slcan.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index e1e9a7498..58a064ad6 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -252,6 +252,17 @@ def fileno(self): return -1 def get_version(self, timeout): + """Get HW and SW version of the slcan interface. + + :type timeout: int or None + :param timeout: + seconds to wait for version or None to wait indefinitely + + :returns: tuple (hw_version, sw_version) + WHERE + int hw_version is the hardware version or None on timeout + int sw_version is the software version or None on timeout + """ cmd = "V" self.write(cmd) @@ -280,6 +291,16 @@ def get_version(self, timeout): return None, None def get_serial_number(self, timeout): + """Get serial number of the slcan interface. + + :type timeout: int or None + :param timeout: + seconds to wait for serial number or None to wait indefinitely + + :rtype str or None + :return: + None on timeout or a str object. + """ cmd = "N" self.write(cmd) From 47d738f7e2dca5be5f3e47c2f1690a4c8309422a Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Wed, 5 Jun 2019 09:19:51 +0200 Subject: [PATCH 0215/1235] Inverse BRP calculation --- can/bit_timing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index b0477227d..b6313e910 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -56,7 +56,7 @@ def bitrate(self): def brp(self): if self._brp: return self._brp - return (2 * self.bitrate * self.nbt) // self.f_clock + return round(self.f_clock / (2 * self.bitrate * self.nbt)) @property def sjw(self): From bbc647b659d54d681e4251b17aebdc6c7c334110 Mon Sep 17 00:00:00 2001 From: Alberto Scotta Date: Wed, 5 Jun 2019 21:38:30 +0200 Subject: [PATCH 0216/1235] make write an hidden method --- can/interfaces/slcan.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 58a064ad6..be5d672d3 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -115,7 +115,7 @@ def set_bitrate(self, bitrate): """ self.close() if bitrate in self._BITRATES: - self.write(self._BITRATES[bitrate]) + self._write(self._BITRATES[bitrate]) else: raise ValueError( "Invalid bitrate, choose one of " + (", ".join(self._BITRATES)) + "." @@ -128,10 +128,10 @@ def set_bitrate_reg(self, btr): BTR register value to set custom can speed """ self.close() - self.write("s" + btr) + self._write("s" + btr) self.open() - def write(self, string): + def _write(self, string): self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() @@ -173,10 +173,10 @@ def flush(self): self.serialPortOrig.read() def open(self): - self.write("O") + self._write("O") def close(self): - self.write("C") + self._write("C") def _recv_internal(self, timeout): @@ -239,7 +239,7 @@ def send(self, msg, timeout=None): else: sendStr = "t%03X%d" % (msg.arbitration_id, msg.dlc) sendStr += "".join(["%02X" % b for b in msg.data]) - self.write(sendStr) + self._write(sendStr) def shutdown(self): self.close() @@ -264,7 +264,7 @@ def get_version(self, timeout): int sw_version is the software version or None on timeout """ cmd = "V" - self.write(cmd) + self._write(cmd) start = time.time() time_left = timeout @@ -302,7 +302,7 @@ def get_serial_number(self, timeout): None on timeout or a str object. """ cmd = "N" - self.write(cmd) + self._write(cmd) start = time.time() time_left = timeout From 934a753671d243729e92c9a045bb344e0dab6ee8 Mon Sep 17 00:00:00 2001 From: nick black Date: Sat, 8 Jun 2019 11:32:42 -0400 Subject: [PATCH 0217/1235] [canalystii] get CANalystII working through init --- can/interfaces/canalystii.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 10de8b170..a28e182d2 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -73,18 +73,18 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): def __init__( - self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None + self, channel, device=0, bitrate=None, Timing0=None, Timing1=None, can_filters=None, **kwargs, ): """ :param channel: channel number :param device: device number - :param baud: baud rate - :param Timing0: customize the timing register if baudrate is not specified + :param bitrate: CAN network bandwidth (bytes/s) + :param Timing0: customize the timing register if bitrate is not specified :param Timing1: :param can_filters: filters for packet """ - super().__init__(channel, can_filters) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) if isinstance(channel, (list, tuple)): self.channels = channel @@ -100,11 +100,11 @@ def __init__( self.device, self.channels ) - if baud is not None: + if bitrate is not None: try: - Timing0, Timing1 = TIMING_DICT[baud] + Timing0, Timing1 = TIMING_DICT[bitrate] except KeyError: - raise ValueError("Baudrate is not supported") + raise ValueError("Bitrate is not supported") if Timing0 is None or Timing1 is None: raise ValueError("Timing registers are not set") From f0017ca7b7f8cb29ece23ac2518f4ce2de9ef8c0 Mon Sep 17 00:00:00 2001 From: nick black Date: Sat, 8 Jun 2019 11:54:47 -0400 Subject: [PATCH 0218/1235] Add canalystii+systec to configuration doc --- doc/configuration.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/configuration.rst b/doc/configuration.rst index dda2ace2a..142e816da 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -126,3 +126,7 @@ Lookup table of interface names: +---------------------+-------------------------------------+ | ``"virtual"`` | :doc:`interfaces/virtual` | +---------------------+-------------------------------------+ +| ``"canalystii"`` | :doc:`interfaces/canalystii` | ++---------------------+-------------------------------------+ +| ``"systec"`` | :doc:`interfaces/systec` | ++---------------------+-------------------------------------+ From c3b18b19e9b07b10853ada7b76d582ae47795ec2 Mon Sep 17 00:00:00 2001 From: nick black Date: Sat, 8 Jun 2019 11:55:21 -0400 Subject: [PATCH 0219/1235] Add myself to CONTRIBUTORS --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 9a37da3b5..b7ac9fbd4 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -27,3 +27,4 @@ Alexander Mueller Jan Goeteyn "ykzheng" Lear Corporation +Nick Black From 07d8fc2c8d62803af7ea9930c795a4552edb7527 Mon Sep 17 00:00:00 2001 From: nick black Date: Sat, 8 Jun 2019 12:02:40 -0400 Subject: [PATCH 0220/1235] [black] normalize indentation --- can/interfaces/canalystii.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index a28e182d2..cc68ff240 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -73,7 +73,14 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): def __init__( - self, channel, device=0, bitrate=None, Timing0=None, Timing1=None, can_filters=None, **kwargs, + self, + channel, + device=0, + bitrate=None, + Timing0=None, + Timing1=None, + can_filters=None, + **kwargs, ): """ From 324afd0bdd61f2ab036c200f579f0f1e1a77ae25 Mon Sep 17 00:00:00 2001 From: shedfly Date: Sun, 9 Jun 2019 16:28:06 +1000 Subject: [PATCH 0221/1235] changing name to seeedstuido and changes for python3 --- can/interfaces/__init__.py | 2 +- can/interfaces/seeedstudio/__init__.py | 6 ++++++ .../{usb_can_analyzer => seeedstudio}/notes | 0 .../seeedstudio.py} | 12 ++++++------ can/interfaces/usb_can_analyzer/__init__.py | 6 ------ 5 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 can/interfaces/seeedstudio/__init__.py rename can/interfaces/{usb_can_analyzer => seeedstudio}/notes (100%) rename can/interfaces/{usb_can_analyzer/usb_can_analyzer.py => seeedstudio/seeedstudio.py} (95%) delete mode 100644 can/interfaces/usb_can_analyzer/__init__.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 671f53cc0..c17d85823 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -24,7 +24,7 @@ 'slcan': ('can.interfaces.slcan', 'slcanBus'), 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus'), 'systec': ('can.interfaces.systec', 'UcanBus'), - 'usb_can_analyzer': ('can.interfaces.usb_can_analyzer.usb_can_analyzer', 'CanAnalyzer') + 'seeedstudio': ('can.interfaces.seeedstudio', 'CanAnalyzer') } BACKENDS.update({ diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py new file mode 100644 index 000000000..32306df97 --- /dev/null +++ b/can/interfaces/seeedstudio/__init__.py @@ -0,0 +1,6 @@ +# coding: utf-8 + +""" +""" + +from can.interfaces.seeedstudio.seeedstudio import CanAnalyzer diff --git a/can/interfaces/usb_can_analyzer/notes b/can/interfaces/seeedstudio/notes similarity index 100% rename from can/interfaces/usb_can_analyzer/notes rename to can/interfaces/seeedstudio/notes diff --git a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py b/can/interfaces/seeedstudio/seeedstudio.py similarity index 95% rename from can/interfaces/usb_can_analyzer/usb_can_analyzer.py rename to can/interfaces/seeedstudio/seeedstudio.py index 73def806d..9bd38bf3d 100644 --- a/can/interfaces/usb_can_analyzer/usb_can_analyzer.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -121,7 +121,7 @@ def init_frame(self, timeout=None): byte_msg.append(0x12) # Initialization Message ID byte_msg.append(CanAnalyzer.BITRATE[self.bit_rate]) # CAN Baud Rate - byte_msg.append(CanAnalyzer.FRAMETYPE[self.frame_type]) + byte_msg.append(CanAnalyzer.FRAMETYPE[self.frame_type]) byte_msg.extend(self.filter_id) @@ -135,11 +135,11 @@ def init_frame(self, timeout=None): byte_msg.append(0x00) crc = Crc8Darc.calc(byte_msg[2:]) - crc_byte = struct.pack('B', crc) +# crc_byte = struct.pack('B', crc) - byte_msg.append(crc_byte) + byte_msg.append(crc) - logger.debug("init_frm:\t" + binascii.hexlify(byte_msg)) + logger.debug("init_frm:\t" + byte_msg.hex()) self.ser.write(byte_msg) def flush_buffer(self): @@ -161,7 +161,7 @@ def status_frame(self, timeout=None): byte_msg.append(crc_byte) - logger.debug("status_frm:\t" + binascii.hexlify(byte_msg)) + logger.debug("status_frm:\t" + byte_msg.hex()) self.ser.write(byte_msg) def send(self, msg, timeout=None): @@ -198,7 +198,7 @@ def send(self, msg, timeout=None): byte_msg.extend(msg.data) byte_msg.append(0x55) - logger.debug("Sending:\t" + binascii.hexlify(byte_msg)) + logger.debug("Sending:\t" + byte_msg.hex()) self.ser.write(byte_msg) def _recv_internal(self, timeout): diff --git a/can/interfaces/usb_can_analyzer/__init__.py b/can/interfaces/usb_can_analyzer/__init__.py deleted file mode 100644 index ec07c4d68..000000000 --- a/can/interfaces/usb_can_analyzer/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# coding: utf-8 - -""" -""" - -from can.interfaces.usb_can_analyzer.usb_can_analyzer import CanAnalyzer as Bus From aad6d6635b411fc17ce924af68e52d7665d76005 Mon Sep 17 00:00:00 2001 From: shedfly Date: Sun, 9 Jun 2019 16:36:17 +1000 Subject: [PATCH 0222/1235] update option name in setup.py to seeedstudio. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 03055ab18..85947c596 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ extras_require = { 'serial': ['pyserial~=3.0'], 'neovi': ['python-ics>=2.12'], - 'usb_can_analyzer': ['crccheck>=0.6','pyserial>=3.0'] + 'seeedstudio': ['crccheck>=0.6','pyserial>=3.0'] } tests_require = [ From 19287be69448db97ed26962193070e2b617d792b Mon Sep 17 00:00:00 2001 From: shedfly Date: Sun, 9 Jun 2019 17:09:49 +1000 Subject: [PATCH 0223/1235] tidy up merge conflicts. --- can/interfaces/__init__.py | 44 +++++++++++++++++++---------------- setup.py | 47 ++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index c17d85823..2f8768ed5 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -10,26 +10,30 @@ # interface_name => (module, classname) BACKENDS = { - 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), - 'socketcan': ('can.interfaces.socketcan', 'SocketcanBus'), - 'serial': ('can.interfaces.serial.serial_can','SerialBus'), - 'pcan': ('can.interfaces.pcan', 'PcanBus'), - 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), - 'ixxat': ('can.interfaces.ixxat', 'IXXATBus'), - 'nican': ('can.interfaces.nican', 'NicanBus'), - 'iscan': ('can.interfaces.iscan', 'IscanBus'), - 'virtual': ('can.interfaces.virtual', 'VirtualBus'), - 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), - 'vector': ('can.interfaces.vector', 'VectorBus'), - 'slcan': ('can.interfaces.slcan', 'slcanBus'), - 'canalystii': ('can.interfaces.canalystii', 'CANalystIIBus'), - 'systec': ('can.interfaces.systec', 'UcanBus'), - 'seeedstudio': ('can.interfaces.seeedstudio', 'CanAnalyzer') + "kvaser": ("can.interfaces.kvaser", "KvaserBus"), + "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), + "serial": ("can.interfaces.serial.serial_can", "SerialBus"), + "pcan": ("can.interfaces.pcan", "PcanBus"), + "usb2can": ("can.interfaces.usb2can", "Usb2canBus"), + "ixxat": ("can.interfaces.ixxat", "IXXATBus"), + "nican": ("can.interfaces.nican", "NicanBus"), + "iscan": ("can.interfaces.iscan", "IscanBus"), + "virtual": ("can.interfaces.virtual", "VirtualBus"), + "neovi": ("can.interfaces.ics_neovi", "NeoViBus"), + "vector": ("can.interfaces.vector", "VectorBus"), + "slcan": ("can.interfaces.slcan", "slcanBus"), + "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), + "systec": ("can.interfaces.systec", "UcanBus"), + "seeedstudio": ("can.interfaces.seeedstudio", "CanAnalyzer"), } -BACKENDS.update({ - interface.name: (interface.module_name, interface.attrs[0]) - for interface in iter_entry_points('can.interface') -}) +BACKENDS.update( + { + interface.name: (interface.module_name, interface.attrs[0]) + for interface in iter_entry_points("can.interface") + } +) -VALID_INTERFACES = frozenset(list(BACKENDS.keys()) + ['socketcan_native', 'socketcan_ctypes']) +VALID_INTERFACES = frozenset( + list(BACKENDS.keys()) + ["socketcan_native", "socketcan_ctypes"] +) diff --git a/setup.py b/setup.py index 85947c596..71427bbfa 100644 --- a/setup.py +++ b/setup.py @@ -15,30 +15,28 @@ logging.basicConfig(level=logging.WARNING) -with open('can/__init__.py', 'r') as fd: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - fd.read(), re.MULTILINE).group(1) +with open("can/__init__.py", "r") as fd: + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE + ).group(1) -with open('README.rst', 'r') as f: +with open("README.rst", "r") as f: long_description = f.read() # Dependencies -extras_require = { - 'serial': ['pyserial~=3.0'], - 'neovi': ['python-ics>=2.12'], - 'seeedstudio': ['crccheck>=0.6','pyserial>=3.0'] -} +extras_require = {"serial": ["pyserial~=3.0"], + "neovi": ["python-ics>=2.12"], + "seeedstudio": ["crccheck>=0.6","pyserial>=3.0"]} tests_require = [ - 'pytest~=4.3', - 'pytest-timeout~=1.3', - 'pytest-cov~=2.6', - 'codecov~=2.0', - 'six', - 'hypothesis' -] + extras_require['serial'] + "pytest~=4.3", + "pytest-timeout~=1.3", + "pytest-cov~=2.6", + "codecov~=2.0", + "hypothesis", +] + extras_require["serial"] -extras_require['test'] = tests_require +extras_require["test"] = tests_require setup( @@ -70,37 +68,32 @@ "Topic :: System :: Monitoring", "Topic :: System :: Networking", "Topic :: System :: Hardware :: Hardware Drivers", - "Topic :: Utilities" + "Topic :: Utilities", ], - # Code version=version, packages=find_packages(exclude=["test", "doc", "scripts", "examples"]), scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), - # Author author="Brian Thorne", author_email="brian@thorne.link", - # License license="LGPL v3", - # Package data package_data={ "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], "doc": ["*.*"], - "examples": ["*.py"] + "examples": ["*.py"], }, - # Installation # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=3.6", install_requires=[ - 'wrapt~=1.10', - 'aenum', + "wrapt~=1.10", + "aenum", 'windows-curses;platform_system=="Windows"', ], setup_requires=["pytest-runner"], extras_require=extras_require, - tests_require=tests_require + tests_require=tests_require, ) From 16deb5c43b1aa272f9f8d9e60235facfc4dd8d88 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Sun, 9 Jun 2019 20:27:43 +0200 Subject: [PATCH 0224/1235] Fixes, tweaks, and clarifications. --- can/bit_timing.py | 140 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 103 insertions(+), 37 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index b6313e910..10348f889 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -1,37 +1,66 @@ +class BitTiming: + """Representation of a bit timing configuration. + + The class can be constructed in various ways, depending on the information + available or the capabilities of the interfaces that need to be supported. + The preferred way is using bitrate, CAN clock frequency, TSEG1, TSEG2, SJW:: -class BitTiming: + can.BitTiming(bitrate=1000000, f_clock=8000000, tseg1=5, tseg2=1, sjw=1) + + If the clock frequency is unknown it may be omitted but some interfaces may + require it. + + Alternatively the BRP can be given instead of bitrate and clock frequency but this + will limit the number of supported interfaces. + + It is also possible specify BTR registers directly, + but will not work for all interfaces:: + + can.BitTiming(btr0=0x00, btr1=0x14) + """ - # Is this always 1? sync_seg = 1 def __init__( self, bitrate=None, + f_clock=None, brp=None, - sjw=None, tseg1=None, tseg2=None, + sjw=None, nof_samples=1, - f_clock=None, btr0=None, - btr1=None + btr1=None, ): - if brp is not None and (brp < 1 or brp > 64): - raise ValueError("brp must be 1 - 64") - if sjw is not None and (sjw < 1 or sjw > 4): - raise ValueError("sjw must be 1 - 4") - if tseg1 is not None and (tseg1 < 1 or tseg1 > 16): - raise ValueError("tseg1 must be 1 - 16") - if tseg2 is not None and (tseg2 < 1 or tseg2 > 8): - raise ValueError("tseg2 must be 1 - 8") - if nof_samples is not None and nof_samples not in (1, 3): - raise ValueError("nof_samples must be 1 or 3") - if btr0 is not None and (btr0 < 0 or btr0 > 255): - raise ValueError("btr0 must be 0 - 255") - if btr1 is not None and (btr1 < 0 or btr1 > 255): - raise ValueError("btr1 must be 0 - 255") - + """ + :param int bitrate: + Bitrate in bits/s. + :param int f_clock: + The CAN system clock frequency in Hz. + Usually the oscillator frequency divided by 2. + :param int brp: + Bit Rate Prescaler. Prefer to use bitrate and f_clock instead. + :param int tseg1: + Time segment 1, that is, the number of quanta from (but not including) + the Sync Segment to the sampling point. + :param int tseg2: + Time segment 2, that is, the number of quanta from the sampling + point to the end of the bit. + :param int sjw: + The Synchronization Jump Width. Decides the maximum number of time quanta + that the controller can resynchronize every bit. + :param int nof_samples: + Either 1 or 3. Some CAN controllers can also sample each bit three times. + In this case, the bit will be sampled three quanta in a row, + with the last sample being taken in the edge between TSEG1 and TSEG2. + Three samples should only be used for relatively slow baudrates. + :param int btr0: + The BTR0 register value used by many CAN controllers. + :param int btr1: + The BTR1 register value used by many CAN controllers. + """ self._bitrate = bitrate self._brp = brp self._sjw = sjw @@ -39,73 +68,110 @@ def __init__( self._tseg2 = tseg2 self._nof_samples = nof_samples self._f_clock = f_clock - self._btr0 = btr0 - self._btr1 = btr1 + + if btr0 is not None: + self._brp = (btr0 & 0x3F) + 1 + self._sjw = (btr0 >> 6) + 1 + if btr1 is not None: + self._tseg1 = (btr1 & 0xF) + 1 + self._tseg2 = ((btr1 >> 4) & 0x7) + 1 + self._nof_samples = 3 if btr1 & 0x80 else 1 @property def nbt(self): + """Nominal Bit Time.""" return self.sync_seg + self.tseg1 + self.tseg2 @property def bitrate(self): + """Bitrate in bits/s.""" if self._bitrate: return self._bitrate + if self._f_clock and self._brp: + return self._f_clock / (self._brp * self.nbt) raise ValueError("bitrate must be specified") @property def brp(self): + """Bit Rate Prescaler.""" if self._brp: return self._brp - return round(self.f_clock / (2 * self.bitrate * self.nbt)) + if self._f_clock and self._bitrate: + return round(self._f_clock / (self._bitrate * self.nbt)) + raise ValueError("Either bitrate and f_clock or brp must be specified") @property def sjw(self): + """Synchronization Jump Width.""" if not self._sjw: raise ValueError("sjw must be specified") return self._sjw @property def tseg1(self): + """Time segment 1. + + The number of quanta from (but not including) the Sync Segment to the sampling point. + """ if not self._tseg1: raise ValueError("tseg1 must be specified") return self._tseg1 @property def tseg2(self): + """Time segment 2. + + The number of quanta from the sampling point to the end of the bit. + """ if not self._tseg2: raise ValueError("tseg2 must be specified") return self._tseg2 @property def nof_samples(self): + """Number of samples (1 or 3).""" if not self._nof_samples: raise ValueError("nof_samples must be specified") return self._nof_samples @property def f_clock(self): + """The CAN system clock frequency in Hz. + + Usually the oscillator frequency divided by 2. + """ if not self._f_clock: raise ValueError("f_clock must be specified") return self._f_clock + @property + def sample_point(self): + """Sample point in percent.""" + return 100.0 * (self.nbt - self.tseg2) / self.nbt + @property def btr0(self): - if self._btr0 is not None: - return self._btr0 - btr0 = (self.sjw - 1) << 5 - btr0 |= self.brp - 1 - return btr0 + sjw = self.sjw + brp = self.brp + + if brp < 1 or brp > 64: + raise ValueError("brp must be 1 - 64") + if sjw < 1 or sjw > 4: + raise ValueError("sjw must be 1 - 4") + + return (sjw - 1) << 6 | brp - 1 @property def btr1(self): - if self._btr1 is not None: - return self._btr1 sam = 1 if self.nof_samples == 3 else 0 - btr1 = sam << 7 - btr1 |= (self.tseg2 - 1) << 4 - btr1 |= self.tseg1 - 1 - return btr1 + tseg1 = self.tseg1 + tseg2 = self.tseg2 - @property - def btr(self): - return self.btr0 << 8 | self.btr1 + if tseg1 < 1 or tseg1 > 16: + raise ValueError("tseg1 must be 1 - 16") + if tseg2 < 1 or tseg2 > 8: + raise ValueError("tseg2 must be 1 - 8") + if nof_samples not in (1, 3): + raise ValueError("nof_samples must be 1 or 3") + + return sam << 7 | (tseg2 - 1) << 4 | tseg1 - 1 From 80a7945bd07e7bc518fdc3a6133fb6f617f21c38 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Mon, 10 Jun 2019 21:51:12 +0200 Subject: [PATCH 0225/1235] Add __str__ method --- can/bit_timing.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index 10348f889..58efa2265 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -77,6 +77,9 @@ def __init__( self._tseg2 = ((btr1 >> 4) & 0x7) + 1 self._nof_samples = 3 if btr1 & 0x80 else 1 + if nof_samples not in (1, 3): + raise ValueError("nof_samples must be 1 or 3") + @property def nbt(self): """Nominal Bit Time.""" @@ -171,7 +174,37 @@ def btr1(self): raise ValueError("tseg1 must be 1 - 16") if tseg2 < 1 or tseg2 > 8: raise ValueError("tseg2 must be 1 - 8") - if nof_samples not in (1, 3): - raise ValueError("nof_samples must be 1 or 3") return sam << 7 | (tseg2 - 1) << 4 | tseg1 - 1 + + def __str__(self): + segments = [] + try: + segments.append(f"{self.bitrate} bits/s") + except ValueError: + pass + try: + segments.append(f"sample point: {self.sample_point:.2f}%") + except ValueError: + pass + try: + segments.append(f"BRP: {self.brp}") + except ValueError: + pass + try: + segments.append(f"TSEG1: {self.tseg1}") + except ValueError: + pass + try: + segments.append(f"TSEG2: {self.tseg2}") + except ValueError: + pass + try: + segments.append(f"SJW: {self.sjw}") + except ValueError: + pass + try: + segments.append(f"BTR: {self.btr0:02X}{self.btr1:02X}h") + except ValueError: + pass + return ", ".join(segments) From 3e33accd9423745e7f38cc9ba0c748f5d3a4f521 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Mon, 10 Jun 2019 21:51:23 +0200 Subject: [PATCH 0226/1235] Add unit tests --- test/bit_timing_test.py | 87 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 test/bit_timing_test.py diff --git a/test/bit_timing_test.py b/test/bit_timing_test.py new file mode 100644 index 000000000..ceb0023cc --- /dev/null +++ b/test/bit_timing_test.py @@ -0,0 +1,87 @@ +import can + + +def test_sja1000(): + """Test some values obtained using other bit timing calculators.""" + timing = can.BitTiming(f_clock=8000000, bitrate=125000, tseg1=11, tseg2=4, sjw=2) + assert timing.f_clock == 8000000 + assert timing.bitrate == 125000 + assert timing.brp == 4 + assert timing.nbt == 16 + assert timing.tseg1 == 11 + assert timing.tseg2 == 4 + assert timing.sjw == 2 + assert timing.sample_point == 75 + assert timing.btr0 == 0x43 + assert timing.btr1 == 0x3A + + timing = can.BitTiming(f_clock=8000000, bitrate=500000, tseg1=13, tseg2=2, sjw=1) + assert timing.f_clock == 8000000 + assert timing.bitrate == 500000 + assert timing.brp == 1 + assert timing.nbt == 16 + assert timing.tseg1 == 13 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + assert timing.sample_point == 87.5 + assert timing.btr0 == 0x00 + assert timing.btr1 == 0x1C + + timing = can.BitTiming(f_clock=8000000, bitrate=1000000, tseg1=5, tseg2=2, sjw=1) + assert timing.f_clock == 8000000 + assert timing.bitrate == 1000000 + assert timing.brp == 1 + assert timing.nbt == 8 + assert timing.tseg1 == 5 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + assert timing.sample_point == 75 + assert timing.btr0 == 0x00 + assert timing.btr1 == 0x14 + + +def test_can_fd(): + timing = can.BitTiming( + f_clock=80000000, bitrate=500000, tseg1=119, tseg2=40, sjw=40 + ) + assert timing.f_clock == 80000000 + assert timing.bitrate == 500000 + assert timing.brp == 1 + assert timing.nbt == 160 + assert timing.tseg1 == 119 + assert timing.tseg2 == 40 + assert timing.sjw == 40 + assert timing.sample_point == 75 + + timing = can.BitTiming( + f_clock=80000000, bitrate=2000000, tseg1=29, tseg2=10, sjw=10 + ) + assert timing.f_clock == 80000000 + assert timing.bitrate == 2000000 + assert timing.brp == 1 + assert timing.nbt == 40 + assert timing.tseg1 == 29 + assert timing.tseg2 == 10 + assert timing.sjw == 10 + assert timing.sample_point == 75 + + +def test_from_btr(): + timing = can.BitTiming(f_clock=8000000, btr0=0x00, btr1=0x14) + assert timing.bitrate == 1000000 + assert timing.brp == 1 + assert timing.nbt == 8 + assert timing.tseg1 == 5 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + assert timing.sample_point == 75 + assert timing.btr0 == 0x00 + assert timing.btr1 == 0x14 + + +def test_string_representation(): + timing = can.BitTiming(f_clock=8000000, bitrate=1000000, tseg1=5, tseg2=2, sjw=1) + assert ( + str(timing) + == "1000000 bits/s, sample point: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, BTR: 0014h" + ) From 950a9c6924e682b076d21b95f6e631111452ff67 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 11 Jun 2019 13:10:24 +0200 Subject: [PATCH 0227/1235] Rename and add some test --- test/{bit_timing_test.py => test_bit_timing.py} | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) rename test/{bit_timing_test.py => test_bit_timing.py} (91%) diff --git a/test/bit_timing_test.py b/test/test_bit_timing.py similarity index 91% rename from test/bit_timing_test.py rename to test/test_bit_timing.py index ceb0023cc..0b22e308f 100644 --- a/test/bit_timing_test.py +++ b/test/test_bit_timing.py @@ -3,7 +3,9 @@ def test_sja1000(): """Test some values obtained using other bit timing calculators.""" - timing = can.BitTiming(f_clock=8000000, bitrate=125000, tseg1=11, tseg2=4, sjw=2) + timing = can.BitTiming( + f_clock=8000000, bitrate=125000, tseg1=11, tseg2=4, sjw=2, nof_samples=3 + ) assert timing.f_clock == 8000000 assert timing.bitrate == 125000 assert timing.brp == 4 @@ -11,9 +13,10 @@ def test_sja1000(): assert timing.tseg1 == 11 assert timing.tseg2 == 4 assert timing.sjw == 2 + assert timing.nof_samples == 3 assert timing.sample_point == 75 assert timing.btr0 == 0x43 - assert timing.btr1 == 0x3A + assert timing.btr1 == 0xBA timing = can.BitTiming(f_clock=8000000, bitrate=500000, tseg1=13, tseg2=2, sjw=1) assert timing.f_clock == 8000000 @@ -23,6 +26,7 @@ def test_sja1000(): assert timing.tseg1 == 13 assert timing.tseg2 == 2 assert timing.sjw == 1 + assert timing.nof_samples == 1 assert timing.sample_point == 87.5 assert timing.btr0 == 0x00 assert timing.btr1 == 0x1C @@ -35,6 +39,7 @@ def test_sja1000(): assert timing.tseg1 == 5 assert timing.tseg2 == 2 assert timing.sjw == 1 + assert timing.nof_samples == 1 assert timing.sample_point == 75 assert timing.btr0 == 0x00 assert timing.btr1 == 0x14 From efc1cf888d20bc673bf829d0e04b319fb8e4a90e Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 11 Jun 2019 13:22:17 +0200 Subject: [PATCH 0228/1235] Add docs --- doc/api.rst | 1 + doc/bit_timing.rst | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 doc/bit_timing.rst diff --git a/doc/api.rst b/doc/api.rst index 640f61e2d..1aef90e9a 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -17,6 +17,7 @@ A form of CAN interface is also required. listeners asyncio bcm + bit_timing internal-api diff --git a/doc/bit_timing.rst b/doc/bit_timing.rst new file mode 100644 index 000000000..1b25adf06 --- /dev/null +++ b/doc/bit_timing.rst @@ -0,0 +1,4 @@ +Bit Timing Configuration +======================== + +.. autoclass:: can.BitTiming From 7b6c4221d4456097828ff49b4b03cf2fb94cfa93 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 11 Jun 2019 13:38:39 +0200 Subject: [PATCH 0229/1235] Fix Lint --- can/bit_timing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index 58efa2265..a513b477e 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -1,13 +1,13 @@ class BitTiming: """Representation of a bit timing configuration. - + The class can be constructed in various ways, depending on the information available or the capabilities of the interfaces that need to be supported. The preferred way is using bitrate, CAN clock frequency, TSEG1, TSEG2, SJW:: can.BitTiming(bitrate=1000000, f_clock=8000000, tseg1=5, tseg2=1, sjw=1) - + If the clock frequency is unknown it may be omitted but some interfaces may require it. @@ -123,7 +123,7 @@ def tseg1(self): @property def tseg2(self): """Time segment 2. - + The number of quanta from the sampling point to the end of the bit. """ if not self._tseg2: From 6beeecbd0e5fd015ddd6b8f28cfbe2041b880e44 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 11 Jun 2019 14:43:33 +0200 Subject: [PATCH 0230/1235] Remove OSX + Python 3.7 & nightly It is unsupported and causes problems. See [here](https://github.com/hardbyte/python-can/pull/554#issuecomment-500453610). --- .travis.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 50909340f..2eca95d6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,14 +63,6 @@ jobs: os: osx osx_image: xcode8.3 python: 3.6-dev - - stage: test - os: osx - osx_image: xcode8.3 - python: 3.7-dev - - stage: test - os: osx - osx_image: xcode8.3 - python: nightly - stage: documentation name: "Sphinx Build" From afc6eaf5dd75c97bbb81d20c8902a21edfe264bd Mon Sep 17 00:00:00 2001 From: nick black Date: Tue, 11 Jun 2019 11:11:46 -0400 Subject: [PATCH 0231/1235] [canalystii] fix param description bytes->bits --- can/interfaces/canalystii.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index cc68ff240..1134ffe51 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -86,7 +86,7 @@ def __init__( :param channel: channel number :param device: device number - :param bitrate: CAN network bandwidth (bytes/s) + :param bitrate: CAN network bandwidth (bits/s) :param Timing0: customize the timing register if bitrate is not specified :param Timing1: :param can_filters: filters for packet From 2a61ed914be543dd0db59e7c7c1c23e0ba0e7a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Tue, 11 Jun 2019 08:14:07 -0400 Subject: [PATCH 0232/1235] Use inter-process mutex to prevent concurrent neoVI device open When neoVI server is enabled, there is an issue with concurrent device open. --- can/interfaces/ics_neovi/neovi_bus.py | 12 +++++++++++- setup.py | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 3df0ccc39..9f341963e 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -11,9 +11,12 @@ """ import logging +import os +import tempfile from collections import deque from can import Message, CanError, BusABC +from filelock import FileLock logger = logging.getLogger(__name__) @@ -122,7 +125,14 @@ def __init__(self, channel, can_filters=None, **kwargs): type_filter = kwargs.get("type_filter") serial = kwargs.get("serial") self.dev = self._find_device(type_filter, serial) - ics.open_device(self.dev) + + # Use inter-process mutex to prevent concurrent device open. + # When neoVI server is enabled, there is an issue with concurrent + # device open. + open_lock = FileLock( + os.path.join(tempfile.gettempdir(), 'neovi.lock')) + with open_lock: + ics.open_device(self.dev) if "bitrate" in kwargs: for channel in self.channels: diff --git a/setup.py b/setup.py index 1918b644b..576bf1e28 100644 --- a/setup.py +++ b/setup.py @@ -90,6 +90,7 @@ "wrapt~=1.10", "aenum", 'windows-curses;platform_system=="Windows"', + 'filelock' ], setup_requires=["pytest-runner"], extras_require=extras_require, From d1aa80c0040af89f19c7c1e848f937d469b3a3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Tue, 11 Jun 2019 08:51:25 -0400 Subject: [PATCH 0233/1235] Move neoVI open lock to module level --- can/interfaces/ics_neovi/neovi_bus.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 9f341963e..ad53850b3 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -15,9 +15,10 @@ import tempfile from collections import deque -from can import Message, CanError, BusABC from filelock import FileLock +from can import Message, CanError, BusABC + logger = logging.getLogger(__name__) try: @@ -31,6 +32,11 @@ ics = None +# Use inter-process mutex to prevent concurrent device open. +# When neoVI server is enabled, there is an issue with concurrent device open. +open_lock = FileLock(os.path.join(tempfile.gettempdir(), "neovi.lock")) + + class ICSApiError(CanError): """ Indicates an error with the ICS API. @@ -126,11 +132,6 @@ def __init__(self, channel, can_filters=None, **kwargs): serial = kwargs.get("serial") self.dev = self._find_device(type_filter, serial) - # Use inter-process mutex to prevent concurrent device open. - # When neoVI server is enabled, there is an issue with concurrent - # device open. - open_lock = FileLock( - os.path.join(tempfile.gettempdir(), 'neovi.lock')) with open_lock: ics.open_device(self.dev) From c2e767b35887b5cf4b3994a32fa2c91939c660ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Tue, 11 Jun 2019 10:55:59 -0400 Subject: [PATCH 0234/1235] Adding filelock to neovi extra requirements --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 576bf1e28..9ea7b78fb 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,10 @@ long_description = f.read() # Dependencies -extras_require = {"serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12"]} +extras_require = { + "serial": ["pyserial~=3.0"], + "neovi": ["python-ics>=2.12", "filelock"], +} tests_require = [ "pytest~=4.3", From 3dc6dfc60eb73231af00da596d845cd5bc009fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Tue, 11 Jun 2019 13:37:41 -0400 Subject: [PATCH 0235/1235] Adding dummy file lock when importing FileLock fails --- can/interfaces/ics_neovi/neovi_bus.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index ad53850b3..55a510283 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -15,8 +15,6 @@ import tempfile from collections import deque -from filelock import FileLock - from can import Message, CanError, BusABC logger = logging.getLogger(__name__) @@ -32,6 +30,30 @@ ics = None +try: + from filelock import FileLock +except ImportError as ie: + + logger.warning( + "Using ICS NeoVi can backend without the " + "filelock module installed may cause some issues!: %s", + ie, + ) + + class FileLock: + """Dummy file lock that do not actually do anything""" + + def __init__(self, lock_file, timeout=-1): + self._lock_file = lock_file + self.timeout = timeout + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + return None + + # Use inter-process mutex to prevent concurrent device open. # When neoVI server is enabled, there is an issue with concurrent device open. open_lock = FileLock(os.path.join(tempfile.gettempdir(), "neovi.lock")) From e18c2d51db76bb6cd9f8f4d285a7bba44205f53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Tue, 11 Jun 2019 14:08:59 -0400 Subject: [PATCH 0236/1235] Using doubles quote since Black refers double quotes --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9ea7b78fb..fb1a4de54 100644 --- a/setup.py +++ b/setup.py @@ -93,7 +93,7 @@ "wrapt~=1.10", "aenum", 'windows-curses;platform_system=="Windows"', - 'filelock' + "filelock", ], setup_requires=["pytest-runner"], extras_require=extras_require, From 33ee636de54039481769ff3603f4ab23ba6822ec Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 11 Jun 2019 21:17:09 +0200 Subject: [PATCH 0237/1235] Add typings --- can/bit_timing.py | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index a513b477e..0b892640d 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -1,3 +1,6 @@ +from typing import Union + + class BitTiming: """Representation of a bit timing configuration. @@ -24,15 +27,15 @@ class BitTiming: def __init__( self, - bitrate=None, - f_clock=None, - brp=None, - tseg1=None, - tseg2=None, - sjw=None, - nof_samples=1, - btr0=None, - btr1=None, + bitrate: int = None, + f_clock: int = None, + brp: int = None, + tseg1: int = None, + tseg2: int = None, + sjw: int = None, + nof_samples: int = 1, + btr0: int = None, + btr1: int = None, ): """ :param int bitrate: @@ -81,12 +84,12 @@ def __init__( raise ValueError("nof_samples must be 1 or 3") @property - def nbt(self): + def nbt(self) -> int: """Nominal Bit Time.""" return self.sync_seg + self.tseg1 + self.tseg2 @property - def bitrate(self): + def bitrate(self) -> Union[int, float]: """Bitrate in bits/s.""" if self._bitrate: return self._bitrate @@ -95,7 +98,7 @@ def bitrate(self): raise ValueError("bitrate must be specified") @property - def brp(self): + def brp(self) -> int: """Bit Rate Prescaler.""" if self._brp: return self._brp @@ -104,14 +107,14 @@ def brp(self): raise ValueError("Either bitrate and f_clock or brp must be specified") @property - def sjw(self): + def sjw(self) -> int: """Synchronization Jump Width.""" if not self._sjw: raise ValueError("sjw must be specified") return self._sjw @property - def tseg1(self): + def tseg1(self) -> int: """Time segment 1. The number of quanta from (but not including) the Sync Segment to the sampling point. @@ -121,7 +124,7 @@ def tseg1(self): return self._tseg1 @property - def tseg2(self): + def tseg2(self) -> int: """Time segment 2. The number of quanta from the sampling point to the end of the bit. @@ -131,14 +134,14 @@ def tseg2(self): return self._tseg2 @property - def nof_samples(self): + def nof_samples(self) -> int: """Number of samples (1 or 3).""" if not self._nof_samples: raise ValueError("nof_samples must be specified") return self._nof_samples @property - def f_clock(self): + def f_clock(self) -> int: """The CAN system clock frequency in Hz. Usually the oscillator frequency divided by 2. @@ -148,12 +151,12 @@ def f_clock(self): return self._f_clock @property - def sample_point(self): + def sample_point(self) -> float: """Sample point in percent.""" return 100.0 * (self.nbt - self.tseg2) / self.nbt @property - def btr0(self): + def btr0(self) -> int: sjw = self.sjw brp = self.brp @@ -165,7 +168,7 @@ def btr0(self): return (sjw - 1) << 6 | brp - 1 @property - def btr1(self): + def btr1(self) -> int: sam = 1 if self.nof_samples == 3 else 0 tseg1 = self.tseg1 tseg2 = self.tseg2 @@ -177,7 +180,7 @@ def btr1(self): return sam << 7 | (tseg2 - 1) << 4 | tseg1 - 1 - def __str__(self): + def __str__(self) -> str: segments = [] try: segments.append(f"{self.bitrate} bits/s") From 200707f566fce8078510c09473644bc54bb95b20 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 9 Jun 2019 12:29:15 +0200 Subject: [PATCH 0238/1235] Add badge for black Adds this badge: https://camo.githubusercontent.com/28a51fe3a2c05048d8ca8ecd039d6b1619037326/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64652532307374796c652d626c61636b2d3030303030302e737667 --- README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5b29f46ad..ee489d06f 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ python-can ========== -|release| |docs| |build_travis| |build_appveyor| |coverage| |downloads| +|release| |docs| |build_travis| |build_appveyor| |coverage| |downloads| |formatter| .. |release| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ @@ -27,6 +27,10 @@ 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 From fa3ab30273574f396104e0b99fed27530b5906d8 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Wed, 12 Jun 2019 12:56:19 +0200 Subject: [PATCH 0239/1235] Allow bit timing to be set from config file CAN-FD not supported. --- can/bit_timing.py | 19 +++++++++++++++++++ can/util.py | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/can/bit_timing.py b/can/bit_timing.py index 0b892640d..8d9cf5727 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -211,3 +211,22 @@ def __str__(self) -> str: except ValueError: pass return ", ".join(segments) + + def __repr__(self) -> str: + kwargs = {} + if self._f_clock: + kwargs["f_clock"] = self._f_clock + if self._bitrate: + kwargs["bitrate"] = self._bitrate + if self._brp: + kwargs["brp"] = self._brp + if self._tseg1: + kwargs["tseg1"] = self._tseg1 + if self._tseg2: + kwargs["tseg2"] = self._tseg2 + if self._sjw: + kwargs["sjw"] = self._sjw + if self._nof_samples != 1: + kwargs["nof_samples"] = self._nof_samples + args = ", ".join(f"{key}={value}" for key, value in kwargs.items()) + return f"can.BitTiming({args})" diff --git a/can/util.py b/can/util.py index c04bb70c9..803fa2388 100644 --- a/can/util.py +++ b/can/util.py @@ -166,6 +166,25 @@ def load_config(path=None, config=None, context=None): if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) + # Create bit timing configuration if given + timing_conf = {} + for key in ( + "f_clock", + "brp", + "tseg1", + "tseg2", + "sjw", + "nof_samples", + "btr0", + "btr1", + ): + if key in config: + timing_conf[key] = int(config[key], base=0) + del config[key] + if timing_conf: + timing_conf["bitrate"] = config.get("bitrate") + config["timing"] = can.BitTiming(**timing_conf) + can.log.debug("can config: {}".format(config)) return config From 587de9b663dee7fded611314aafcfa7779704b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Wed, 12 Jun 2019 07:44:36 -0400 Subject: [PATCH 0240/1235] Grammar fix --- can/interfaces/ics_neovi/neovi_bus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 55a510283..176747ad2 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -41,7 +41,7 @@ ) class FileLock: - """Dummy file lock that do not actually do anything""" + """Dummy file lock that does not actually do anything""" def __init__(self, lock_file, timeout=-1): self._lock_file = lock_file From 3ded7834c24e9183c87e1757539ab6690535be7d Mon Sep 17 00:00:00 2001 From: shedfly Date: Wed, 12 Jun 2019 22:00:10 +1000 Subject: [PATCH 0241/1235] adding doc and cleaning up comments. Changing to Bus class name SeeedBus. --- can/interfaces/__init__.py | 2 +- can/interfaces/seeedstudio/__init__.py | 2 +- can/interfaces/seeedstudio/notes | 167 ---------------------- can/interfaces/seeedstudio/seeedstudio.py | 81 +++++------ doc/interfaces/seeedstudio.rst | 85 +++++++++++ 5 files changed, 124 insertions(+), 213 deletions(-) delete mode 100644 can/interfaces/seeedstudio/notes create mode 100644 doc/interfaces/seeedstudio.rst diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 2f8768ed5..66e55153d 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -24,7 +24,7 @@ "slcan": ("can.interfaces.slcan", "slcanBus"), "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), "systec": ("can.interfaces.systec", "UcanBus"), - "seeedstudio": ("can.interfaces.seeedstudio", "CanAnalyzer"), + "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), } BACKENDS.update( diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py index 32306df97..507ac873e 100644 --- a/can/interfaces/seeedstudio/__init__.py +++ b/can/interfaces/seeedstudio/__init__.py @@ -3,4 +3,4 @@ """ """ -from can.interfaces.seeedstudio.seeedstudio import CanAnalyzer +from can.interfaces.seeedstudio.seeedstudio import SeeedBus diff --git a/can/interfaces/seeedstudio/notes b/can/interfaces/seeedstudio/notes deleted file mode 100644 index 829c85173..000000000 --- a/can/interfaces/seeedstudio/notes +++ /dev/null @@ -1,167 +0,0 @@ -USB-CAN Analyzer - USB to CAN Bus Serial Protocol Definition - -Posted by Wilfried Voss on September 19, 2018 - copperhilltech.com - -CAN Bus To USB Mini Converter - -The USB-CAN Analyzer, in combination with the corresponding Windows software, represents a very economical solution to run an effective CAN Bus Analyzer. It allows you to develop, test, manage, and maintain your own CAN Bus network, as well as receiving, sending, logging, and analyzing CAN Bus data. - -For the following, we need to point out that the device is manufactured in China, and documentation is sparse. And while this is a very affordable solution, there is naturally room for some improvement. In general, there is no application interface, or, to be more precise, the communication between the USB-CAN device and the host system, in this case a PC running Windows, is not documented. Consequently, there is currently no software support for Linux (e.g. SocketCAN), and it would be useful if users could write their own applications, regardless of the operating system they use. - -As a first step in this direction, we invested some efforts to analyze and document the USB communication protocol, which makes it easy developing an application interface. - -There has been a document, USB (Serial port) to CAN protocol defines, that was distributed online, claiming a documentation of the "Send and receive data packet format." However, after analyzing the protocol, we believe that the data frame architecture described therein represents the original intention. - -We believe that the architecture as described caused problems when it came to higher baud rates, specifically at 1 Mbps in combination with high busloads. Consequently, the firmware developers decided to optimize the data frame size. As a result, they cut the frame size from a static 20 bytes to a dynamic 6 to 16 bytes, depending on message ID length (11 or 29 bits) and number of data bytes. - -The following describes the serial protocol as we analyzed it, with a few unknowns remaining. However, the documentation includes all vital information for creating your own CAN Bus application. - -CAN Bus Data Frame - -The CAN Bus data frame format applies to both, send and receive. - -Both message ID lengths, 11-bit standard and 29-bit extended, use the first two bytes in the serial data frame. Depending on the message ID length, the remaining bytes will be filled differently. - -The data frame does not utilize a checksum check, most probably due to keeping the number of transmitted bytes to a minimum and cutting down on processing time. - -Byte Description - -0 0xAA = Packet Start - -1 CAN Bus Data Frame Information - - Bit 7 Always 1 - - Bit 6 Always 1 - - Bit 5 0=STD; 1=EXT - - Bit 4 0=Data; 1=Remote - - Bit 3…0 Data Length Code (DLC); 0…8 - -Standard CAN data frame continues: - -Byte Description - -2 Message ID LSB - -3 Message ID MSB - -x Data, length depending on DLC - -y 0x55 = Packet End - -Extended CAN data frame continues: - -Byte Description - -2 Message ID LSB - -3 Message ID 2ND - -4 Message ID 3RD - -5 Message ID MSB - -x Data, length depending on DLC - -y 0x55 = Packet End - -CAN Bus Data Frame Size - -Standard 11-bit 6 bytes with zero data bytes - - 14 bytes with eight data bytes - -Extended 29-bit 8 bytes with zero data bytes - - 16 bytes with eight data bytes - -CAN Bus Initialization Frame - -The CAN Bus initialization frame has a constant length of 20 bytes. - -Byte(s) Description - -0 0xAA = Frame Start Byte 1 - -1 0x55 = Frame Start Byte 2 - -2 0x12 = Initialization Message ID - -3 CAN Baud Rate (See table below) - -4 0x01=STD; 0x02=EXT; Applies only to transmitting - -5 – 8 Filter ID; LSB first, MSB last - -9 – 12 Mask ID; LSB first, MSB last - -13 Operation Mode (See table below) - -14 0x01; Purpose Unknown - -15 – 18 All 0x00 - -19 Checksum - -Note: The checksum is processed between bytes 2 and 18. - -CAN Bus baud rate - -0x01 1000k - -0x02 800k - -0x03 500k - -0x04 400k - -0x05 250k - -0x06 200k - -0x07 125k - -0x08 100k - -0x09 50k - -0x0A 20k - -0x0B 10k - -0x0C 5k - -Operation Mode - -0x00 Normal - -0x01 Loopback - -0x02 Silent (Listen-Only) - -0x03 Loopback + Silent - -CAN Controller Status Frame - -The format of the status request and status report frames are identical, where the request frame sends all zeroes between the frame header (frame start tokens plus frame ID) and the checksum, i.e. bytes 3 through 18 are 0x00. - -Byte(s) Description - -0 0xAA = Frame Start Byte 1 - -1 0x55 = Frame Start Byte 2 - -2 0x04 = Status Message ID - -3 Receive Error Counter - -4 Transmit Error Counter - -5 – 18 Unknown Purpose - In case of status report, they may be filled with data - -19 Checksum - diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 9bd38bf3d..390ef7e6d 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -2,25 +2,19 @@ """ To Support the Seeed USB-Can analyzer interface. The device will appear -as a serial port, for example "/dev/ttyS1" or "/dev/ttyUSB0" on Linux -machines or "COM1" on Windows. +as a serial port, for example "/dev/ttyUSB0" on Linux machines +or "COM1" on Windows. https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html SKU 114991193 -See protoocl: -https://copperhilltech.com/blog/usbcan-analyzer-usb-to-can-bus-serial-protocol-definition/ - this file uses Crc8Darc checksums. """ -from __future__ import absolute_import, division - import logging import struct -import binascii from time import sleep, time from can import BusABC, Message -logger = logging.getLogger('can.CanAnalyzer') +logger = logging.getLogger(__name__) try: import serial @@ -32,16 +26,11 @@ try: from crccheck.crc import Crc8Darc except ImportError: - logger.warning("The interface requires the install option crccheck.") + logger.warning("The interface requires the install option seeddstudio.") - -class CanAnalyzer(BusABC): +class SeeedBus(BusABC): """ - Enable basic can communication over a serial device. - - .. note:: See :meth:`can.interfaces.serial.CanAnalyzer._recv_internal` - for some special semantics. - + Enable basic can communication over a USB-CAN-Analyzer device. """ BITRATE = { 1000000: 0x01, @@ -70,25 +59,24 @@ class CanAnalyzer(BusABC): "loopback_and_silent":0x03 } - def __init__(self, channel, baudrate=2000000, timeout=0.1, rtscts=False, - frame_type='STD', operation_mode='normal', bit_rate=500000, - *args, **kwargs): + def __init__(self, channel, baudrate=2000000, timeout=0.1, frame_type="STD", + operation_mode="normal", bit_rate=500000, *args, **kwargs): """ :param str channel: The serial device to open. For example "/dev/ttyS1" or "/dev/ttyUSB0" on Linux or "COM1" on Windows systems. - :param int baudrate: - Baud rate of the serial device in bit/s (default 115200). - - .. warning:: - Some serial port implementations don't care about the baudrate. + :param baudrate: + The default matches required baudrate :param float timeout: Timeout for the serial device in seconds (default 0.1). - :param bool rtscts: - turn hardware handshake (RTS/CTS) on and off + :param str frame_type: + STD or EXT, to select standard or extended messages + + :param operation_mode + normal, loopback, silent or loopback_and_silent. """ self.bit_rate = bit_rate @@ -101,9 +89,9 @@ def __init__(self, channel, baudrate=2000000, timeout=0.1, rtscts=False, self.channel_info = "Serial interface: " + channel self.ser = serial.Serial( - channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts) + channel, baudrate=baudrate, timeout=timeout, rtscts=False) - super(CanAnalyzer, self).__init__(channel=channel, *args, **kwargs) + super(SeeedBus, self).__init__(channel=channel, *args, **kwargs) self.init_frame() def shutdown(self): @@ -113,30 +101,29 @@ def shutdown(self): self.ser.close() def init_frame(self, timeout=None): + """ + Send init message to setup the device for comms. this is called during + interface creation. + :param timeout: + This parameter will be ignored. The timeout value of the channel is + used instead. + """ byte_msg = bytearray() byte_msg.append(0xAA) # Frame Start Byte 1 byte_msg.append(0x55) # Frame Start Byte 2 - byte_msg.append(0x12) # Initialization Message ID - - byte_msg.append(CanAnalyzer.BITRATE[self.bit_rate]) # CAN Baud Rate - byte_msg.append(CanAnalyzer.FRAMETYPE[self.frame_type]) - + byte_msg.append(SeeedBus.BITRATE[self.bit_rate]) # CAN Baud Rate + byte_msg.append(SeeedBus.FRAMETYPE[self.frame_type]) byte_msg.extend(self.filter_id) - byte_msg.extend(self.mask_id) - - byte_msg.append(CanAnalyzer.OPERATIONMODE[self.op_mode]) - + byte_msg.append(SeeedBus.OPERATIONMODE[self.op_mode]) byte_msg.append(0x01) for i in range(0, 4): byte_msg.append(0x00) crc = Crc8Darc.calc(byte_msg[2:]) -# crc_byte = struct.pack('B', crc) - byte_msg.append(crc) logger.debug("init_frm:\t" + byte_msg.hex()) @@ -146,6 +133,13 @@ def flush_buffer(self): self.ser.flushInput() def status_frame(self, timeout=None): + """ + Send status message over the serial device. + + :param timeout: + This parameter will be ignored. The timeout value of the channel is + used instead. + """ byte_msg = bytearray() byte_msg.append(0xAA) # Frame Start Byte 1 byte_msg.append(0x55) # Frame Start Byte 2 @@ -157,9 +151,7 @@ def status_frame(self, timeout=None): byte_msg.append(0x00) crc = Crc8Darc.calc(byte_msg[2:]) - crc_byte = struct.pack('B', crc) - - byte_msg.append(crc_byte) + byte_msg.append(crc) logger.debug("status_frm:\t" + byte_msg.hex()) self.ser.write(byte_msg) @@ -208,7 +200,8 @@ def _recv_internal(self, timeout): :param timeout: .. warning:: - This parameter will be ignored. The timeout value of the channel is used. + This parameter will be ignored. The timeout value of the + channel is used. :returns: Received message and False (because not filtering as taken place). diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst new file mode 100644 index 000000000..35cc25b93 --- /dev/null +++ b/doc/interfaces/seeedstudio.rst @@ -0,0 +1,85 @@ +.. _seeeddoc: + + +USB-CAN Analyzer +================ +...by Seeed Studio + +SKU: 114991193 + +Links: + +- https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html +- https://github.com/SeeedDocument/USB-CAN_Analyzer +- https://copperhilltech.com/blog/usbcan-analyzer-usb-to-can-bus-serial-protocol-definition/ + +^^^^^^^^^^ + +Installation +------------ +This interface has additional dependencies which can be installed using pip and the optional extra [seeedstudio]. That will install two additional packages if not already available: + - pyserial + - crccheck + + +:: + + pip3 install python-can[seeedstudio] + + +^^^^^^^^^^ + + +Interface +--------- + +:: + + can.interfaces.seeedstudio.SeeedBus + +A bus example:: + + bus = can.interface.Bus(bustype='seeedstudio', channel='/dev/ttyUSB0', bitrate=500000) + + +^^^^^^^^^^ + +Parameters +---------- +:: + + SeeedBus(channel, + timeout=0.1, + frame_type='STD', + operation_mode='normal', + bit_rate=500000) + +ChANNEL + The serial port created by the USB device when connected. + +TIMEOUT + Only used by the underling serial port, it probably should not be changed. The serial port baudrate=2000000 and rtscts=false are also matched to the device so are not added here. + +FRAMETYPE + - "STD" + - "EXT" + +OPERATIONMODE + - "normal" + - "loopback" + - "silent" + - "loopback_and_silent" + +BITRATE + - 1000000 + - 800000 + - 500000 + - 400000 + - 250000 + - 200000 + - 125000 + - 100000 + - 50000 + - 20000 + - 10000 + - 5000 From d3ef672d4a1f287e949a91117aadcfb58c5cf1aa Mon Sep 17 00:00:00 2001 From: shedfly Date: Wed, 12 Jun 2019 22:03:17 +1000 Subject: [PATCH 0242/1235] changing from bit_rate to bitrate to match other interfaces and examples. --- can/interfaces/seeedstudio/seeedstudio.py | 7 +++++-- doc/interfaces/seeedstudio.rst | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 390ef7e6d..33493ca1b 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -60,7 +60,7 @@ class SeeedBus(BusABC): } def __init__(self, channel, baudrate=2000000, timeout=0.1, frame_type="STD", - operation_mode="normal", bit_rate=500000, *args, **kwargs): + operation_mode="normal", bitrate=500000, *args, **kwargs): """ :param str channel: The serial device to open. For example "/dev/ttyS1" or @@ -78,8 +78,11 @@ def __init__(self, channel, baudrate=2000000, timeout=0.1, frame_type="STD", :param operation_mode normal, loopback, silent or loopback_and_silent. + :param bitrate + CAN bus bit rate, selected from available list. + """ - self.bit_rate = bit_rate + self.bit_rate = bitrate self.frame_type = frame_type self.op_mode = operation_mode self.filter_id = bytearray([0x00, 0x00, 0x00, 0x00]) diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst index 35cc25b93..b018a6643 100644 --- a/doc/interfaces/seeedstudio.rst +++ b/doc/interfaces/seeedstudio.rst @@ -49,10 +49,11 @@ Parameters :: SeeedBus(channel, + baudrate=2000000, timeout=0.1, frame_type='STD', operation_mode='normal', - bit_rate=500000) + bitrate=500000) ChANNEL The serial port created by the USB device when connected. From fde4fdd6be690a0de2845069ec6796a762975b9c Mon Sep 17 00:00:00 2001 From: shedfly Date: Wed, 12 Jun 2019 22:06:01 +1000 Subject: [PATCH 0243/1235] corrected crc calc and removed crccheck package. --- can/interfaces/seeedstudio/seeedstudio.py | 24 ++++++++++------------- setup.py | 2 +- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 33493ca1b..351ecb93f 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -6,7 +6,6 @@ or "COM1" on Windows. https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html SKU 114991193 -this file uses Crc8Darc checksums. """ import logging @@ -14,7 +13,7 @@ from time import sleep, time from can import BusABC, Message -logger = logging.getLogger(__name__) +logger = logging.getLogger('seeedbus') try: import serial @@ -23,11 +22,6 @@ "the serial module installed!") serial = None -try: - from crccheck.crc import Crc8Darc -except ImportError: - logger.warning("The interface requires the install option seeddstudio.") - class SeeedBus(BusABC): """ Enable basic can communication over a USB-CAN-Analyzer device. @@ -121,12 +115,12 @@ def init_frame(self, timeout=None): byte_msg.extend(self.filter_id) byte_msg.extend(self.mask_id) byte_msg.append(SeeedBus.OPERATIONMODE[self.op_mode]) - byte_msg.append(0x01) + byte_msg.append(0x01) # Follows 'Send once' in windows app. - for i in range(0, 4): + for i in range(0, 4): # Manual bitrate config, details unknown. byte_msg.append(0x00) - crc = Crc8Darc.calc(byte_msg[2:]) + crc = sum(byte_msg[2:]) & 0xFF byte_msg.append(crc) logger.debug("init_frm:\t" + byte_msg.hex()) @@ -137,7 +131,8 @@ def flush_buffer(self): def status_frame(self, timeout=None): """ - Send status message over the serial device. + Send status request message over the serial device. The device will + respond but details of error codes are unknown but are logged - DEBUG. :param timeout: This parameter will be ignored. The timeout value of the channel is @@ -153,7 +148,7 @@ def status_frame(self, timeout=None): for i in range(0, 14): byte_msg.append(0x00) - crc = Crc8Darc.calc(byte_msg[2:]) + crc = sum(byte_msg[2:]) & 0xFF byte_msg.append(crc) logger.debug("status_frm:\t" + byte_msg.hex()) @@ -193,7 +188,7 @@ def send(self, msg, timeout=None): byte_msg.extend(msg.data) byte_msg.append(0x55) - logger.debug("Sending:\t" + byte_msg.hex()) + logger.debug("sending:\t" + byte_msg.hex()) self.ser.write(byte_msg) def _recv_internal(self, timeout): @@ -224,7 +219,8 @@ def _recv_internal(self, timeout): rx_byte_2 = ord(self.ser.read()) time_stamp = time() if rx_byte_2 == 0x55: - rx_msg_type = self.ser.read() + status = bytearray(self.ser.read(18)) + logger.debug("status resp:\t" + status.hex()) else: length = int(rx_byte_2 & 0x0F) diff --git a/setup.py b/setup.py index 29a50329f..ef3cc3ca0 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ extras_require = { "serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12"], - "seeedstudio": ["crccheck>=0.6", "pyserial>=3.0"], + "seeedstudio": ["pyserial>=3.0"], } tests_require = [ From 0c0b9a1b1d18dacf50ad8de293b296f47b96a811 Mon Sep 17 00:00:00 2001 From: shedfly Date: Wed, 12 Jun 2019 22:25:29 +1000 Subject: [PATCH 0244/1235] remove crccheck reference from doco --- doc/interfaces/seeedstudio.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst index b018a6643..17476a498 100644 --- a/doc/interfaces/seeedstudio.rst +++ b/doc/interfaces/seeedstudio.rst @@ -19,7 +19,6 @@ Installation ------------ This interface has additional dependencies which can be installed using pip and the optional extra [seeedstudio]. That will install two additional packages if not already available: - pyserial - - crccheck :: From ed54cc4d1b46e54826dd51b0023fdf74b29e607f Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Thu, 13 Jun 2019 20:54:21 +0200 Subject: [PATCH 0245/1235] Update docs --- doc/bit_timing.rst | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/doc/bit_timing.rst b/doc/bit_timing.rst index 1b25adf06..e1d2feeeb 100644 --- a/doc/bit_timing.rst +++ b/doc/bit_timing.rst @@ -1,4 +1,49 @@ Bit Timing Configuration ======================== +The CAN protocol allows the bitrate, sample point and number of samples to be +optimized for a given application. You can read more on Wikipedia_, Kvaser_ +and other sources. + +In most cases the recommended settings for a predefined set of common +bitrates will work just fine. In some cases it may however be necessary to specify +custom settings. The :class:`can.BitTiming` class can be used for this purpose to +specify them in a relatively interface agnostic manner. + +It is also possible to specify the same settings for a CAN 2.0 bus +using the config file: + + +.. code-block:: none + + [default] + bitrate=1000000 + f_clock=8000000 + tseg1=5 + tseg2=2 + sjw=1 + nof_samples=1 + + +.. code-block:: none + + [default] + brp=1 + tseg1=5 + tseg2=2 + sjw=1 + nof_samples=1 + + +.. code-block:: none + + [default] + btr0=0x00 + btr1=0x14 + + .. autoclass:: can.BitTiming + + +.. _Wikipedia: https://en.wikipedia.org/wiki/CAN_bus#Bit_timing +.. _Kvaser: https://www.kvaser.com/about-can/the-can-protocol/can-bit-timing/ From a26c209143b003641f4424c455466d1815857de1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 13 Jun 2019 10:54:43 +0200 Subject: [PATCH 0246/1235] Update Readme: 4.x is currently developed Make it clearer that 4.x is the version currently developed, maybe more people will stop using the develop branch on Python < 3.6. --- README.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index ee489d06f..ef92a6222 100644 --- a/README.rst +++ b/README.rst @@ -44,13 +44,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 3.6+ -================== =========== +============================= =========== +Library Version Python +----------------------------- ----------- + 2.x 2.6+, 3.4+ + 3.x 2.7+, 3.5+ + 4.x *(currently on devlop)* 3.6+ +============================= =========== Features From 24e4b3521b2713e5b3e9c79d1c206b8518bfa454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Wed, 19 Jun 2019 11:30:25 -0400 Subject: [PATCH 0247/1235] Simplifying the configuration context handling. Also allowing to use the context with environment variables. --- can/util.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/can/util.py b/can/util.py index c04bb70c9..b65959ee5 100644 --- a/can/util.py +++ b/can/util.py @@ -30,7 +30,7 @@ CONFIG_FILES.extend(["can.ini", os.path.join(os.getenv("APPDATA", ""), "can.ini")]) -def load_file_config(path=None, section=None): +def load_file_config(path=None, section="default"): """ Loads configuration from file with following content:: @@ -52,16 +52,13 @@ def load_file_config(path=None, section=None): _config = {} - section = section if section is not None else "default" if config.has_section(section): - if config.has_section("default"): - _config.update(dict((key, val) for key, val in config.items("default"))) _config.update(dict((key, val) for key, val in config.items(section))) return _config -def load_environment_config(): +def load_environment_config(context=None): """ Loads config dict from environmental variables (if set): @@ -69,15 +66,27 @@ def load_environment_config(): * CAN_CHANNEL * CAN_BITRATE + if context is supplied, "_{context}" is appended to the environment + variable name we will look at. For example if context="ABC": + + * CAN_INTERFACE_ABC + * CAN_CHANNEL_ABC + * CAN_BITRATE_ABC + """ mapper = { "interface": "CAN_INTERFACE", "channel": "CAN_CHANNEL", "bitrate": "CAN_BITRATE", } - return dict( - (key, os.environ.get(val)) for key, val in mapper.items() if val in os.environ - ) + config = dict() + for key, val in mapper.items(): + if context is not None: + val = val + "_{}".format(context) + if val in os.environ: + config[key] = os.environ.get(val) + + return config def load_config(path=None, config=None, context=None): @@ -135,8 +144,10 @@ def load_config(path=None, config=None, context=None): config_sources = [ given_config, can.rc, - lambda _context: load_environment_config(), # context is not supported + lambda _context: load_environment_config(_context), + lambda _context: load_environment_config(), lambda _context: load_file_config(path, _context), + lambda _context: load_file_config(path), ] # Slightly complex here to only search for the file config if required From 2e37856f468009f7c0852b4606c959ee43440566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Wed, 19 Jun 2019 11:57:34 -0400 Subject: [PATCH 0248/1235] Updating tests --- test/test_load_file_config.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py index d2f5fc3f2..79b2e6c4b 100644 --- a/test/test_load_file_config.py +++ b/test/test_load_file_config.py @@ -45,15 +45,16 @@ def test_config_file_with_default(self): def test_config_file_with_default_and_section(self): tmp_config = self._gen_configration_file(["default", "one"]) - default = can.util.load_file_config(path=tmp_config) - self.assertEqual(default, self.configuration["default"]) + config = can.util.load_file_config(path=tmp_config) + self.assertEqual(config, self.configuration["default"]) - one = can.util.load_file_config(path=tmp_config, section="one") - self.assertEqual(one, self.configuration["one"]) + config.update(can.util.load_file_config(path=tmp_config, section="one")) + self.assertEqual(config, self.configuration["one"]) def test_config_file_with_section_only(self): tmp_config = self._gen_configration_file(["one"]) - config = can.util.load_file_config(path=tmp_config, section="one") + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="one")) self.assertEqual(config, self.configuration["one"]) def test_config_file_with_section_and_key_in_default(self): @@ -61,13 +62,15 @@ def test_config_file_with_section_and_key_in_default(self): expected.update(self.configuration["two"]) tmp_config = self._gen_configration_file(["default", "two"]) - config = can.util.load_file_config(path=tmp_config, section="two") + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="two")) self.assertEqual(config, expected) def test_config_file_with_section_missing_interface(self): expected = self.configuration["two"].copy() tmp_config = self._gen_configration_file(["two"]) - config = can.util.load_file_config(path=tmp_config, section="two") + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="two")) self.assertEqual(config, expected) def test_config_file_extra(self): @@ -75,14 +78,16 @@ def test_config_file_extra(self): expected.update(self.configuration["three"]) tmp_config = self._gen_configration_file(["default", "three"]) - config = can.util.load_file_config(path=tmp_config, section="three") + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="three")) self.assertEqual(config, expected) def test_config_file_with_non_existing_section(self): - expected = {} + expected = self.configuration["default"].copy() tmp_config = self._gen_configration_file(["default", "one", "two", "three"]) - config = can.util.load_file_config(path=tmp_config, section="zero") + config = can.util.load_file_config(path=tmp_config) + config.update(can.util.load_file_config(path=tmp_config, section="zero")) self.assertEqual(config, expected) From 62ff42ee1613f4af87fada1776ff2d3abaa33a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Wed, 19 Jun 2019 12:06:55 -0400 Subject: [PATCH 0249/1235] Allow to set bus config argument via CAN_CONFIG environment variable --- can/util.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/can/util.py b/can/util.py index b65959ee5..e892fdcde 100644 --- a/can/util.py +++ b/can/util.py @@ -4,6 +4,7 @@ Utilities and configuration file parsing. """ +import json import os import os.path import platform @@ -65,6 +66,7 @@ def load_environment_config(context=None): * CAN_INTERFACE * CAN_CHANNEL * CAN_BITRATE + * CAN_CONFIG if context is supplied, "_{context}" is appended to the environment variable name we will look at. For example if context="ABC": @@ -72,6 +74,7 @@ def load_environment_config(context=None): * CAN_INTERFACE_ABC * CAN_CHANNEL_ABC * CAN_BITRATE_ABC + * CAN_CONFIG_ABC """ mapper = { @@ -79,12 +82,18 @@ def load_environment_config(context=None): "channel": "CAN_CHANNEL", "bitrate": "CAN_BITRATE", } - config = dict() + + context_suffix = "_{}".format(context) if context else "" + + config = {} + + can_config_key = "CAN_CONFIG" + context_suffix + if can_config_key in os.environ: + config = json.loads(os.environ.get(can_config_key)) + for key, val in mapper.items(): - if context is not None: - val = val + "_{}".format(context) if val in os.environ: - config[key] = os.environ.get(val) + config[key] = os.environ.get(val + context_suffix) return config From f7afe9930ea5689841d13e8a2fd18ca67defda97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Wed, 19 Jun 2019 13:20:50 -0400 Subject: [PATCH 0250/1235] Disable unnecessary lambda pylint warning. This line is more readable and consistent with the other lines with the use of lambda --- can/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/util.py b/can/util.py index e892fdcde..5bd1ce52c 100644 --- a/can/util.py +++ b/can/util.py @@ -153,7 +153,9 @@ def load_config(path=None, config=None, context=None): config_sources = [ given_config, can.rc, - lambda _context: load_environment_config(_context), + lambda _context: load_environment_config( # pylint: disable=unnecessary-lambda + _context + ), lambda _context: load_environment_config(), lambda _context: load_file_config(path, _context), lambda _context: load_file_config(path), From dce74315c3001684f4c24178ea98ba4293e11a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Sch=C3=A4rlund?= Date: Sat, 22 Jun 2019 10:14:05 +0200 Subject: [PATCH 0251/1235] Avoid padding CAN_FD_MESSAGE_64 objects to 4 bytes --- can/io/blf.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index 06afd6552..94ed2b51b 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -206,8 +206,13 @@ def __iter__(self): raise BLFParseError() obj_size = header[3] + obj_type = header[4] # Calculate position of next object - next_pos = pos + obj_size + (obj_size % 4) + if obj_size % 4 and obj_type != CAN_FD_MESSAGE_64: + next_pos = pos + obj_size + (obj_size % 4) + else: + # CAN_FD_MESSAGE_64 objects are not padded to 4 bytes. + next_pos = pos + obj_size if next_pos > len(data): # Object continues in next log container break @@ -239,7 +244,6 @@ def __iter__(self): factor = 1e-9 timestamp = timestamp * factor + self.start_timestamp - obj_type = header[4] # Both CAN message types have the same starting content if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): ( From fee245411c5e185ab9a4421f1bc9f3762dd87c59 Mon Sep 17 00:00:00 2001 From: shedfly Date: Sun, 23 Jun 2019 14:32:57 +1000 Subject: [PATCH 0252/1235] fixing pylint errors, Unused variable 'i', unused import sleep. --- can/interfaces/seeedstudio/seeedstudio.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 351ecb93f..f4f6867ba 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -10,7 +10,7 @@ import logging import struct -from time import sleep, time +from time import time from can import BusABC, Message logger = logging.getLogger('seeedbus') @@ -117,7 +117,7 @@ def init_frame(self, timeout=None): byte_msg.append(SeeedBus.OPERATIONMODE[self.op_mode]) byte_msg.append(0x01) # Follows 'Send once' in windows app. - for i in range(0, 4): # Manual bitrate config, details unknown. + for _ in range(0, 4): # Manual bitrate config, details unknown. byte_msg.append(0x00) crc = sum(byte_msg[2:]) & 0xFF @@ -145,7 +145,7 @@ def status_frame(self, timeout=None): byte_msg.append(0x00) # In response packet - Rx error count byte_msg.append(0x00) # In response packet - Tx error count - for i in range(0, 14): + for _ in range(0, 14): byte_msg.append(0x00) crc = sum(byte_msg[2:]) & 0xFF From cf1fcb4db6c62ce27ff4ef4f5611ce67adbcefe8 Mon Sep 17 00:00:00 2001 From: shedfly Date: Sun, 23 Jun 2019 15:54:02 +1000 Subject: [PATCH 0253/1235] fixing pylint errors, logging string arguments --- can/interfaces/seeedstudio/seeedstudio.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index f4f6867ba..328d67649 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -123,7 +123,7 @@ def init_frame(self, timeout=None): crc = sum(byte_msg[2:]) & 0xFF byte_msg.append(crc) - logger.debug("init_frm:\t" + byte_msg.hex()) + logger.debug("init_frm:\t%s", byte_msg.hex()) self.ser.write(byte_msg) def flush_buffer(self): @@ -151,7 +151,7 @@ def status_frame(self, timeout=None): crc = sum(byte_msg[2:]) & 0xFF byte_msg.append(crc) - logger.debug("status_frm:\t" + byte_msg.hex()) + logger.debug("status_frm:\t%s", byte_msg.hex()) self.ser.write(byte_msg) def send(self, msg, timeout=None): @@ -188,7 +188,7 @@ def send(self, msg, timeout=None): byte_msg.extend(msg.data) byte_msg.append(0x55) - logger.debug("sending:\t" + byte_msg.hex()) + logger.debug("sending:\t%s", byte_msg.hex()) self.ser.write(byte_msg) def _recv_internal(self, timeout): @@ -219,8 +219,9 @@ def _recv_internal(self, timeout): rx_byte_2 = ord(self.ser.read()) time_stamp = time() if rx_byte_2 == 0x55: - status = bytearray(self.ser.read(18)) - logger.debug("status resp:\t" + status.hex()) + status = bytearray([0xAA, 0x55]) + status += bytearray(self.ser.read(18)) + logger.debug("status resp:\t%s", status.hex()) else: length = int(rx_byte_2 & 0x0F) @@ -243,7 +244,7 @@ def _recv_internal(self, timeout): is_remote_frame=is_remote, dlc=length, data=data) - logger.debug("recv message: " + str(msg)) + logger.debug("recv message: %s", str(msg)) return msg, False else: From 876e12cb79b39dd6beec9d66cb9077ba75ecfec3 Mon Sep 17 00:00:00 2001 From: shedfly Date: Sun, 23 Jun 2019 16:08:40 +1000 Subject: [PATCH 0254/1235] fixing format errors, ran back format tool. --- can/interfaces/seeedstudio/seeedstudio.py | 119 ++++++++++++---------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 328d67649..7e93dfeb0 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -13,48 +13,58 @@ from time import time from can import BusABC, Message -logger = logging.getLogger('seeedbus') +logger = logging.getLogger("seeedbus") try: import serial except ImportError: - logger.warning("You won't be able to use the serial can backend without " - "the serial module installed!") + logger.warning( + "You won't be able to use the serial can backend without " + "the serial module installed!" + ) serial = None + class SeeedBus(BusABC): """ Enable basic can communication over a USB-CAN-Analyzer device. """ + BITRATE = { - 1000000: 0x01, - 800000: 0x02, - 500000: 0x03, - 400000: 0x04, - 250000: 0x05, - 200000: 0x06, - 125000: 0x07, - 100000: 0x08, - 50000: 0x09, - 20000: 0x0A, - 10000: 0x0B, - 5000: 0x0C - } - - FRAMETYPE = { - "STD":0x01, - "EXT":0x02 - } + 1000000: 0x01, + 800000: 0x02, + 500000: 0x03, + 400000: 0x04, + 250000: 0x05, + 200000: 0x06, + 125000: 0x07, + 100000: 0x08, + 50000: 0x09, + 20000: 0x0A, + 10000: 0x0B, + 5000: 0x0C, + } + + FRAMETYPE = {"STD": 0x01, "EXT": 0x02} OPERATIONMODE = { - "normal":0x00, - "loopback":0x01, - "silent":0x02, - "loopback_and_silent":0x03 - } - - def __init__(self, channel, baudrate=2000000, timeout=0.1, frame_type="STD", - operation_mode="normal", bitrate=500000, *args, **kwargs): + "normal": 0x00, + "loopback": 0x01, + "silent": 0x02, + "loopback_and_silent": 0x03, + } + + def __init__( + self, + channel, + baudrate=2000000, + timeout=0.1, + frame_type="STD", + operation_mode="normal", + bitrate=500000, + *args, + **kwargs + ): """ :param str channel: The serial device to open. For example "/dev/ttyS1" or @@ -86,7 +96,8 @@ def __init__(self, channel, baudrate=2000000, timeout=0.1, frame_type="STD", self.channel_info = "Serial interface: " + channel self.ser = serial.Serial( - channel, baudrate=baudrate, timeout=timeout, rtscts=False) + channel, baudrate=baudrate, timeout=timeout, rtscts=False + ) super(SeeedBus, self).__init__(channel=channel, *args, **kwargs) self.init_frame() @@ -107,17 +118,17 @@ def init_frame(self, timeout=None): used instead. """ byte_msg = bytearray() - byte_msg.append(0xAA) # Frame Start Byte 1 - byte_msg.append(0x55) # Frame Start Byte 2 - byte_msg.append(0x12) # Initialization Message ID + byte_msg.append(0xAA) # Frame Start Byte 1 + byte_msg.append(0x55) # Frame Start Byte 2 + byte_msg.append(0x12) # Initialization Message ID byte_msg.append(SeeedBus.BITRATE[self.bit_rate]) # CAN Baud Rate byte_msg.append(SeeedBus.FRAMETYPE[self.frame_type]) byte_msg.extend(self.filter_id) byte_msg.extend(self.mask_id) byte_msg.append(SeeedBus.OPERATIONMODE[self.op_mode]) - byte_msg.append(0x01) # Follows 'Send once' in windows app. + byte_msg.append(0x01) # Follows 'Send once' in windows app. - for _ in range(0, 4): # Manual bitrate config, details unknown. + for _ in range(0, 4): # Manual bitrate config, details unknown. byte_msg.append(0x00) crc = sum(byte_msg[2:]) & 0xFF @@ -139,11 +150,11 @@ def status_frame(self, timeout=None): used instead. """ byte_msg = bytearray() - byte_msg.append(0xAA) # Frame Start Byte 1 - byte_msg.append(0x55) # Frame Start Byte 2 - byte_msg.append(0x04) # Status Message ID - byte_msg.append(0x00) # In response packet - Rx error count - byte_msg.append(0x00) # In response packet - Tx error count + byte_msg.append(0xAA) # Frame Start Byte 1 + byte_msg.append(0x55) # Frame Start Byte 2 + byte_msg.append(0x04) # Status Message ID + byte_msg.append(0x00) # In response packet - Rx error count + byte_msg.append(0x00) # In response packet - Tx error count for _ in range(0, 14): byte_msg.append(0x00) @@ -169,7 +180,7 @@ def send(self, msg, timeout=None): byte_msg = bytearray() byte_msg.append(0xAA) - m_type = 0xc0 + m_type = 0xC0 if msg.is_extended_id: m_type += 1 << 5 @@ -180,9 +191,9 @@ def send(self, msg, timeout=None): byte_msg.append(m_type) if msg.is_extended_id: - a_id = struct.pack(' Date: Sun, 23 Jun 2019 16:22:28 +1000 Subject: [PATCH 0255/1235] minor tidy-up --- can/interfaces/seeedstudio/seeedstudio.py | 6 ++---- doc/interfaces/seeedstudio.rst | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 7e93dfeb0..eebd07753 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -128,8 +128,7 @@ def init_frame(self, timeout=None): byte_msg.append(SeeedBus.OPERATIONMODE[self.op_mode]) byte_msg.append(0x01) # Follows 'Send once' in windows app. - for _ in range(0, 4): # Manual bitrate config, details unknown. - byte_msg.append(0x00) + byte_msg.extend([0x00] * 4) # Manual bitrate config, details unknown. crc = sum(byte_msg[2:]) & 0xFF byte_msg.append(crc) @@ -156,8 +155,7 @@ def status_frame(self, timeout=None): byte_msg.append(0x00) # In response packet - Rx error count byte_msg.append(0x00) # In response packet - Tx error count - for _ in range(0, 14): - byte_msg.append(0x00) + byte_msg.extend([0x00] * 14) crc = sum(byte_msg[2:]) & 0xFF byte_msg.append(crc) diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst index 17476a498..5c86fa688 100644 --- a/doc/interfaces/seeedstudio.rst +++ b/doc/interfaces/seeedstudio.rst @@ -17,7 +17,7 @@ Links: Installation ------------ -This interface has additional dependencies which can be installed using pip and the optional extra [seeedstudio]. That will install two additional packages if not already available: +This interface has additional dependencies which can be installed using pip and the optional extra [seeedstudio]. That will install an additional packages if not already available: - pyserial From 37dc484bf425a268271276eca1bbb809412f1eae Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 23 Jun 2019 12:36:12 +0200 Subject: [PATCH 0256/1235] reformat asc.py --- can/io/asc.py | 124 ++++++++++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index a8a50ed38..1371d8049 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,27 +63,30 @@ 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) + msg = Message( + timestamp=timestamp, + is_error_frame=True, + channel=channel, + ) yield msg - elif ( not isinstance(channel, int) - or dummy.strip()[0:10].lower() == "statistic:" + 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) + can_id_num, is_extended_id = self._extract_can_id( + can_id_str + ) msg = Message( timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, @@ -93,24 +95,27 @@ def __iter__(self): channel=channel, ) yield msg - else: try: # this only works if dlc > 0 and thus data is availabe - can_id_str, _, _, dlc, data = dummy.split(None, 4) + can_id_str, _, _, dlc, data = dummy.split( + None, 4 + ) except ValueError: # but if not, we only want to get the stuff up to the dlc - can_id_str, _, _, dlc = dummy.split(None, 3) + 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) + can_id_num, is_extended_id = self._extract_can_id( + can_id_str + ) yield Message( timestamp=timestamp, @@ -121,7 +126,6 @@ def __iter__(self): data=frame, channel=channel, ) - self.stop() @@ -134,26 +138,30 @@ class ASCWriter(BaseIOHandler, Listener): It the first message does not have a timestamp, it is set to zero. """ - 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_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" @@ -169,7 +177,9 @@ def __init__(self, file, channel=1): self.channel = channel # write start of file header - now = datetime.now().strftime("%a %b %m %I:%M:%S.%f %p %Y") + now = datetime.now().strftime( + "%a %b %m %I:%M:%S.%f %p %Y" + ) self.file.write("date %s\n" % now) self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") @@ -192,56 +202,64 @@ def log_event(self, message, timestamp=None): """ if not message: # if empty or None - logger.debug("ASCWriter: ignoring empty message") + 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 self.started = self.last_timestamp - mlsec = repr(self.last_timestamp).split(".")[1][:3] + mlsec = repr(self.last_timestamp).split(".")[1][ + :3 + ] formatted_date = time.strftime( - self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp) + self.FORMAT_DATE.format(mlsec), + time.localtime(self.last_timestamp), + ) + self.file.write( + "Begin Triggerblock %s\n" % formatted_date ) - 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! - + 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) + line = self.FORMAT_EVENT.format( + timestamp=timestamp, message=message + ) self.file.write(line) def on_message_received(self, msg): if msg.is_error_frame: - self.log_event("{} ErrorFrame".format(self.channel), msg.timestamp) + 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] - + 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 - if msg.is_fd: flags = 0 flags |= 1 << 12 @@ -249,7 +267,6 @@ def on_message_received(self, msg): flags |= 1 << 13 if msg.error_state_indicator: flags |= 1 << 14 - serialized = self.FORMAT_MESSAGE_FD.format( channel=channel, id=arb_id, @@ -276,5 +293,4 @@ def on_message_received(self, msg): dtype=dtype, data=" ".join(data), ) - self.log_event(serialized, msg.timestamp) From ccab928c0d09fa7ef8241ae58f68f2f112b6b5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Sch=C3=A4rlund?= Date: Sun, 23 Jun 2019 13:55:21 +0200 Subject: [PATCH 0257/1235] Refactor to save calculations --- can/io/blf.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index 94ed2b51b..2ac553717 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -208,11 +208,9 @@ def __iter__(self): obj_size = header[3] obj_type = header[4] # Calculate position of next object - if obj_size % 4 and obj_type != CAN_FD_MESSAGE_64: - next_pos = pos + obj_size + (obj_size % 4) - else: - # CAN_FD_MESSAGE_64 objects are not padded to 4 bytes. - next_pos = pos + obj_size + next_pos = pos + obj_size + if obj_type != CAN_FD_MESSAGE_64: + next_pos += obj_size % 4 if next_pos > len(data): # Object continues in next log container break From 1753dc81f8344892d93ac28b9a5807f23b485093 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 24 Jun 2019 10:25:56 +0200 Subject: [PATCH 0258/1235] hope to make black happier --- can/io/asc.py | 70 +++++++++++++-------------------------------------- 1 file changed, 17 insertions(+), 53 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 1371d8049..656ac4fbe 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -70,23 +70,16 @@ def __iter__(self): except ValueError: pass if dummy.strip()[0:10].lower() == "errorframe": - msg = Message( - timestamp=timestamp, - is_error_frame=True, - channel=channel, - ) + msg = Message(timestamp=timestamp, is_error_frame=True, channel=channel) yield msg elif ( not isinstance(channel, int) - or dummy.strip()[0:10].lower() - == "statistic:" + 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 - ) + can_id_num, is_extended_id = self._extract_can_id(can_id_str) msg = Message( timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, @@ -98,14 +91,10 @@ def __iter__(self): else: try: # this only works if dlc > 0 and thus data is availabe - can_id_str, _, _, dlc, data = dummy.split( - None, 4 - ) + can_id_str, _, _, dlc, data = dummy.split(None, 4) except ValueError: # but if not, we only want to get the stuff up to the dlc - can_id_str, _, _, dlc = dummy.split( - None, 3 - ) + can_id_str, _, _, dlc = dummy.split(None, 3) # and we set data to an empty sequence manually data = "" dlc = int(dlc) @@ -113,9 +102,7 @@ def __iter__(self): 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 - ) + can_id_num, is_extended_id = self._extract_can_id(can_id_str) yield Message( timestamp=timestamp, @@ -138,9 +125,7 @@ class ASCWriter(BaseIOHandler, Listener): It the first message does not have a timestamp, it is set to zero. """ - FORMAT_MESSAGE = ( - "{channel} {id:<15} Rx {dtype} {data}" - ) + FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" FORMAT_MESSAGE_FD = " ".join( [ "CANFD", @@ -177,9 +162,7 @@ def __init__(self, file, channel=1): self.channel = channel # write start of file header - now = datetime.now().strftime( - "%a %b %m %I:%M:%S.%f %p %Y" - ) + now = datetime.now().strftime("%a %b %m %I:%M:%S.%f %p %Y") self.file.write("date %s\n" % now) self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") @@ -202,55 +185,39 @@ def log_event(self, message, timestamp=None): """ if not message: # if empty or None - logger.debug( - "ASCWriter: ignoring empty message" - ) + 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 self.started = self.last_timestamp - mlsec = repr(self.last_timestamp).split(".")[1][ - :3 - ] + mlsec = repr(self.last_timestamp).split(".")[1][:3] formatted_date = time.strftime( - self.FORMAT_DATE.format(mlsec), - time.localtime(self.last_timestamp), - ) - self.file.write( - "Begin Triggerblock %s\n" % formatted_date + self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp) ) + 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! + 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 - ) + line = self.FORMAT_EVENT.format(timestamp=timestamp, message=message) self.file.write(line) def on_message_received(self, msg): if msg.is_error_frame: - self.log_event( - "{} ErrorFrame".format(self.channel), - msg.timestamp, - ) + 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 - ] + data = ["{:02X}".format(byte) for byte in msg.data] arb_id = "{:X}".format(msg.arbitration_id) if msg.is_extended_id: arb_id += "x" @@ -288,9 +255,6 @@ def on_message_received(self, msg): ) else: serialized = self.FORMAT_MESSAGE.format( - channel=channel, - id=arb_id, - dtype=dtype, - data=" ".join(data), + channel=channel, id=arb_id, dtype=dtype, data=" ".join(data) ) self.log_event(serialized, msg.timestamp) From b1c7c131e63df182cf63c375c1cff713187cdd8a Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Wed, 26 Jun 2019 12:59:02 +0200 Subject: [PATCH 0259/1235] Add CAN-FD support to CLI's and config file --- can/logger.py | 12 ++++++++++++ can/player.py | 12 ++++++++++++ can/util.py | 5 +++++ can/viewer.py | 12 ++++++++++++ 4 files changed, 41 insertions(+) diff --git a/can/logger.py b/can/logger.py index 9b326946c..00f667979 100644 --- a/can/logger.py +++ b/can/logger.py @@ -79,6 +79,14 @@ def main(): "-b", "--bitrate", type=int, help="""Bitrate to use for the CAN bus.""" ) + parser.add_argument("--fd", help="Activate CAN-FD support", action="store_true") + + parser.add_argument( + "--data_bitrate", + type=int, + help="""Bitrate to use for the data phase in case of CAN-FD.""", + ) + state_group = parser.add_mutually_exclusive_group(required=False) state_group.add_argument( "--active", @@ -123,6 +131,10 @@ def main(): config["interface"] = results.interface if results.bitrate: config["bitrate"] = results.bitrate + if results.fd: + config["fd"] = True + if results.data_bitrate: + config["data_bitrate"] = results.data_bitrate bus = Bus(results.channel, **config) if results.active: diff --git a/can/player.py b/can/player.py index b279579f7..34be48670 100644 --- a/can/player.py +++ b/can/player.py @@ -58,6 +58,14 @@ def main(): "-b", "--bitrate", type=int, help="""Bitrate to use for the CAN bus.""" ) + parser.add_argument("--fd", help="Activate CAN-FD support", action="store_true") + + parser.add_argument( + "--data_bitrate", + type=int, + help="""Bitrate to use for the data phase in case of CAN-FD.""", + ) + parser.add_argument( "--ignore-timestamps", dest="timestamps", @@ -108,6 +116,10 @@ def main(): config["interface"] = results.interface if results.bitrate: config["bitrate"] = results.bitrate + if results.fd: + config["fd"] = True + if results.data_bitrate: + config["data_bitrate"] = results.data_bitrate bus = Bus(results.channel, **config) reader = LogReader(results.infile) diff --git a/can/util.py b/can/util.py index 803fa2388..5ecc028c1 100644 --- a/can/util.py +++ b/can/util.py @@ -165,6 +165,10 @@ def load_config(path=None, config=None, context=None): if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) + if "fd" in config: + config["fd"] = config["fd"] not in ("0", "False", "false") + if "data_bitrate" in config: + config["data_bitrate"] = int(config["data_bitrate"]) # Create bit timing configuration if given timing_conf = {} @@ -186,6 +190,7 @@ def load_config(path=None, config=None, context=None): config["timing"] = can.BitTiming(**timing_conf) can.log.debug("can config: {}".format(config)) + print(config) return config diff --git a/can/viewer.py b/can/viewer.py index b6ecbfe36..21980e361 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -377,6 +377,14 @@ def parse_args(args): help="""Bitrate to use for the given CAN interface""", ) + optional.add_argument("--fd", help="Activate CAN-FD support", action="store_true") + + optional.add_argument( + "--data_bitrate", + type=int, + help="""Bitrate to use for the data phase in case of CAN-FD.""", + ) + optional.add_argument( "-c", "--channel", @@ -537,6 +545,10 @@ def main(): # pragma: no cover config["interface"] = parsed_args.interface if parsed_args.bitrate: config["bitrate"] = parsed_args.bitrate + if parsed_args.fd: + config["fd"] = True + if parsed_args.data_bitrate: + config["data_bitrate"] = parsed_args.data_bitrate # Create a CAN-Bus interface bus = can.Bus(parsed_args.channel, **config) From 161eb0a463679aaf4a52a477ac793f7b954b1b48 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Wed, 26 Jun 2019 13:35:02 +0200 Subject: [PATCH 0260/1235] Fix viewer when data does not fit --- can/viewer.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/can/viewer.py b/can/viewer.py index 21980e361..707192526 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -212,11 +212,6 @@ def draw_can_bus_message(self, msg, sorting=False): msg.arbitration_id, 8 if msg.is_extended_id else 3 ) - # Generate data string - data_string = "" - if msg.dlc > 0: - data_string = " ".join("{:02X}".format(x) for x in msg.data) - # Use red for error frames if msg.is_error_frame: color = curses.color_pair(1) @@ -236,7 +231,14 @@ def draw_can_bus_message(self, msg, sorting=False): ) self.draw_line(self.ids[key]["row"], 35, arbitration_id_string, color) self.draw_line(self.ids[key]["row"], 47, str(msg.dlc), color) - self.draw_line(self.ids[key]["row"], 52, data_string, color) + for i, b in enumerate(msg.data): + col = 52 + i * 3 + if col > self.x - 2: + # Data does not fit + self.draw_line(self.ids[key]["row"], col - 4, "...", color) + break + text = "{:02X}".format(b) + self.draw_line(self.ids[key]["row"], col, text, color) if self.data_structs: try: From c3f4c82a3535636ef172d37aa267342e72ca0e8c Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Wed, 26 Jun 2019 13:37:31 +0200 Subject: [PATCH 0261/1235] Remove debug print --- can/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/can/util.py b/can/util.py index 5ecc028c1..9d0145dd7 100644 --- a/can/util.py +++ b/can/util.py @@ -190,7 +190,6 @@ def load_config(path=None, config=None, context=None): config["timing"] = can.BitTiming(**timing_conf) can.log.debug("can config: {}".format(config)) - print(config) return config From b59b684c7dfb096e1f63bf35564b190c493191ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Thu, 27 Jun 2019 13:27:44 -0400 Subject: [PATCH 0262/1235] Adding doc --- doc/configuration.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/configuration.rst b/doc/configuration.rst index 142e816da..0ce8f85a7 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -92,6 +92,13 @@ Configuration can be pulled from these environmental variables: * CAN_INTERFACE * CAN_CHANNEL * CAN_BITRATE + * CAN_CONFIG + +The ``CAN_CONFIG`` environment variable allows to set any bus configuration using JSON. + +For example: + +``CAN_INTERFACE=socketcan CAN_CONFIG={"receive_own_messages": true, "fd": true}`` Interface Names From 1892dc5bf19be2fd873cd809aa22e2810ca5a7c3 Mon Sep 17 00:00:00 2001 From: Pierre-jean Texier Date: Thu, 4 Jul 2019 13:31:33 +0200 Subject: [PATCH 0263/1235] setup.py: require pytest-runner only when necessary (#633) This optimizes setup.py for cases when pytest-runner is not needed, using the approach that is suggested upstream: https://pypi.python.org/pypi/pytest-runner#conditional-requirement --- setup.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 221c0301d..ded73f458 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ from os.path import isfile, join import re import logging +import sys from setuptools import setup, find_packages logging.basicConfig(level=logging.WARNING) @@ -40,6 +41,13 @@ extras_require["test"] = tests_require +# Check for 'pytest-runner' only if setup.py was invoked with 'test'. +# This optimizes setup.py for cases when pytest-runner is not needed, +# using the approach that is suggested upstream. +# +# See https://pypi.org/project/pytest-runner/#conditional-requirement +needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) +pytest_runner = ["pytest-runner"] if needs_pytest else [] setup( # Description @@ -96,7 +104,7 @@ 'windows-curses;platform_system=="Windows"', "filelock", ], - setup_requires=["pytest-runner"], + setup_requires=pytest_runner, extras_require=extras_require, tests_require=tests_require, ) From b2e1d758c86873d8591c9b901cd8dfa374733195 Mon Sep 17 00:00:00 2001 From: zariiii9003 Date: Sat, 6 Jul 2019 15:33:42 +0200 Subject: [PATCH 0264/1235] added xlCanRequestChipState and some constants --- can/interfaces/vector/vxlapi.py | 67 +++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index 5669490ac..d1af9693b 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -28,36 +28,90 @@ 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_CAN_EV_TAG_RX_OK = 1024 -XL_CAN_EV_TAG_TX_OK = 1028 +XL_CHIP_STATE = 4 +XL_TRANSCEIVER = 6 +XL_TIMER = 8 XL_TRANSMIT_MSG = 10 -XL_CAN_EV_TAG_TX_MSG = 1088 - +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 @@ -424,3 +478,8 @@ def check_status(result, function, arguments): 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 From 56f153f9ccd8c79388f8642640b4c3d4101a9ac7 Mon Sep 17 00:00:00 2001 From: karl ding Date: Wed, 10 Jul 2019 14:27:45 -0700 Subject: [PATCH 0265/1235] Add support for multiple Cyclic Messages in Task (#610) This adds support for multiple Cyclic Messages in a Cyclic Task. The default implementation is also changed to provide support for this, by iterating over the list of Cyclic Messages. In order to maintain backwards compatibility, the periodic APIs now take a CAN Message as before, in addition to a Sequence of CAN Messages. The SocketCAN interface takes advantage of the Linux BCM APIs to do so, while the IXXAT interface maintains its original behaviour. This also introduces a new example that illustrates how to use Cyclic Messages, backed by the SocketCAN interface. We previously tracked the can_id and arbitration_id class members due to the ongoing deprecation of the can_id Message attribute. Now that can_id is replaced by arbitration_id, we no longer need this in CyclicSendTaskABC either. As such, this removes the deprecated can_id member from the Cyclic Task. Fixes #606 --- can/broadcastmanager.py | 127 +++++-- can/bus.py | 44 ++- can/interfaces/ixxat/canlib.py | 19 +- can/interfaces/socketcan/socketcan.py | 136 ++++--- examples/cyclic_multiple.py | 147 +++++++ test/test_socketcan_cyclic_multiple.py | 507 +++++++++++++++++++++++++ 6 files changed, 879 insertions(+), 101 deletions(-) create mode 100644 examples/cyclic_multiple.py create mode 100644 test/test_socketcan_cyclic_multiple.py diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index ae5d126fd..edb5da2a3 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -12,6 +12,8 @@ import threading import time +import can + log = logging.getLogger("can.bcm") @@ -34,28 +36,65 @@ class CyclicSendTaskABC(CyclicTask): Message send task with defined period """ - def __init__(self, message, period): + def __init__(self, messages, period): """ - :param can.Message message: The message to be sent periodically. - :param float period: The rate in seconds at which to send the message. + :param Union[Sequence[can.Message], can.Message] messages: + The messages to be sent periodically. + :param float period: The rate in seconds at which to send the messages. """ - self.message = message - self.can_id = message.arbitration_id - self.arbitration_id = message.arbitration_id + messages = self._check_and_convert_messages(messages) + + # Take the Arbitration ID of the first element + self.arbitration_id = messages[0].arbitration_id self.period = period - super().__init__() + self.messages = messages + + @staticmethod + def _check_and_convert_messages(messages): + """Helper function to convert a Message or Sequence of messages into a + tuple, and raises an error when the given value is invalid. + + Performs error checking to ensure that all Messages have the same + arbitration ID and channel. + + Should be called when the cyclic task is initialized + """ + if not isinstance(messages, (list, tuple)): + if isinstance(messages, can.Message): + messages = [messages] + else: + raise ValueError("Must be either a list, tuple, or a Message") + if not messages: + raise ValueError("Must be at least a list or tuple of length 1") + messages = tuple(messages) + + all_same_id = all( + message.arbitration_id == messages[0].arbitration_id for message in messages + ) + if not all_same_id: + raise ValueError("All Arbitration IDs should be the same") + + all_same_channel = all( + message.channel == messages[0].channel for message in messages + ) + if not all_same_channel: + raise ValueError("All Channel IDs should be the same") + + return messages class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): - def __init__(self, message, period, duration): + def __init__(self, messages, period, duration): """Message send task with a defined duration and period. - :param can.Message message: The message to be sent periodically. - :param float period: The rate in seconds at which to send the message. + :param Union[Sequence[can.Message], can.Message] messages: + The messages to be sent periodically. + :param float period: The rate in seconds at which to send the messages. :param float duration: - The duration to keep sending this message at given rate. + Approximate duration in seconds to continue sending messages. If + no duration is provided, the task will continue indefinitely. """ - super().__init__(message, period) + super().__init__(messages, period) self.duration = duration @@ -71,33 +110,61 @@ def start(self): class ModifiableCyclicTaskABC(CyclicSendTaskABC): """Adds support for modifying a periodic message""" - def modify_data(self, message): - """Update the contents of this periodically sent message without altering - the timing. + def _check_modified_messages(self, messages): + """Helper function to perform error checking when modifying the data in + the cyclic task. + + Performs error checking to ensure the arbitration ID and the number of + cyclic messages hasn't changed. - :param can.Message message: - The message with the new :attr:`can.Message.data`. - Note: The arbitration ID cannot be changed. + Should be called when modify_data is called in the cyclic task. """ - self.message = message + if len(self.messages) != len(messages): + raise ValueError( + "The number of new cyclic messages to be sent must be equal to " + "the number of messages originally specified for this task" + ) + if self.arbitration_id != messages[0].arbitration_id: + raise ValueError( + "The arbitration ID of new cyclic messages cannot be changed " + "from when the task was created" + ) + + def modify_data(self, messages): + """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`. + + Note: The arbitration ID cannot be changed. + + Note: The number of new cyclic messages to be sent must be equal + to the original number of messages originally specified for this + task. + """ + messages = self._check_and_convert_messages(messages) + self._check_modified_messages(messages) + + self.messages = messages class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): """A Cyclic send task that supports switches send frequency after a set time. """ - def __init__(self, channel, message, count, initial_period, subsequent_period): + def __init__(self, channel, messages, count, initial_period, subsequent_period): """ Transmits a message `count` times at `initial_period` then continues to - transmit message at `subsequent_period`. + transmit messages at `subsequent_period`. :param channel: See interface specific documentation. - :param can.Message message: + :param Union[Sequence[can.Message], can.Message] messages: :param int count: :param float initial_period: :param float subsequent_period: """ - super().__init__(channel, message, subsequent_period) + super().__init__(channel, messages, subsequent_period) class ThreadBasedCyclicSendTask( @@ -105,10 +172,10 @@ class ThreadBasedCyclicSendTask( ): """Fallback cyclic send task using thread.""" - def __init__(self, bus, lock, message, period, duration=None): - super().__init__(message, period, duration) + def __init__(self, bus, lock, messages, period, duration=None): + super().__init__(messages, period, duration) self.bus = bus - self.lock = lock + self.send_lock = lock self.stopped = True self.thread = None self.end_time = time.time() + duration if duration else None @@ -120,23 +187,25 @@ def stop(self): def start(self): self.stopped = False if self.thread is None or not self.thread.is_alive(): - name = "Cyclic send task for 0x%X" % (self.message.arbitration_id) + 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 self.thread.start() def _run(self): + msg_index = 0 while not self.stopped: # Prevent calling bus.send from multiple threads - with self.lock: + with self.send_lock: started = time.time() try: - self.bus.send(self.message) + 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: 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)) diff --git a/can/bus.py b/can/bus.py index 7fcf910b0..9682b31af 100644 --- a/can/bus.py +++ b/can/bus.py @@ -5,6 +5,7 @@ """ from abc import ABCMeta, abstractmethod +import can import logging import threading from time import time @@ -163,8 +164,8 @@ def send(self, msg, timeout=None): """ raise NotImplementedError("Trying to write to a readonly bus?") - def send_periodic(self, msg, period, duration=None, store_task=True): - """Start sending a message at a given period on this bus. + def send_periodic(self, msgs, period, duration=None, store_task=True): + """Start sending messages at a given period on this bus. The task will be active until one of the following conditions are met: @@ -174,12 +175,12 @@ def send_periodic(self, msg, period, duration=None, store_task=True): - :meth:`BusABC.stop_all_periodic_tasks()` is called - the task's :meth:`CyclicTask.stop()` method is called. - :param can.Message msg: - Message to transmit + :param Union[Sequence[can.Message], can.Message] msgs: + Messages to transmit :param float period: Period in seconds between each message :param float duration: - The duration to keep sending this message at given rate. If + Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. :param bool store_task: If True (the default) the task will be attached to this Bus instance. @@ -191,18 +192,26 @@ def send_periodic(self, msg, period, duration=None, store_task=True): .. note:: - Note the duration before the message stops being sent may not + Note the duration before the messages stop being sent may not be exactly the same as the duration specified by the user. In general the message will be sent at the given rate until at least **duration** seconds. .. note:: - For extremely long running Bus instances with many short lived tasks the default - api with ``store_task==True`` may not be appropriate as the stopped tasks are - still taking up memory as they are associated with the Bus instance. + For extremely long running Bus instances with many short lived + tasks the default api with ``store_task==True`` may not be + appropriate as the stopped tasks are still taking up memory as they + are associated with the Bus instance. """ - task = self._send_periodic_internal(msg, period, duration) + if not isinstance(msgs, (list, tuple)): + if isinstance(msgs, can.Message): + msgs = [msgs] + else: + raise ValueError("Must be either a list, tuple, or a Message") + if not msgs: + raise ValueError("Must be at least a list or tuple of length 1") + task = self._send_periodic_internal(msgs, period, duration) # we wrap the task's stop method to also remove it from the Bus's list of tasks original_stop_method = task.stop @@ -221,21 +230,22 @@ def wrapped_stop_method(remove_task=True): return task - def _send_periodic_internal(self, msg, period, duration=None): + def _send_periodic_internal(self, msgs, period, duration=None): """Default implementation of periodic message sending using threading. Override this method to enable a more efficient backend specific approach. - :param can.Message msg: - Message to transmit + :param Union[Sequence[can.Message], can.Message] msgs: + Messages to transmit :param float period: Period in seconds between each message :param float duration: - The duration to keep sending this message at given rate. If + 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. + 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"): @@ -244,7 +254,7 @@ def _send_periodic_internal(self, msg, period, duration=None): threading.Lock() ) # pylint: disable=attribute-defined-outside-init task = ThreadBasedCyclicSendTask( - self, self._lock_send_periodic, msg, period, duration + self, self._lock_send_periodic, msgs, period, duration ) return task diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 0dae6a513..13c8ef779 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -716,20 +716,25 @@ def shutdown(self): class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" - def __init__(self, scheduler, msg, period, duration, resolution): - super().__init__(msg, period, duration) + def __init__(self, scheduler, msgs, period, duration, resolution): + super().__init__(msgs, period, duration) + if len(self.messages) != 1: + raise ValueError( + "IXXAT Interface only supports periodic transmission of 1 element" + ) + self._scheduler = scheduler self._index = None self._count = int(duration / period) if duration else 0 self._msg = structures.CANCYCLICTXMSG() self._msg.wCycleTime = int(round(period * resolution)) - self._msg.dwMsgId = msg.arbitration_id + self._msg.dwMsgId = self.messages[0].arbitration_id self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - self._msg.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - self._msg.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - self._msg.uMsgInfo.Bits.dlc = msg.dlc - for i, b in enumerate(msg.data): + self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0 + self._msg.uMsgInfo.Bits.rtr = 1 if self.messages[0].is_remote_frame else 0 + self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc + for i, b in enumerate(self.messages[0].data): self._msg.abData[i] = b self.start() diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 81315c8dc..f504d1cfc 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -191,7 +191,7 @@ def build_bcm_tx_delete_header(can_id, flags): def build_bcm_transmit_header( - can_id, count, initial_period, subsequent_period, msg_flags + can_id, count, initial_period, subsequent_period, msg_flags, nframes=1 ): opcode = CAN_BCM_TX_SETUP @@ -209,7 +209,6 @@ def split_time(value): ival1_seconds, ival1_usec = split_time(initial_period) ival2_seconds, ival2_usec = split_time(subsequent_period) - nframes = 1 return build_bcm_header( opcode, @@ -224,8 +223,8 @@ def split_time(value): ) -def build_bcm_update_header(can_id, msg_flags): - return build_bcm_header(CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, 1) +def build_bcm_update_header(can_id, msg_flags, nframes=1): + return build_bcm_header(CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, nframes) def dissect_can_frame(frame): @@ -288,7 +287,7 @@ class CyclicSendTask( LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC ): """ - A socketcan cyclic send task supports: + A SocketCAN cyclic send task supports: - setting of a task duration - modifying the data @@ -296,24 +295,32 @@ class CyclicSendTask( """ - def __init__(self, bcm_socket, message, period, duration=None): + def __init__(self, bcm_socket, messages, period, duration=None): """ - :param bcm_socket: An open bcm socket on the desired CAN channel. - :param can.Message message: The message to be sent periodically. - :param float period: The rate in seconds at which to send the message. - :param float duration: Approximate duration in seconds to send the message. + :param bcm_socket: An open BCM socket on the desired CAN channel. + :param Union[Sequence[can.Message], can.Message] messages: + The messages to be sent periodically. + :param float period: + The rate in seconds at which to send the messages. + :param float duration: + Approximate duration in seconds to send the messages for. """ - super().__init__(message, period, duration) - self.bcm_socket = bcm_socket - self.duration = duration - self._tx_setup(message) - self.message = message + # The following are assigned by LimitedDurationCyclicSendTaskABC: + # - self.messages + # - self.period + # - self.duration + super().__init__(messages, period, duration) - def _tx_setup(self, message): + self.bcm_socket = bcm_socket + self._tx_setup(self.messages) + def _tx_setup(self, messages): # Create a low level packed frame to pass to the kernel - self.can_id_with_flags = _add_flags_to_can_id(message) - self.flags = CAN_FD_FRAME if message.is_fd else 0 + header = bytearray() + body = bytearray() + self.can_id_with_flags = _add_flags_to_can_id(messages[0]) + self.flags = CAN_FD_FRAME if messages[0].is_fd else 0 + if self.duration: count = int(self.duration / self.period) ival1 = self.period @@ -322,12 +329,19 @@ def _tx_setup(self, message): count = 0 ival1 = 0 ival2 = self.period + header = build_bcm_transmit_header( - self.can_id_with_flags, count, ival1, ival2, self.flags + self.can_id_with_flags, + count, + ival1, + ival2, + self.flags, + nframes=len(messages), ) - frame = build_can_frame(message) + for message in messages: + body += build_can_frame(message) log.debug("Sending BCM command") - send_bcm(self.bcm_socket, header + frame) + send_bcm(self.bcm_socket, header + body) def stop(self): """Send a TX_DELETE message to cancel this task. @@ -341,22 +355,35 @@ def stop(self): stopframe = build_bcm_tx_delete_header(self.can_id_with_flags, self.flags) send_bcm(self.bcm_socket, stopframe) - def modify_data(self, message): - """Update the contents of this periodically sent message. + def modify_data(self, messages): + """Update the contents of the periodically sent messages. + + Note: The messages must all have the same + :attr:`~can.Message.arbitration_id` like the first message. - Note the Message must have the same :attr:`~can.Message.arbitration_id` - like the first message. + Note: The number of new cyclic messages to be sent must be equal to the + original number of messages originally specified for this task. + + :param Union[Sequence[can.Message], can.Message] messages: + The messages with the new :attr:`can.Message.data`. """ - assert ( - message.arbitration_id == self.can_id - ), "You cannot modify the can identifier" - self.message = message - header = build_bcm_update_header(self.can_id_with_flags, self.flags) - frame = build_can_frame(message) - send_bcm(self.bcm_socket, header + frame) + messages = self._check_and_convert_messages(messages) + self._check_modified_messages(messages) + + self.messages = messages + + header = bytearray() + body = bytearray() + header = build_bcm_update_header( + can_id=self.can_id_with_flags, msg_flags=self.flags, nframes=len(messages) + ) + for message in messages: + body += build_can_frame(message) + log.debug("Sending BCM command") + send_bcm(self.bcm_socket, header + body) def start(self): - self._tx_setup(self.message) + self._tx_setup(self.messages) class MultiRateCyclicSendTask(CyclicSendTask): @@ -365,17 +392,25 @@ class MultiRateCyclicSendTask(CyclicSendTask): """ - def __init__(self, channel, message, count, initial_period, subsequent_period): - super().__init__(channel, message, subsequent_period) + def __init__(self, channel, messages, count, initial_period, subsequent_period): + super().__init__(channel, messages, subsequent_period) # Create a low level packed frame to pass to the kernel - frame = build_can_frame(message) header = build_bcm_transmit_header( - self.can_id_with_flags, count, initial_period, subsequent_period, self.flags + self.can_id_with_flags, + count, + initial_period, + subsequent_period, + self.flags, + nframes=len(messages), ) + body = bytearray() + for message in messages: + body += build_can_frame(message) + log.info("Sending BCM TX_SETUP command") - send_bcm(self.bcm_socket, header + frame) + send_bcm(self.bcm_socket, header + body) def create_socket(): @@ -599,17 +634,17 @@ def _send_once(self, data, channel=None): raise can.CanError("Failed to transmit: %s" % exc) return sent - def _send_periodic_internal(self, msg, period, duration=None): - """Start sending a message at a given period on this bus. + def _send_periodic_internal(self, msgs, period, duration=None): + """Start sending messages at a given period on this bus. - The kernel's broadcast manager will be used. + The kernel's Broadcast Manager SocketCAN API will be used. - :param can.Message msg: - Message to transmit + :param Union[Sequence[can.Message], can.Message] messages: + The messages to be sent periodically :param float period: - Period in seconds between each message + The rate in seconds at which to send the messages. :param float duration: - The duration to keep sending this message at given rate. If + Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. :return: @@ -619,14 +654,19 @@ def _send_periodic_internal(self, msg, period, duration=None): .. note:: - Note the duration before the message stops being sent may not + Note the duration before the messages stop being sent may not be exactly the same as the duration specified by the user. In general the message will be sent at the given rate until at least *duration* seconds. """ - bcm_socket = self._get_bcm_socket(msg.channel or self.channel) - task = CyclicSendTask(bcm_socket, msg, period, duration) + msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages(msgs) + + bcm_socket = self._get_bcm_socket(msgs[0].channel or self.channel) + # TODO: The SocketCAN BCM interface treats all cyclic tasks sharing an + # Arbitration ID as the same Cyclic group. We should probably warn the + # user instead of overwriting the old group? + task = CyclicSendTask(bcm_socket, msgs, period, duration) return task def _get_bcm_socket(self, channel): diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py new file mode 100644 index 000000000..ae32e7416 --- /dev/null +++ b/examples/cyclic_multiple.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This example exercises the periodic task's multiple message sending capabilities + +Expects a vcan0 interface: + + python3 -m examples.cyclic_multiple + +""" + +from __future__ import print_function + +import logging +import time + +import can + +logging.basicConfig(level=logging.INFO) + + +def cyclic_multiple_send(bus): + """ + Sends periodic messages every 1 s with no explicit timeout + Sleeps for 10 seconds then stops the task. + """ + print("Starting to send a message every 1 s for 10 s") + messages = [] + + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + task = bus.send_periodic(messages, 1) + assert isinstance(task, can.CyclicSendTaskABC) + time.sleep(10) + task.stop() + print("stopped cyclic send") + + +def cyclic_multiple_send_modify(bus): + """ + Sends initial set of 3 Messages containing Odd data sent every 1 s with + no explicit timeout. Sleeps for 8 s. + + Then the set is updated to 3 Messages containing Even data. + Sleeps for 10 s. + """ + messages_odd = [] + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + messages_even = [] + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x66, 0x66, 0x66, 0x66, 0x66, 0x66], + is_extended_id=False, + ) + ) + print("Starting to send a message with odd every 1 s for 8 s with odd data") + task = bus.send_periodic(messages_odd, 1) + assert isinstance(task, can.CyclicSendTaskABC) + time.sleep(8) + print("Starting to send a message with even data every 1 s for 10 s with even data") + task.modify_data(messages_even) + time.sleep(10) + print("stopped cyclic modify send") + + +if __name__ == "__main__": + for interface, channel in [("socketcan", "vcan0")]: + print("Carrying out cyclic multiple tests with {} interface".format(interface)) + + bus = can.Bus(interface=interface, channel=channel, bitrate=500000) + + cyclic_multiple_send(bus) + + cyclic_multiple_send_modify(bus) + + bus.shutdown() + + time.sleep(2) diff --git a/test/test_socketcan_cyclic_multiple.py b/test/test_socketcan_cyclic_multiple.py new file mode 100644 index 000000000..ee2f0adaf --- /dev/null +++ b/test/test_socketcan_cyclic_multiple.py @@ -0,0 +1,507 @@ +""" +This module tests multiple message cyclic send tasks. +""" +import unittest + +import time +import can + +from .config import TEST_INTERFACE_SOCKETCAN + + +@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +class SocketCanCyclicMultiple(unittest.TestCase): + BITRATE = 500000 + TIMEOUT = 0.1 + + INTERFACE_1 = "socketcan" + CHANNEL_1 = "vcan0" + INTERFACE_2 = "socketcan" + CHANNEL_2 = "vcan0" + + PERIOD = 1.0 + + DELTA = 0.01 + + def _find_start_index(self, tx_messages, message): + """ + :param tx_messages: + The list of messages that were passed to the periodic backend + :param message: + The message whose data we wish to match and align to + + :returns: start index in the tx_messages + """ + start_index = -1 + for index, tx_message in enumerate(tx_messages): + if tx_message.data == message.data: + start_index = index + break + return start_index + + def setUp(self): + self._send_bus = can.Bus( + interface=self.INTERFACE_1, channel=self.CHANNEL_1, bitrate=self.BITRATE + ) + self._recv_bus = can.Bus( + interface=self.INTERFACE_2, channel=self.CHANNEL_2, bitrate=self.BITRATE + ) + + def tearDown(self): + self._send_bus.shutdown() + self._recv_bus.shutdown() + + def test_cyclic_initializer_list(self): + messages = [] + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + + task = self._send_bus.send_periodic(messages, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + results = [] + for _ in range(len(messages) * 2): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results.append(result) + + task.stop() + + # Find starting index for each + start_index = self._find_start_index(messages, results[0]) + self.assertTrue(start_index != -1) + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results): + tx_message = messages[start_index] + + self.assertIsNotNone(rx_message) + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + start_index = (start_index + 1) % len(messages) + + def test_cyclic_initializer_tuple(self): + messages = [] + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + messages = tuple(messages) + + self.assertIsInstance(messages, tuple) + + task = self._send_bus.send_periodic(messages, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + results = [] + for _ in range(len(messages) * 2): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results.append(result) + + task.stop() + + # Find starting index for each + start_index = self._find_start_index(messages, results[0]) + self.assertTrue(start_index != -1) + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results): + tx_message = messages[start_index] + + self.assertIsNotNone(rx_message) + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + start_index = (start_index + 1) % len(messages) + + def test_cyclic_initializer_message(self): + message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + task = self._send_bus.send_periodic(message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + # Take advantage of kernel's queueing mechanisms + time.sleep(4 * self.PERIOD) + task.stop() + + for _ in range(4): + tx_message = message + rx_message = self._recv_bus.recv(self.TIMEOUT) + + self.assertIsNotNone(rx_message) + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + def test_cyclic_initializer_invalid_none(self): + with self.assertRaises(ValueError): + task = self._send_bus.send_periodic(None, self.PERIOD) + + def test_cyclic_initializer_invalid_empty_list(self): + with self.assertRaises(ValueError): + task = self._send_bus.send_periodic([], self.PERIOD) + + def test_cyclic_initializer_different_arbitration_ids(self): + messages = [] + messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages.append( + can.Message( + arbitration_id=0x3E1, + data=[0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE], + is_extended_id=False, + ) + ) + with self.assertRaises(ValueError): + task = self._send_bus.send_periodic(messages, self.PERIOD) + + def test_modify_data_list(self): + messages_odd = [] + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + ) + messages_odd.append( + can.Message( + arbitration_id=0x401, + data=[0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + is_extended_id=False, + ) + ) + messages_even = [] + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], + is_extended_id=False, + ) + ) + messages_even.append( + can.Message( + arbitration_id=0x401, + data=[0x66, 0x66, 0x66, 0x66, 0x66, 0x66], + is_extended_id=False, + ) + ) + + task = self._send_bus.send_periodic(messages_odd, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + results_odd = [] + results_even = [] + for _ in range(len(messages_odd) * 2): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_odd.append(result) + + task.modify_data(messages_even) + for _ in range(len(messages_even) * 2): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_even.append(result) + + task.stop() + + # Make sure we received some messages + self.assertTrue(len(results_even) != 0) + self.assertTrue(len(results_odd) != 0) + + # Find starting index for each + start_index_even = self._find_start_index(messages_even, results_even[0]) + self.assertTrue(start_index_even != -1) + + start_index_odd = self._find_start_index(messages_odd, results_odd[0]) + self.assertTrue(start_index_odd != -1) + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results_even): + tx_message = messages_even[start_index_even] + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + start_index_even = (start_index_even + 1) % len(messages_even) + + if rx_index != 0: + prev_rx_message = results_even[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + for rx_index, rx_message in enumerate(results_odd): + tx_message = messages_odd[start_index_odd] + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + start_index_odd = (start_index_odd + 1) % len(messages_odd) + + if rx_index != 0: + prev_rx_message = results_odd[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + def test_modify_data_message(self): + message_odd = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + message_even = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + task = self._send_bus.send_periodic(message_odd, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + results_odd = [] + results_even = [] + for _ in range(1 * 4): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_odd.append(result) + + task.modify_data(message_even) + for _ in range(1 * 4): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_even.append(result) + + task.stop() + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results_even): + tx_message = message_even + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + if rx_index != 0: + prev_rx_message = results_even[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + for rx_index, rx_message in enumerate(results_odd): + tx_message = message_odd + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + if rx_index != 0: + prev_rx_message = results_odd[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + def test_modify_data_invalid(self): + message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + task = self._send_bus.send_periodic(message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + time.sleep(2 * self.PERIOD) + + with self.assertRaises(ValueError): + task.modify_data(None) + + def test_modify_data_unequal_lengths(self): + message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + new_messages = [] + new_messages.append( + can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + ) + new_messages.append( + can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + ) + + task = self._send_bus.send_periodic(message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + time.sleep(2 * self.PERIOD) + + with self.assertRaises(ValueError): + task.modify_data(new_messages) + + def test_modify_data_different_arbitration_id_than_original(self): + old_message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + new_message = can.Message( + arbitration_id=0x3E1, + data=[0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE], + is_extended_id=False, + ) + + task = self._send_bus.send_periodic(old_message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + time.sleep(2 * self.PERIOD) + + with self.assertRaises(ValueError): + task.modify_data(new_message) + + +if __name__ == "__main__": + unittest.main() From 36a8d63cbad2fc590ea66954af5c22c50350aa91 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 11 Jul 2019 07:25:49 +0200 Subject: [PATCH 0266/1235] Fix iteration in Bus.stop_all_periodic_tasks Modifying a list while iterating over it results in unspecified order. This is now fixed in `Bus.stop_all_periodic_tasks`. What happens when an exception is thrown while stopping a task? Do we want to catch it, log it and continue? Or do we want to leave the note on unspecified behaviour in there? Fixes #634 --- can/bus.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/can/bus.py b/can/bus.py index 9682b31af..59ddbd1d2 100644 --- a/can/bus.py +++ b/can/bus.py @@ -259,13 +259,21 @@ def _send_periodic_internal(self, msgs, period, duration=None): return task def stop_all_periodic_tasks(self, remove_tasks=True): - """Stop sending any messages that were started using bus.send_periodic + """Stop sending any messages that were started using **bus.send_periodic**. + + .. note:: + The result is undefined if a single task throws an exception while being stopped. :param bool remove_tasks: Stop tracking the stopped tasks. """ for task in self._periodic_tasks: - task.stop(remove_task=remove_tasks) + # we cannot let `task.stop()` modify `self._periodic_tasks` while we are + # iterating over it (#634) + task.stop(remove_task=False) + + if remove_tasks: + self._periodic_tasks = [] def __iter__(self): """Allow iteration on messages as they are received. From 948f141adc512a52bfa7de1647698ad0a0a0c474 Mon Sep 17 00:00:00 2001 From: Karl Date: Thu, 11 Jul 2019 10:00:11 -0700 Subject: [PATCH 0267/1235] Read the BCM status before creating a Task Currently bus.send_periodic blindly wraps the BCM socket API, and sends a BCM operation with the TX_SETUP opcode. However, the semantics that the kernel driver provides are an "upsert". As such, when one attempts to create two Tasks with the same CAN Arbitration ID, it ends up silently modifying the periodic messages, which is contrary to what our API implies. This fixes the problem by first sending a TX_READ opcode with the CAN Arbitration ID that we wish to create. The kernel driver will return -EINVAL if a periodic transmission with the given Arbitration ID does not exist. If this is the case, then we can continue creating the Task, otherwise we error and notify the user. Fixes #605 --- can/interfaces/socketcan/constants.py | 1 + can/interfaces/socketcan/socketcan.py | 25 +++++++++++++++++++ ...c_multiple.py => test_cyclic_socketcan.py} | 23 ++++++++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) rename test/{test_socketcan_cyclic_multiple.py => test_cyclic_socketcan.py} (95%) diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index ba5100403..98bbc39d1 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -11,6 +11,7 @@ # BCM opcodes CAN_BCM_TX_SETUP = 1 CAN_BCM_TX_DELETE = 2 +CAN_BCM_TX_READ = 3 # BCM flags SETTIMER = 0x0001 diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index f504d1cfc..a4886722f 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -330,6 +330,31 @@ def _tx_setup(self, messages): ival1 = 0 ival2 = self.period + # First do a TX_READ before creating a new task, and check if we get + # EINVAL. If so, then we are referring to a CAN message with the same + # ID + check_header = build_bcm_header( + opcode=CAN_BCM_TX_READ, + flags=0, + count=0, + ival1_seconds=0, + ival1_usec=0, + ival2_seconds=0, + ival2_usec=0, + can_id=self.can_id_with_flags, + nframes=0, + ) + try: + self.bcm_socket.send(check_header) + except OSError as e: + assert e.errno == errno.EINVAL + else: + raise ValueError( + "A periodic Task for Arbitration ID {} has already been created".format( + messages[0].arbitration_id + ) + ) + header = build_bcm_transmit_header( self.can_id_with_flags, count, diff --git a/test/test_socketcan_cyclic_multiple.py b/test/test_cyclic_socketcan.py similarity index 95% rename from test/test_socketcan_cyclic_multiple.py rename to test/test_cyclic_socketcan.py index ee2f0adaf..bd3042b38 100644 --- a/test/test_socketcan_cyclic_multiple.py +++ b/test/test_cyclic_socketcan.py @@ -10,7 +10,7 @@ @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") -class SocketCanCyclicMultiple(unittest.TestCase): +class CyclicSocketCan(unittest.TestCase): BITRATE = 500000 TIMEOUT = 0.1 @@ -244,6 +244,27 @@ def test_cyclic_initializer_different_arbitration_ids(self): with self.assertRaises(ValueError): task = self._send_bus.send_periodic(messages, self.PERIOD) + def test_create_same_id_raises_exception(self): + messages_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + messages_b = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + + task_a = self._send_bus.send_periodic(messages_a, 1) + self.assertIsInstance(task_a, can.broadcastmanager.CyclicSendTaskABC) + + # The second one raises a ValueError when we attempt to create a new + # Task, since it has the same arbitration ID. + with self.assertRaises(ValueError): + task_b = self._send_bus.send_periodic(messages_b, 1) + def test_modify_data_list(self): messages_odd = [] messages_odd.append( From 6a4da474265c4ca6a81731ee649f8da40aa816e1 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 13 Jul 2019 14:38:59 -0700 Subject: [PATCH 0268/1235] Raise Exception instead of using assert on EINVAL --- can/interfaces/socketcan/socketcan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index a4886722f..b99124719 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -347,7 +347,8 @@ def _tx_setup(self, messages): try: self.bcm_socket.send(check_header) except OSError as e: - assert e.errno == errno.EINVAL + if e.errno != errno.EINVAL: + raise e else: raise ValueError( "A periodic Task for Arbitration ID {} has already been created".format( From b05d94673c8f865cdb186a8c980fdb2103a0c9d2 Mon Sep 17 00:00:00 2001 From: "Seemann Jochen (CC-DA/ECU2)" Date: Sun, 14 Jul 2019 21:38:12 +0200 Subject: [PATCH 0269/1235] add support for query available PCAN channels --- can/interfaces/pcan/pcan.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 80b6054d0..1e19d0e76 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -477,6 +477,27 @@ def state(self, new_state): ) + @staticmethod + def _detect_available_configs(): + libraryHandle = PCANBasic() + channels = [] + interfaces = [] + for i in range(16): + interfaces.append({'id': TPCANHandle(PCAN_PCIBUS1.value + i), 'name': 'PCAN_PCIBUS'+str(i+1)}) + for i in range(16): + interfaces.append({'id': TPCANHandle(PCAN_USBBUS1.value + i), 'name': 'PCAN_USBBUS'+str(i+1)}) + for i in range(2): + interfaces.append({'id': TPCANHandle(PCAN_PCCBUS1.value + i), 'name': 'PCAN_PCCBUS'+str(i+1)}) + for i in range(16): + interfaces.append({'id': TPCANHandle(PCAN_LANBUS1.value + i), 'name': 'PCAN_LANBUS'+str(i+1)}) + status = TPCANStatus(0) + for i in interfaces: + error, value = libraryHandle.GetValue(i['id'], PCAN_CHANNEL_CONDITION) + if error != PCAN_ERROR_OK or value != PCAN_CHANNEL_AVAILABLE: + continue + channels.append({'interface': 'pcan', 'channel': i['name']}) + return channels + class PcanError(CanError): """ A generic error on a PCAN bus. From 4ecf02e94f5d0af50b93414e5ba527475fd85b8c Mon Sep 17 00:00:00 2001 From: "Seemann Jochen (CC-DA/ECU2)" Date: Sun, 14 Jul 2019 21:49:33 +0200 Subject: [PATCH 0270/1235] add CAN FD support of channel in config --- can/interfaces/pcan/pcan.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 1e19d0e76..69f5b7214 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -495,7 +495,11 @@ def _detect_available_configs(): error, value = libraryHandle.GetValue(i['id'], PCAN_CHANNEL_CONDITION) if error != PCAN_ERROR_OK or value != PCAN_CHANNEL_AVAILABLE: continue - channels.append({'interface': 'pcan', 'channel': i['name']}) + hasFd = False + error, value = libraryHandle.GetValue(i['id'], PCAN_CHANNEL_FEATURES) + if error == PCAN_ERROR_OK: + hasFd = bool(value & FEATURE_FD_CAPABLE) + channels.append({'interface': 'pcan', 'channel': i['name'], 'supportsFd': hasFd}) return channels class PcanError(CanError): From dab03b71f5c9622183ef014e5c5b4595a6acd093 Mon Sep 17 00:00:00 2001 From: "Seemann Jochen (CC-DA/ECU2)" Date: Sun, 14 Jul 2019 22:21:14 +0200 Subject: [PATCH 0271/1235] provide CAN FD support information in config detection --- can/interfaces/vector/canlib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index a34ebe853..579336c31 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -484,6 +484,7 @@ def _detect_available_configs(): "interface": "vector", "app_name": None, "channel": channel_config.channelIndex, + 'supportsFd': bool(channel_config.channelBusCapabilities & vxlapi.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT)} } ) return configs From b4cfda287ffa8b6c5f97fff64663ff46c1d4e804 Mon Sep 17 00:00:00 2001 From: "Seemann Jochen (CC-DA/ECU2)" Date: Sun, 14 Jul 2019 22:23:09 +0200 Subject: [PATCH 0272/1235] skip channels without CAN support --- can/interfaces/vector/canlib.py | 2 ++ can/interfaces/vector/vxlapi.py | 1 + 2 files changed, 3 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 579336c31..215b57a05 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -474,6 +474,8 @@ 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: + continue LOG.info( "Channel index %d: %s", channel_config.channelIndex, diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index 5669490ac..83e9cf337 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -63,6 +63,7 @@ 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 From 2868b6670ea340bfb6c3d05cb0d720f2161c0265 Mon Sep 17 00:00:00 2001 From: jsee23 Date: Mon, 15 Jul 2019 10:53:01 +0200 Subject: [PATCH 0273/1235] use consistent naming scheme Co-Authored-By: Felix Divo --- can/interfaces/pcan/pcan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 69f5b7214..f7da6e21a 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -499,7 +499,7 @@ def _detect_available_configs(): error, value = libraryHandle.GetValue(i['id'], PCAN_CHANNEL_FEATURES) if error == PCAN_ERROR_OK: hasFd = bool(value & FEATURE_FD_CAPABLE) - channels.append({'interface': 'pcan', 'channel': i['name'], 'supportsFd': hasFd}) + channels.append({'interface': 'pcan', 'channel': i['name'], 'supports_fd': hasFd}) return channels class PcanError(CanError): From 2c03af4ca574b0bc4566b2019cb598937943ceda Mon Sep 17 00:00:00 2001 From: jsee23 Date: Mon, 15 Jul 2019 10:55:13 +0200 Subject: [PATCH 0274/1235] use consistent naming scheme Co-Authored-By: Felix Divo --- can/interfaces/vector/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 215b57a05..3e1dd1df9 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -486,7 +486,7 @@ def _detect_available_configs(): "interface": "vector", "app_name": None, "channel": channel_config.channelIndex, - 'supportsFd': bool(channel_config.channelBusCapabilities & vxlapi.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT)} + 'supports_fd': bool(channel_config.channelBusCapabilities & vxlapi.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT)} } ) return configs From b1839f80a21e4aa4836b3fd86da19f5a30018413 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 15 Jul 2019 12:24:47 +0200 Subject: [PATCH 0275/1235] Document socketcan Adds some internal comments for the socketcan module. Removes a TODO that was placed there previously. --- can/interfaces/socketcan/socketcan.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index b99124719..a0ee5c595 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -1,5 +1,12 @@ # coding: utf-8 +""" +The main module of the socketcan interface containing most user-facing classes and methods +along some internal methods. + +At the end of the file the usage of the internal methods is shown. +""" + import logging import ctypes import ctypes.util @@ -726,15 +733,14 @@ def _detect_available_configs(): if __name__ == "__main__": - # TODO move below to examples? - - # Create two sockets on vcan0 to test send and receive + # This example demonstrates how to use the internal methods of this module. + # It creates two sockets on vcan0 to test sending and receiving. # # If you want to try it out you can do the following (possibly using sudo): # # modprobe vcan # ip link add dev vcan0 type vcan - # ifconfig vcan0 up + # ip link set vcan0 up # log.setLevel(logging.DEBUG) From 9f170be15ea49365bcdd6d442a7c4f84796ac301 Mon Sep 17 00:00:00 2001 From: "Seemann Jochen (CC-DA/ECU2)" Date: Mon, 15 Jul 2019 12:43:55 +0200 Subject: [PATCH 0276/1235] fix unvalid syntax --- can/interfaces/vector/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 3e1dd1df9..65b8b76eb 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -486,7 +486,7 @@ def _detect_available_configs(): "interface": "vector", "app_name": None, "channel": channel_config.channelIndex, - 'supports_fd': bool(channel_config.channelBusCapabilities & vxlapi.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT)} + 'supports_fd': bool(channel_config.channelBusCapabilities & vxlapi.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT) } ) return configs From a0362145ff6781d11aaa753779808a3bb45b076a Mon Sep 17 00:00:00 2001 From: Jochen Seemann Date: Mon, 15 Jul 2019 20:13:03 +0200 Subject: [PATCH 0277/1235] handle exception if library cannot be loaded --- can/interfaces/pcan/pcan.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index f7da6e21a..bbeba2e9b 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -479,8 +479,11 @@ def state(self, new_state): @staticmethod def _detect_available_configs(): - libraryHandle = PCANBasic() channels = [] + try: + libraryHandle = PCANBasic() + except: + return channels interfaces = [] for i in range(16): interfaces.append({'id': TPCANHandle(PCAN_PCIBUS1.value + i), 'name': 'PCAN_PCIBUS'+str(i+1)}) From 15264beb47a49c732c55afdbbb60bd2f1717bd59 Mon Sep 17 00:00:00 2001 From: Jochen Seemann Date: Mon, 15 Jul 2019 20:37:41 +0200 Subject: [PATCH 0278/1235] fix linter and formatting warnings --- can/interfaces/pcan/pcan.py | 47 +++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index bbeba2e9b..4bddfc3b1 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -476,35 +476,56 @@ def state(self, new_state): self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_ON ) - @staticmethod def _detect_available_configs(): channels = [] try: - libraryHandle = PCANBasic() - except: + library_handle = PCANBasic() + except OSError: return channels interfaces = [] for i in range(16): - interfaces.append({'id': TPCANHandle(PCAN_PCIBUS1.value + i), 'name': 'PCAN_PCIBUS'+str(i+1)}) + interfaces.append( + { + "id": TPCANHandle(PCAN_PCIBUS1.value + i), + "name": "PCAN_PCIBUS" + str(i + 1), + } + ) for i in range(16): - interfaces.append({'id': TPCANHandle(PCAN_USBBUS1.value + i), 'name': 'PCAN_USBBUS'+str(i+1)}) + interfaces.append( + { + "id": TPCANHandle(PCAN_USBBUS1.value + i), + "name": "PCAN_USBBUS" + str(i + 1), + } + ) for i in range(2): - interfaces.append({'id': TPCANHandle(PCAN_PCCBUS1.value + i), 'name': 'PCAN_PCCBUS'+str(i+1)}) + interfaces.append( + { + "id": TPCANHandle(PCAN_PCCBUS1.value + i), + "name": "PCAN_PCCBUS" + str(i + 1), + } + ) for i in range(16): - interfaces.append({'id': TPCANHandle(PCAN_LANBUS1.value + i), 'name': 'PCAN_LANBUS'+str(i+1)}) - status = TPCANStatus(0) + interfaces.append( + { + "id": TPCANHandle(PCAN_LANBUS1.value + i), + "name": "PCAN_LANBUS" + str(i + 1), + } + ) for i in interfaces: - error, value = libraryHandle.GetValue(i['id'], PCAN_CHANNEL_CONDITION) + error, value = library_handle.GetValue(i["id"], PCAN_CHANNEL_CONDITION) if error != PCAN_ERROR_OK or value != PCAN_CHANNEL_AVAILABLE: continue - hasFd = False - error, value = libraryHandle.GetValue(i['id'], PCAN_CHANNEL_FEATURES) + has_fd = False + error, value = library_handle.GetValue(i["id"], PCAN_CHANNEL_FEATURES) if error == PCAN_ERROR_OK: - hasFd = bool(value & FEATURE_FD_CAPABLE) - channels.append({'interface': 'pcan', 'channel': i['name'], 'supports_fd': hasFd}) + has_fd = bool(value & FEATURE_FD_CAPABLE) + channels.append( + {"interface": "pcan", "channel": i["name"], "supports_fd": has_fd} + ) return channels + class PcanError(CanError): """ A generic error on a PCAN bus. From 1aa0bc64033b5fff6bc88474e7d1afb52e81ee7a Mon Sep 17 00:00:00 2001 From: karl ding Date: Wed, 17 Jul 2019 08:52:49 -0700 Subject: [PATCH 0279/1235] Add test for stopping Cyclic Tasks with removal (#645) This increases test coverage in order to verify that Cyclic Tasks are correctly stopped when multiple tasks are active on a particular bus. The test is carried out via the SocketCAN interface. Addresses #634 --- test/test_cyclic_socketcan.py | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index bd3042b38..bb5411be9 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -523,6 +523,56 @@ def test_modify_data_different_arbitration_id_than_original(self): with self.assertRaises(ValueError): task.modify_data(new_message) + def test_stop_all_periodic_tasks_and_remove_task(self): + message_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + message_b = can.Message( + arbitration_id=0x402, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + message_c = can.Message( + arbitration_id=0x403, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + + # Start Tasks + task_a = self._send_bus.send_periodic(message_a, self.PERIOD) + task_b = self._send_bus.send_periodic(message_b, self.PERIOD) + task_c = self._send_bus.send_periodic(message_c, self.PERIOD) + + self.assertIsInstance(task_a, can.broadcastmanager.ModifiableCyclicTaskABC) + self.assertIsInstance(task_b, can.broadcastmanager.ModifiableCyclicTaskABC) + self.assertIsInstance(task_c, can.broadcastmanager.ModifiableCyclicTaskABC) + + for _ in range(6): + _ = self._recv_bus.recv(self.PERIOD) + + # Stop all tasks and delete + self._send_bus.stop_all_periodic_tasks(remove_tasks=True) + + # Now wait for a few periods, after which we should definitely not + # receive any CAN messages + time.sleep(4 * self.PERIOD) + + # If we successfully deleted everything, then we will eventually read + # 0 messages. + successfully_stopped = False + for _ in range(6): + rx_message = self._recv_bus.recv(self.PERIOD) + + if rx_message is None: + successfully_stopped = True + break + self.assertTrue(successfully_stopped, "Still received messages after stopping") + + # None of the tasks should still be associated with the bus + self.assertEqual(0, len(self._send_bus._periodic_tasks)) + if __name__ == "__main__": unittest.main() From ba633096253d8feb9cd8763acb87b2458387fbf2 Mon Sep 17 00:00:00 2001 From: Jochen Seemann Date: Thu, 18 Jul 2019 21:06:26 +0200 Subject: [PATCH 0280/1235] fix formatter warnings --- can/interfaces/vector/canlib.py | 5 ++++- can/interfaces/vector/vxlapi.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 65b8b76eb..970994c56 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -486,7 +486,10 @@ def _detect_available_configs(): "interface": "vector", "app_name": None, "channel": channel_config.channelIndex, - 'supports_fd': bool(channel_config.channelBusCapabilities & vxlapi.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT) + "supports_fd": bool( + channel_config.channelBusCapabilities + & vxlapi.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT + ), } ) return configs diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index 01fed87e3..b631923f2 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -117,7 +117,7 @@ XL_INTERFACE_VERSION = 3 XL_INTERFACE_VERSION_V4 = 4 -XL_BUS_ACTIVE_CAP_CAN = (XL_BUS_TYPE_CAN << 16) +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 From 8fef6997b55ea9802c78cf8fbf86fc0f9d13568a Mon Sep 17 00:00:00 2001 From: Karl Date: Tue, 16 Jul 2019 22:46:26 -0700 Subject: [PATCH 0281/1235] Add mypy type-checking to Travis CI builds Currently just get a few files running under mypy without any errors. The files can incrementally be converted and guarded by CI builds, until python-can is completely PEP 561 compliant. --- .travis.yml | 2 ++ can/__init__.py | 4 +++- can/bit_timing.py | 18 +++++++++--------- can/listener.py | 2 +- can/thread_safe_bus.py | 4 ++-- requirements-lint.txt | 2 ++ 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2eca95d6d..f35a148aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,6 +84,8 @@ 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/util.py - stage: linter name: "Formatting Checks" python: "3.7" 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/listener.py b/can/listener.py index 2f773fcf0..ac0f1aa64 100644 --- a/can/listener.py +++ b/can/listener.py @@ -11,7 +11,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 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/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 From acda99212bb373e0ca70e9d969db788c19bee5e0 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 23 Jul 2019 01:11:21 +0200 Subject: [PATCH 0282/1235] refactoring and tests for vector interface (#646) * add tests for vector interface * fixed WaitForSingleObject with Mock, formatted with black * catch ctypes.windll exception to read ctypes structures add side_effects to mocks * add xlSetNotification mock for windows testing * Rename vxlapi.py to XLDriver.py * refactored vxlapi to XLDefine (enums of constants), XLClass (data types and structures) and XLDriver (api functions) * renamed modules for PEP8 conformity, removed unnecessary lines from test_vector.py --- can/interfaces/vector/canlib.py | 193 +++++++----- can/interfaces/vector/vxlapi.py | 486 ------------------------------ can/interfaces/vector/xlclass.py | 226 ++++++++++++++ can/interfaces/vector/xldefine.py | 172 +++++++++++ can/interfaces/vector/xldriver.py | 224 ++++++++++++++ test/test_vector.py | 301 ++++++++++++++++++ 6 files changed, 1041 insertions(+), 561 deletions(-) delete mode 100644 can/interfaces/vector/vxlapi.py create mode 100644 can/interfaces/vector/xlclass.py create mode 100644 can/interfaces/vector/xldefine.py create mode 100644 can/interfaces/vector/xldriver.py create mode 100644 test/test_vector.py diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 970994c56..5dc44c4e0 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -37,10 +37,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) @@ -93,7 +97,7 @@ def __init__( Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. """ - if vxlapi is None: + if xldriver is None: raise ImportError("The Vector API has not been loaded") self.poll_interval = poll_interval if isinstance(channel, (list, tuple)): @@ -129,8 +133,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 +147,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 +165,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 +177,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 +209,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 +225,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 +247,7 @@ def __init__( ) else: if bitrate: - vxlapi.xlCanSetChannelBitrate( + xldriver.xlCanSetChannelBitrate( self.port_handle, permission_mask, bitrate ) LOG.info("SetChannelBitrate: baudr.=%u", bitrate) @@ -252,25 +256,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 +292,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 +314,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 +335,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 +362,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 +394,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 +408,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 +446,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 +456,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 +474,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 +514,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 +531,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 +539,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/test/test_vector.py b/test/test_vector.py new file mode 100644 index 000000000..69fb42133 --- /dev/null +++ b/test/test_vector.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +Test for Vector Interface +""" + +import ctypes +import time +import logging +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") + 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) + 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) + 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, + ) + 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") + 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) + 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") + 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) + 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") + 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") + 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): + self.bus = can.Bus(channel=0, bustype="vector") + self.bus.reset() + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() + can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called() + + +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() From d0a9b8fe6717c45e750a89203c2bb9c7283f95ac Mon Sep 17 00:00:00 2001 From: Karl Date: Tue, 23 Jul 2019 07:11:14 -0700 Subject: [PATCH 0283/1235] Add sphinx-autodoc-typehints to sphinx docs Add the sphinx-autodoc-typehints extension to the Sphinx documentation generation pipeline, which allows us to automatically generate the appropriate :type argname: and :rtype: directives in the docstring. --- doc/conf.py | 1 + doc/doc-requirements.txt | 1 + 2 files changed, 2 insertions(+) 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 From 6384baa946ab9a993236a88e03234a07fee507a3 Mon Sep 17 00:00:00 2001 From: zariiii9003 Date: Wed, 24 Jul 2019 21:06:59 +0200 Subject: [PATCH 0284/1235] add event based cyclic send --- can/broadcastmanager.py | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index edb5da2a3..a3987f4ae 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -14,6 +14,16 @@ import can +# try to import win32event for event-based cyclic send task(needs pywin32 package) +try: + import win32event + + HAS_EVENTS = True +except ImportError as e: + print(str(e)) + HAS_EVENTS = False + + log = logging.getLogger("can.bcm") @@ -178,10 +188,19 @@ def __init__(self, bus, lock, messages, period, duration=None): 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, "TIMER_" + str(self.period_ms) + ) + self.start() def stop(self): + if HAS_EVENTS: + win32event.CancelWaitableTimer(self.event.handle) self.stopped = True def start(self): @@ -190,6 +209,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 +222,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)) From 8e5a3098a4abe01dd607208be7ae8a2cfc3ccea6 Mon Sep 17 00:00:00 2001 From: zariiii9003 Date: Wed, 24 Jul 2019 21:35:59 +0200 Subject: [PATCH 0285/1235] little fix --- can/broadcastmanager.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index a3987f4ae..6c44c5272 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -23,7 +23,6 @@ print(str(e)) HAS_EVENTS = False - log = logging.getLogger("can.bcm") @@ -192,9 +191,7 @@ def __init__(self, bus, lock, messages, period, duration=None): if HAS_EVENTS: self.period_ms: int = int(round(period * 1000, 0)) - self.event = win32event.CreateWaitableTimer( - None, False, "TIMER_" + str(self.period_ms) - ) + self.event = win32event.CreateWaitableTimer(None, False, None) self.start() From 8b66f0955838c9f9079660ea809a510781a03392 Mon Sep 17 00:00:00 2001 From: zariiii9003 Date: Wed, 24 Jul 2019 21:41:33 +0200 Subject: [PATCH 0286/1235] little fix --- can/broadcastmanager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 6c44c5272..bd550e5db 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -19,8 +19,7 @@ import win32event HAS_EVENTS = True -except ImportError as e: - print(str(e)) +except ImportError: HAS_EVENTS = False log = logging.getLogger("can.bcm") From 0019438d984af0f80c9b66a900aa032b486b7888 Mon Sep 17 00:00:00 2001 From: karl ding Date: Sun, 28 Jul 2019 17:30:26 -0700 Subject: [PATCH 0287/1235] Add typing annotations for functions in can.bus (#652) This adds typing annotations for use via mypy for all functions under can.bus. This works towards PEP 561 compatibility. * Add file for type-checking specific code. Add a Type Alias for CAN Filters used by can.bus * Fix pylint unused import error * Switch CAN Filter to TypedDict * Add mypy_extensions dependency to install_requires * Remove types generated by sphinx-autodoc-typehints With the introduction of the sphinx-autodoc-typehints extension, we don't need to duplicate typing information in the docstring as well as the function signature. --- can/bus.py | 90 +++++++++++++++++++++++++-------------------- can/typechecking.py | 10 +++++ setup.py | 1 + 3 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 can/typechecking.py diff --git a/can/bus.py b/can/bus.py index 59ddbd1d2..b6e7935b7 100644 --- a/can/bus.py +++ b/can/bus.py @@ -4,6 +4,10 @@ Contains the ABC bus implementation and its documentation. """ +from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union + +import can.typechecking + from abc import ABCMeta, abstractmethod import can import logging @@ -38,7 +42,12 @@ class BusABC(metaclass=ABCMeta): RECV_LOGGING_LEVEL = 9 @abstractmethod - def __init__(self, channel, can_filters=None, **kwargs): + def __init__( + self, + channel: Any, + 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,26 +56,24 @@ 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[can.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. :raises can.CanError: @@ -100,7 +107,9 @@ def recv(self, timeout=None): else: return None - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[can.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 +137,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 +152,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: can.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. - :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 +171,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[can.Message], can.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 +188,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:: @@ -223,30 +235,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[can.Message], can.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 +291,7 @@ def stop_all_periodic_tasks(self, remove_tasks=True): if remove_tasks: self._periodic_tasks = [] - def __iter__(self): + def __iter__(self) -> Iterator[can.Message]: """Allow iteration on messages as they are received. >>> for msg in bus: @@ -291,7 +307,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 +315,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 +343,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: can.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 +403,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 +425,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/typechecking.py b/can/typechecking.py new file mode 100644 index 000000000..29ceded62 --- /dev/null +++ b/can/typechecking.py @@ -0,0 +1,10 @@ +"""Types for mypy type-checking +""" +import typing + +import mypy_extensions + +CanFilter = mypy_extensions.TypedDict( + "CanFilter", {"can_id": int, "can_mask": int, "extended": bool} +) +CanFilters = typing.Iterable[CanFilter] diff --git a/setup.py b/setup.py index ded73f458..a68e76721 100644 --- a/setup.py +++ b/setup.py @@ -103,6 +103,7 @@ "aenum", 'windows-curses;platform_system=="Windows"', "filelock", + "mypy_extensions >= 0.4.0, < 0.5.0", ], setup_requires=pytest_runner, extras_require=extras_require, From 02d5032332b7a6a8675db85b18cfb23851f9beae Mon Sep 17 00:00:00 2001 From: Colin Rafferty Date: Mon, 29 Jul 2019 10:19:19 -0400 Subject: [PATCH 0288/1235] Do not incorrectly reset CANMsg.MSGTYPE on remote frame. In `PcanBus.send()`, we initially set `msgType` based on all the flags of `msg`, including RTR. In the if/else for `self.fd`, we are incorrectly resetting it if rtr. We should not, and so we are no longer doing it. --- can/interfaces/pcan/pcan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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] From 80cc665fabb1b828c42398272a6f713d3dfb1e1f Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 28 Jul 2019 20:00:17 -0700 Subject: [PATCH 0289/1235] Add typing annotations for can.broadcastmanager This adds typing annotations for functions in can.broadcastmanager. A Channel definition is introduced for use in can.bus for the generic case. In addition, this remove the redundant typing information that was previously in the docstring, since we now have sphinx-autodoc-typehints to generate the types for the docs from the annotations in the function signature. This works towards PEP 561 compatibility. --- .travis.yml | 16 ++++++++++- can/broadcastmanager.py | 64 +++++++++++++++++++++++++++++------------ can/bus.py | 4 +-- can/typechecking.py | 3 ++ 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index f35a148aa..4d765d43a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -85,7 +85,21 @@ jobs: # 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/util.py + - 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/util.py - stage: linter name: "Formatting Checks" python: "3.7" diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index edb5da2a3..460154e50 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -7,6 +7,10 @@ :meth:`can.BusABC.send_periodic`. """ +from typing import Optional, Sequence, Tuple, Union + +import can.typechecking + import abc import logging import threading @@ -36,11 +40,13 @@ class CyclicSendTaskABC(CyclicTask): Message send task with defined period """ - def __init__(self, messages, period): + def __init__( + self, messages: Union[Sequence[can.Message], can.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 +56,9 @@ def __init__(self, messages, period): self.messages = messages @staticmethod - def _check_and_convert_messages(messages): + def _check_and_convert_messages( + messages: Union[Sequence[can.Message], can.Message] + ) -> Tuple[can.Message, ...]: """Helper function to convert a Message or Sequence of messages into a tuple, and raises an error when the given value is invalid. @@ -84,13 +92,18 @@ def _check_and_convert_messages(messages): class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): - def __init__(self, messages, period, duration): + def __init__( + self, + messages: Union[Sequence[can.Message], can.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 +123,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[can.Message, ...]): """Helper function to perform error checking when modifying the data in the cyclic task. @@ -130,11 +143,11 @@ def _check_modified_messages(self, messages): "from when the task was created" ) - def modify_data(self, messages): + def modify_data(self, messages: Union[Sequence[can.Message], can.Message]): """Update the contents of the periodically sent messages, without altering the timing. - :param Union[Sequence[can.Message], can.Message] messages: + :param messages: The messages with the new :attr:`can.Message.data`. Note: The arbitration ID cannot be changed. @@ -153,18 +166,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: can.typechecking.Channel, + messages: Union[Sequence[can.Message], can.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,7 +193,14 @@ class ThreadBasedCyclicSendTask( ): """Fallback cyclic send task using thread.""" - def __init__(self, bus, lock, messages, period, duration=None): + def __init__( + self, + bus: "can.bus.BusABC", + lock: threading.Lock, + messages: Union[Sequence[can.Message], can.Message], + period: float, + duration: Optional[float] = None, + ): super().__init__(messages, period, duration) self.bus = bus self.send_lock = lock diff --git a/can/bus.py b/can/bus.py index b6e7935b7..c188939a9 100644 --- a/can/bus.py +++ b/can/bus.py @@ -4,7 +4,7 @@ Contains the ABC bus implementation and its documentation. """ -from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union +from typing import Iterator, List, Optional, Sequence, Tuple, Union import can.typechecking @@ -44,7 +44,7 @@ class BusABC(metaclass=ABCMeta): @abstractmethod def __init__( self, - channel: Any, + channel: can.typechecking.Channel, can_filters: Optional[can.typechecking.CanFilters] = None, **kwargs: object ): diff --git a/can/typechecking.py b/can/typechecking.py index 29ceded62..d592d0d4a 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -8,3 +8,6 @@ "CanFilter", {"can_id": int, "can_mask": int, "extended": bool} ) CanFilters = typing.Iterable[CanFilter] + +# Used for the Abstract Base Class +Channel = typing.Union[int, str] From 41e592c4e776294f9d7b42467b99824ff5bfa09b Mon Sep 17 00:00:00 2001 From: Karl Date: Thu, 1 Aug 2019 01:34:06 -0700 Subject: [PATCH 0290/1235] Add typing annotations for can.message This adds typing annotations for functions in can.message. Currently the typing annotation for the Message data is incomplete due to upstream not implementing the buffer protocol. Since can.Message attempts to cast the data to a bytearray via bytearray(), the Message data can be any type that bytearray() supports. In addition, this remove the redundant typing information that was previously in the docstring, since we now have sphinx-autodoc-typehints to generate the types for the docs from the annotations in the function signature. This works towards PEP 561 compatibility. --- can/message.py | 61 ++++++++++++++++++++++++--------------------- can/typechecking.py | 7 ++++++ 2 files changed, 39 insertions(+), 29 deletions(-) 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/typechecking.py b/can/typechecking.py index d592d0d4a..9ca9edfd0 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -9,5 +9,12 @@ ) 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] From d58aac2d76175d4e40ec4087ca16bd809f65240d Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 3 Aug 2019 00:04:47 -0700 Subject: [PATCH 0291/1235] Add RedirectReader to the generated Listener docs This makes Sphinx aware of the RedirectReader class, which allows CAN Messages to be gatewayed from one Bus to another Bus. --- doc/listeners.rst | 7 +++++++ 1 file changed, 7 insertions(+) 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 ------ From 089e29cd1a0339043970a49172a3cffdfbe6f604 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 3 Aug 2019 12:32:51 +0200 Subject: [PATCH 0292/1235] State "windows only" in Vector docs Fix #463 --- doc/interfaces/vector.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 35402f2f775278e520e35eb4607972b34c5f74c3 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 3 Aug 2019 12:39:45 +0200 Subject: [PATCH 0293/1235] raise more helpful exception if using vector on non-windows platforms --- can/interfaces/vector/canlib.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 5dc44c4e0..6e65561f0 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 @@ -97,8 +98,14 @@ def __init__( Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. """ + if os.name != "nt": + 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 From 451ac0b1b61c020043002ae87dc0d05327541ba0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 3 Aug 2019 13:44:06 +0200 Subject: [PATCH 0294/1235] add testing flag to vector --- can/interfaces/vector/canlib.py | 2 +- test/test_vector.py | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 6e65561f0..8ba1afbde 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -98,7 +98,7 @@ def __init__( Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. """ - if os.name != "nt": + if os.name != "nt" and not kwargs.get("testing", d=False): raise OSError( f'The Vector interface is only supported on Windows, but you are running "{os.name}"' ) diff --git a/test/test_vector.py b/test/test_vector.py index 69fb42133..558a2ea4a 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -18,6 +18,7 @@ class TestVectorBus(unittest.TestCase): + def setUp(self) -> None: # basic mock for XLDriver can.interfaces.vector.canlib.xldriver = Mock() @@ -76,7 +77,7 @@ def tearDown(self) -> None: self.bus = None def test_bus_creation(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector") + 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() @@ -92,7 +93,7 @@ def test_bus_creation(self) -> None: 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) + 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() @@ -112,7 +113,7 @@ def test_bus_creation_bitrate(self) -> None: self.assertEqual(xlCanSetChannelBitrate_args[2], 200000) def test_bus_creation_fd(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", fd=True) + 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() @@ -141,6 +142,7 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: sjwDbr=13, tseg1Dbr=14, tseg2Dbr=15, + testing=True, ) self.assertIsInstance(self.bus, canlib.VectorBus) can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() @@ -171,19 +173,19 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: self.assertEqual(canFdConf.tseg2Dbr, 15) def test_receive(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector") + 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) + 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") + 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 ) @@ -192,7 +194,7 @@ def test_send(self) -> None: 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) + 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 ) @@ -201,19 +203,19 @@ def test_send_fd(self) -> None: can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_called() def test_flush_tx_buffer(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector") + 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") + 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): - self.bus = can.Bus(channel=0, bustype="vector") + 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() From 39af904d188080c1656db5621efc627c7eac89b0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 3 Aug 2019 14:06:27 +0200 Subject: [PATCH 0295/1235] syntax fixed --- can/interfaces/vector/canlib.py | 4 ++-- test/test_vector.py | 23 +++++++++++------------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 8ba1afbde..7c8d9c7c3 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -71,7 +71,7 @@ def __init__( sjwDbr=2, tseg1Dbr=6, tseg2Dbr=3, - **kwargs + **kwargs, ): """ :param list channel: @@ -98,7 +98,7 @@ def __init__( Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. """ - if os.name != "nt" and not kwargs.get("testing", d=False): + 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}"' ) diff --git a/test/test_vector.py b/test/test_vector.py index 558a2ea4a..e5a634a50 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -18,7 +18,6 @@ class TestVectorBus(unittest.TestCase): - def setUp(self) -> None: # basic mock for XLDriver can.interfaces.vector.canlib.xldriver = Mock() @@ -77,7 +76,7 @@ def tearDown(self) -> None: self.bus = None def test_bus_creation(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", testing=True) + 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() @@ -93,7 +92,7 @@ def test_bus_creation(self) -> None: 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.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() @@ -113,7 +112,7 @@ def test_bus_creation_bitrate(self) -> None: 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.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() @@ -142,7 +141,7 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: sjwDbr=13, tseg1Dbr=14, tseg2Dbr=15, - testing=True, + _testing=True, ) self.assertIsInstance(self.bus, canlib.VectorBus) can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() @@ -173,19 +172,19 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: self.assertEqual(canFdConf.tseg2Dbr, 15) def test_receive(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", testing=True) + 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 = 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) + 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 ) @@ -194,7 +193,7 @@ def test_send(self) -> None: 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) + 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 ) @@ -203,19 +202,19 @@ def test_send_fd(self) -> None: 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 = 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 = 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): - self.bus = can.Bus(channel=0, bustype="vector", testing=True) + 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() From 4de88d205b82bb8dcdecf695b8ddf9b514e9233a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 3 Aug 2019 14:44:14 +0200 Subject: [PATCH 0296/1235] add tiny test for new exception being thrown --- test/test_vector.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/test_vector.py b/test/test_vector.py index e5a634a50..639b28de9 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -8,6 +8,7 @@ import ctypes import time import logging +import os import unittest from unittest.mock import Mock @@ -213,12 +214,19 @@ def test_shutdown(self) -> None: can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called() can.interfaces.vector.canlib.xldriver.xlCloseDriver.assert_called() - def test_reset(self): + 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, From d035cab46e035a4873d279e3e74cd536ca777fb1 Mon Sep 17 00:00:00 2001 From: zariiii9003 Date: Sun, 4 Aug 2019 14:15:28 +0200 Subject: [PATCH 0297/1235] install pywin32 on appveyor CI --- .appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index 500c71320..4bd178a8f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -14,6 +14,9 @@ install: # Prepend Python installation and scripts (e.g. pytest) to PATH - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% + # Install pywin32 for Windows Events + - "python -m pip install pywin32" + # We need to install the python-can library itself including the dependencies - "python -m pip install .[test,neovi]" From 88b4a2115b70789ebe9db591c06bcf2a5d51fe4a Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 6 Aug 2019 13:31:46 +0200 Subject: [PATCH 0298/1235] Add Vector dependencies to docs (#670) --- doc/installation.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 ----------------------------------------- From 93062bdd63ccc85695f9d154d18d4e3e5aeceecf Mon Sep 17 00:00:00 2001 From: zariiii9003 Date: Tue, 6 Aug 2019 19:01:04 +0200 Subject: [PATCH 0299/1235] Revert "install pywin32 on appveyor CI" This reverts commit d035cab4 --- .appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 4bd178a8f..500c71320 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -14,9 +14,6 @@ install: # Prepend Python installation and scripts (e.g. pytest) to PATH - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% - # Install pywin32 for Windows Events - - "python -m pip install pywin32" - # We need to install the python-can library itself including the dependencies - "python -m pip install .[test,neovi]" From 3c5edf80a3d70dd090abe8d646f6090701f39b93 Mon Sep 17 00:00:00 2001 From: zariiii9003 Date: Tue, 6 Aug 2019 19:02:20 +0200 Subject: [PATCH 0300/1235] add pywin32 to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a68e76721..2b1bf2296 100644 --- a/setup.py +++ b/setup.py @@ -104,6 +104,7 @@ '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, From 78585da6ed3dab1db2fa2710b33d1b0ca99fab3c Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 3 Aug 2019 12:57:48 -0700 Subject: [PATCH 0301/1235] Add the seeedstudio interface to docs Previously the seeedstudio interface wasn't being included in the generated docs. This silences the warning: WARNING: document isn't included in any toctree --- doc/interfaces.rst | 1 + 1 file changed, 1 insertion(+) 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. From 3594d6bcf120364cce85b3625b6b9546f56fa4cb Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 3 Aug 2019 12:19:17 -0700 Subject: [PATCH 0302/1235] Fix the docs for can.detect_available_configs This fixes the Sphinx documentation for can.detect_available_configs so that it appears in the generated build output. --- doc/api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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: From 7252e865a89992ed8648a9d20e9efd648ec3c581 Mon Sep 17 00:00:00 2001 From: Karl Date: Fri, 2 Aug 2019 23:43:48 -0700 Subject: [PATCH 0303/1235] Add typing annotations for can.listener This adds typing annotations for functions in can.listener. In addition, this remove the redundant typing information that was previously in the docstring, since we now have sphinx-autodoc-typehints to generate the types for the docs from the annotations in the function signature. This works towards PEP 561 compatibility. --- can/broadcastmanager.py | 37 ++++++++++++++++++----------------- can/bus.py | 25 ++++++++++++------------ can/listener.py | 43 ++++++++++++++++++++++------------------- 3 files changed, 55 insertions(+), 50 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 1b616cf34..733693802 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -7,17 +7,20 @@ :meth:`can.BusABC.send_periodic`. """ -from typing import Optional, Sequence, Tuple, Union +from typing import Optional, Sequence, Tuple, Union, TYPE_CHECKING -import can.typechecking +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 @@ -48,9 +51,7 @@ class CyclicSendTaskABC(CyclicTask): Message send task with defined period """ - def __init__( - self, messages: Union[Sequence[can.Message], can.Message], period: float - ): + def __init__(self, messages: Union[Sequence[Message], Message], period: float): """ :param messages: The messages to be sent periodically. @@ -65,8 +66,8 @@ def __init__( @staticmethod def _check_and_convert_messages( - messages: Union[Sequence[can.Message], can.Message] - ) -> Tuple[can.Message, ...]: + 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. @@ -76,7 +77,7 @@ def _check_and_convert_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") @@ -102,7 +103,7 @@ def _check_and_convert_messages( class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): def __init__( self, - messages: Union[Sequence[can.Message], can.Message], + messages: Union[Sequence[Message], Message], period: float, duration: Optional[float], ): @@ -131,7 +132,7 @@ def start(self): class ModifiableCyclicTaskABC(CyclicSendTaskABC): """Adds support for modifying a periodic message""" - def _check_modified_messages(self, messages: Tuple[can.Message, ...]): + def _check_modified_messages(self, messages: Tuple[Message, ...]): """Helper function to perform error checking when modifying the data in the cyclic task. @@ -151,12 +152,12 @@ def _check_modified_messages(self, messages: Tuple[can.Message, ...]): "from when the task was created" ) - def modify_data(self, messages: Union[Sequence[can.Message], can.Message]): + def modify_data(self, messages: Union[Sequence[Message], Message]): """Update the contents of the periodically sent messages, without altering the timing. :param messages: - The messages with the new :attr:`can.Message.data`. + The messages with the new :attr:`Message.data`. Note: The arbitration ID cannot be changed. @@ -176,8 +177,8 @@ class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): def __init__( self, - channel: can.typechecking.Channel, - messages: Union[Sequence[can.Message], can.Message], + channel: typechecking.Channel, + messages: Union[Sequence[Message], Message], count: int, initial_period: float, subsequent_period: float, @@ -203,9 +204,9 @@ class ThreadBasedCyclicSendTask( def __init__( self, - bus: "can.bus.BusABC", + bus: "BusABC", lock: threading.Lock, - messages: Union[Sequence[can.Message], can.Message], + messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, ): diff --git a/can/bus.py b/can/bus.py index c188939a9..e22bf6157 100644 --- a/can/bus.py +++ b/can/bus.py @@ -15,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__) @@ -68,14 +69,14 @@ def __init__( def __str__(self) -> str: return self.channel_info - def recv(self, timeout: Optional[float] = None) -> Optional[can.Message]: + def recv(self, timeout: Optional[float] = None) -> Optional[Message]: """Block waiting for a message from the Bus. :param timeout: seconds to wait for a message or None to wait indefinitely :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 """ @@ -109,7 +110,7 @@ def recv(self, timeout: Optional[float] = None) -> Optional[can.Message]: def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[can.Message], bool]: + ) -> 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` @@ -152,12 +153,12 @@ def _recv_internal( raise NotImplementedError("Trying to read from a write only bus?") @abstractmethod - def send(self, msg: can.Message, timeout: Optional[float] = 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. :param timeout: If > 0, wait up to this many seconds for message to be ACK'ed or @@ -173,7 +174,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): def send_periodic( self, - msgs: Union[Sequence[can.Message], can.Message], + msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, store_task: bool = True, @@ -217,7 +218,7 @@ def send_periodic( 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") @@ -244,7 +245,7 @@ def wrapped_stop_method(remove_task=True): def _send_periodic_internal( self, - msgs: Union[Sequence[can.Message], can.Message], + msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: @@ -291,7 +292,7 @@ def stop_all_periodic_tasks(self, remove_tasks=True): if remove_tasks: self._periodic_tasks = [] - def __iter__(self) -> Iterator[can.Message]: + def __iter__(self) -> Iterator[Message]: """Allow iteration on messages as they are received. >>> for msg in bus: @@ -299,7 +300,7 @@ def __iter__(self) -> Iterator[can.Message]: :yields: - :class:`can.Message` msg objects. + :class:`Message` msg objects. """ while True: msg = self.recv(timeout=1.0) @@ -352,7 +353,7 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]): See :meth:`~can.BusABC.set_filters` for details. """ - def _matches_filters(self, msg: can.Message) -> bool: + 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. diff --git a/can/listener.py b/can/listener.py index ac0f1aa64..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: @@ -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() From 29e8e1fecca1aead3b2ab3ed09833289fbc5947b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 7 Aug 2019 01:41:28 +0200 Subject: [PATCH 0304/1235] Add .mypy_cache to gitignore file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6b813427e..5c4962ea5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ test/__tempdir__/ .pytest_cache/ +.mypy_cache/ # ------------------------- # below: https://github.com/github/gitignore/blob/da00310ccba9de9a988cc973ef5238ad2c1460e9/Python.gitignore From a7527a4eb4c1d2ac93cfe53a3d7c363ef8ef157b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Aug 2019 21:05:20 +0200 Subject: [PATCH 0305/1235] add mypy deamon config to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5c4962ea5..258ca73ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ test/__tempdir__/ .pytest_cache/ .mypy_cache/ +.dmypy.json +dmypy.json # ------------------------- # below: https://github.com/github/gitignore/blob/da00310ccba9de9a988cc973ef5238ad2c1460e9/Python.gitignore From ec4862f760434b26e7e6b9b98de73e88f1d228d3 Mon Sep 17 00:00:00 2001 From: Karl Date: Fri, 9 Aug 2019 15:54:26 -0700 Subject: [PATCH 0306/1235] Remove deprecated SocketCAN interfaces from list The SocketCAN interface used to be split into socketcan_native and socketcan_ctypes interfaces. However, these have now been deprecated, with a deprecation window throughout 3.*.* releases. The code relevant to these interfaces has already been cleaned up, but it seems like these references were missed in the process. --- can/interfaces/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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())) From 807fc54da96e4f3e203f9532e0d1f2646c0ebc8c Mon Sep 17 00:00:00 2001 From: Benny Meisels Date: Mon, 12 Aug 2019 12:15:29 +0300 Subject: [PATCH 0307/1235] resolves #680 --- can/interfaces/pcan/basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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) From e6f3453e0abf3665b7c932d5ffb549ce73630a59 Mon Sep 17 00:00:00 2001 From: Karl Date: Wed, 7 Aug 2019 21:16:47 -0700 Subject: [PATCH 0308/1235] Add typing annotations for can.notifier This adds typing annotations for functions in can.notifier. In addition, this remove the redundant typing information that was previously in the docstring, since we now have sphinx-autodoc-typehints to generate the types for the docs from the annotations in the function signature. This works towards PEP 561 compatibility. --- can/notifier.py | 65 +++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 26 deletions(-) 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) From cd3f81bedda963eba2f492631999d6101561f55c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 7 Aug 2019 01:43:22 +0200 Subject: [PATCH 0309/1235] add typing to the generic IO module --- .travis.yml | 2 ++ can/io/generic.py | 16 ++++++++++------ can/typechecking.py | 6 ++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4d765d43a..fc226944c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -99,7 +99,9 @@ jobs: can/notifier.py can/player.py can/thread_safe_bus.py + can/typechecking.py can/util.py + can/io/generic.py - stage: linter name: "Formatting Checks" python: "3.7" diff --git a/can/io/generic.py b/can/io/generic.py index 62bae18d4..8c86a1fd3 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -5,6 +5,9 @@ """ from abc import ABCMeta +from typing import Optional, Union, cast + +from can.typechecking import FileLike, PathLike class BaseIOHandler(metaclass=ABCMeta): @@ -12,24 +15,24 @@ 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: Optional[Union[FileLike, PathLike]], mode: str = "rt"): """ :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[FileLike], file) else: # file is some path-like object - self.file = open(file, mode) + self.file = open(cast(PathLike, file), mode) # for multiple inheritance super().__init__() @@ -41,6 +44,7 @@ def __exit__(self, *args): self.stop() def stop(self): + """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() diff --git a/can/typechecking.py b/can/typechecking.py index 9ca9edfd0..bf02e5c85 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -1,5 +1,7 @@ """Types for mypy type-checking """ + +import os import typing import mypy_extensions @@ -18,3 +20,7 @@ # Used for the Abstract Base Class Channel = typing.Union[int, str] + +# Used by the IO module +FileLike = typing.IO[typing.Any] +PathLike = typing.Union[str, bytes, os.PathLike] From fb4e5298ee978f55ed2188ee61eff18230440968 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 7 Aug 2019 02:06:41 +0200 Subject: [PATCH 0310/1235] add io/logger.py --- .travis.yml | 1 + can/io/generic.py | 4 ++-- can/io/logger.py | 50 ++++++++++++++++++++++++--------------------- can/typechecking.py | 3 ++- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc226944c..51a5e67d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -102,6 +102,7 @@ jobs: can/typechecking.py can/util.py can/io/generic.py + can/io/logger.py - stage: linter name: "Formatting Checks" python: "3.7" diff --git a/can/io/generic.py b/can/io/generic.py index 8c86a1fd3..cb543c94d 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -7,7 +7,7 @@ from abc import ABCMeta from typing import Optional, Union, cast -from can.typechecking import FileLike, PathLike +from can.typechecking import FileLike, PathLike, AcceptedIOType class BaseIOHandler(metaclass=ABCMeta): @@ -20,7 +20,7 @@ class BaseIOHandler(metaclass=ABCMeta): was opened """ - def __init__(self, file: Optional[Union[FileLike, PathLike]], mode: str = "rt"): + def __init__(self, file: AcceptedIOType, mode: str = "rt"): """ :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 diff --git a/can/io/logger.py b/can/io/logger.py index edffe1c78..b0c7fc4da 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -5,6 +5,8 @@ """ import logging +import pathlib +import typing from ..listener import Listener from .generic import BaseIOHandler @@ -15,6 +17,8 @@ from .sqlite import SqliteWriter from .printer import Printer +from can.typechecking import PathLike + log = logging.getLogger("can.io.logger") @@ -28,36 +32,36 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` * .log :class:`can.CanutilsLogWriter` - * other: :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[PathLike], *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` """ - 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) + + suffix = pathlib.PurePath(filename).suffix + if suffix == ".asc": + return ASCWriter(filename, *args, **kwargs) + if suffix == ".blf": + return BLFWriter(filename, *args, **kwargs) + if suffix == ".csv": + return CSVWriter(filename, *args, **kwargs) + if suffix == ".db": + return SqliteWriter(filename, *args, **kwargs) + if suffix == ".log": + return CanutilsLogWriter(filename, *args, **kwargs) + + raise ValueError(f'unknown file type "{filename}"') diff --git a/can/typechecking.py b/can/typechecking.py index bf02e5c85..773e38cfa 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -23,4 +23,5 @@ # Used by the IO module FileLike = typing.IO[typing.Any] -PathLike = typing.Union[str, bytes, os.PathLike] +PathLike = typing.Union[str, os.PathLike[str]] +AcceptedIOType = typing.Optional[typing.Union[FileLike, PathLike]] From 3200713d722a1088f994268247446c44ccb89681 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 7 Aug 2019 02:09:33 +0200 Subject: [PATCH 0311/1235] fix problem with shpinx-build and typing --- can/io/generic.py | 8 ++++---- can/io/logger.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/can/io/generic.py b/can/io/generic.py index cb543c94d..07703074d 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -7,7 +7,7 @@ from abc import ABCMeta from typing import Optional, Union, cast -from can.typechecking import FileLike, PathLike, AcceptedIOType +import can.typechecking class BaseIOHandler(metaclass=ABCMeta): @@ -20,7 +20,7 @@ class BaseIOHandler(metaclass=ABCMeta): was opened """ - def __init__(self, file: AcceptedIOType, mode: str = "rt"): + def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt"): """ :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 @@ -29,10 +29,10 @@ def __init__(self, file: AcceptedIOType, mode: str = "rt"): """ if file is None or (hasattr(file, "read") and hasattr(file, "write")): # file is None or some file-like object - self.file = cast(Optional[FileLike], file) + self.file = cast(Optional[can.typechecking.FileLike], file) else: # file is some path-like object - self.file = open(cast(PathLike, file), mode) + self.file = open(cast(can.typechecking.PathLike, file), mode) # for multiple inheritance super().__init__() diff --git a/can/io/logger.py b/can/io/logger.py index b0c7fc4da..a39834342 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -17,7 +17,7 @@ from .sqlite import SqliteWriter from .printer import Printer -from can.typechecking import PathLike +import can.typechecking log = logging.getLogger("can.io.logger") @@ -43,7 +43,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method """ @staticmethod - def __new__(cls, filename: typing.Optional[PathLike], *args, **kwargs): + def __new__(cls, filename: typing.Optional[can.typechecking.PathLike], *args, **kwargs): """ :param filename: the filename/path of the file to write to, may be a path-like object or None to From dfa264a02213dc4674ef841e1db1eef98a95d4ff Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 7 Aug 2019 02:18:17 +0200 Subject: [PATCH 0312/1235] fix formatting --- can/io/generic.py | 10 +++++----- can/io/logger.py | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/can/io/generic.py b/can/io/generic.py index 07703074d..67588008d 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -5,7 +5,7 @@ """ from abc import ABCMeta -from typing import Optional, Union, cast +from typing import Optional, cast import can.typechecking @@ -20,7 +20,7 @@ class BaseIOHandler(metaclass=ABCMeta): was opened """ - def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "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 @@ -37,13 +37,13 @@ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt"): # 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() diff --git a/can/io/logger.py b/can/io/logger.py index a39834342..678bbf02e 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -8,6 +8,8 @@ import pathlib import typing +import can.typechecking + from ..listener import Listener from .generic import BaseIOHandler from .asc import ASCWriter @@ -17,8 +19,6 @@ from .sqlite import SqliteWriter from .printer import Printer -import can.typechecking - log = logging.getLogger("can.io.logger") @@ -43,7 +43,9 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method """ @staticmethod - def __new__(cls, filename: typing.Optional[can.typechecking.PathLike], *args, **kwargs): + def __new__( + cls, filename: typing.Optional[can.typechecking.PathLike], *args, **kwargs + ): """ :param filename: the filename/path of the file to write to, may be a path-like object or None to From 8f8c5e7c8cfae2d4ef019011e226a281a18e02cb Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Aug 2019 01:14:08 +0200 Subject: [PATCH 0313/1235] finalize IO typechecking, some import erros remain --- .travis.yml | 3 +-- can/io/generic.py | 13 +++++++++++ can/io/logger.py | 3 --- can/io/player.py | 57 +++++++++++++++++++++++++-------------------- can/typechecking.py | 2 +- 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/.travis.yml b/.travis.yml index 51a5e67d3..8ecb4abd5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,8 +101,7 @@ jobs: can/thread_safe_bus.py can/typechecking.py can/util.py - can/io/generic.py - can/io/logger.py + can/io/**.py - stage: linter name: "Formatting Checks" python: "3.7" diff --git a/can/io/generic.py b/can/io/generic.py index 67588008d..59157e762 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -7,6 +7,7 @@ from abc import ABCMeta from typing import Optional, cast +import can import can.typechecking @@ -48,3 +49,15 @@ def stop(self) -> None: 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 678bbf02e..1cd5ce579 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -4,7 +4,6 @@ See the :class:`Logger` class. """ -import logging import pathlib import typing @@ -19,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 """ diff --git a/can/io/player.py b/can/io/player.py index 3455fe97a..39a525ac0 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -6,8 +6,11 @@ in the recorded order an time intervals. """ +import pathlib from time import time, sleep -import logging +import typing + +import can.typechecking from .generic import BaseIOHandler from .asc import ASCReader @@ -16,8 +19,6 @@ from .csv import CSVReader from .sqlite import SqliteReader -log = logging.getLogger("can.io.player") - class LogReader(BaseIOHandler): """ @@ -45,45 +46,51 @@ 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 the file to read from """ - 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"): + suffix = pathlib.PurePath(filename).suffix + + if suffix == ".asc": + return ASCReader(filename) + if suffix == ".blf": + return BLFReader(filename) + if suffix == ".csv": + return CSVReader(filename) + if suffix == ".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) - ) + if suffix == ".log": + return CanutilsLogReader(filename) + + raise NotImplementedError(f"No read support for this log format: {filename}") -class MessageSync: +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/typechecking.py b/can/typechecking.py index 773e38cfa..1375a7a07 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -23,5 +23,5 @@ # Used by the IO module FileLike = typing.IO[typing.Any] -PathLike = typing.Union[str, os.PathLike[str]] +PathLike = typing.Union[str, "os.PathLike[str]"] AcceptedIOType = typing.Optional[typing.Union[FileLike, PathLike]] From b8a1130793378a6aab1703454addee89920376ef Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Aug 2019 01:54:36 +0200 Subject: [PATCH 0314/1235] cleanups --- can/io/generic.py | 4 +--- can/io/logger.py | 3 +++ can/io/player.py | 8 ++++---- can/io/printer.py | 7 +++++-- can/typechecking.py | 4 +++- test/listener_test.py | 13 +++++++------ test/logformats_test.py | 5 ++++- 7 files changed, 27 insertions(+), 17 deletions(-) diff --git a/can/io/generic.py b/can/io/generic.py index 59157e762..70d5e92d9 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -52,9 +52,7 @@ def stop(self) -> None: # pylint: disable=abstract-method,too-few-public-methods -class MessageWriter( - BaseIOHandler, can.Listener, metaclass=ABCMeta -): +class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): """The base class for all writers.""" diff --git a/can/io/logger.py b/can/io/logger.py index 1cd5ce579..33c2460c5 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -29,6 +29,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` * .log :class:`can.CanutilsLogWriter` + * .txt :class:`can.Printer` The **filename** may also be *None*, to fall back to :class:`can.Printer`. @@ -62,5 +63,7 @@ def __new__( return SqliteWriter(filename, *args, **kwargs) if suffix == ".log": return CanutilsLogWriter(filename, *args, **kwargs) + if suffix == ".txt": + return Printer(filename, *args, **kwargs) raise ValueError(f'unknown file type "{filename}"') diff --git a/can/io/player.py b/can/io/player.py index 39a525ac0..baee3cc4c 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -10,7 +10,7 @@ from time import time, sleep import typing -import can.typechecking +import can from .generic import BaseIOHandler from .asc import ASCReader @@ -46,7 +46,7 @@ class LogReader(BaseIOHandler): """ @staticmethod - def __new__(cls, filename: can.typechecking.PathLike, *args, **kwargs): + def __new__(cls, filename: "can.typechecking.PathLike", *args, **kwargs): """ :param filename: the filename/path the file to read from """ @@ -73,7 +73,7 @@ class MessageSync: # pylint: disable=too-few-public-methods def __init__( self, - messages: typing.Iterable[can.Message], + messages: typing.Iterable["can.Message"], timestamps: bool = True, gap: float = 0.0001, skip: float = 60.0, @@ -90,7 +90,7 @@ def __init__( self.gap = gap self.skip = skip - def __iter__(self) -> typing.Generator[can.Message, None, None]: + 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/typechecking.py b/can/typechecking.py index 1375a7a07..53396c883 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -1,9 +1,11 @@ """Types for mypy type-checking """ -import os import typing +if typing.TYPE_CHECKING: + import os + import mypy_extensions CanFilter = mypy_extensions.TypedDict( diff --git a/test/listener_test.py b/test/listener_test.py index c44acc7a6..be820892c 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 @@ -137,13 +135,16 @@ 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) + # test file extensions that should use a fallback + should_fail_with = ["", ".", ".some_unknown_extention_42"] + for supposed_fail in should_fail_with: + with self.assertRaises(ValueError): + with can.Logger(supposed_fail): # 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 = ( From 32d1ffcff4a7302320205ffd7998c2557d8466ee Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Aug 2019 02:02:09 +0200 Subject: [PATCH 0315/1235] fix problem in sphinx-build doc generation --- can/typechecking.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/can/typechecking.py b/can/typechecking.py index 53396c883..1583dd197 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -3,8 +3,7 @@ import typing -if typing.TYPE_CHECKING: - import os +import os import mypy_extensions From c01f49ab0c8739924dda681293995e1e062b0048 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Aug 2019 02:09:16 +0200 Subject: [PATCH 0316/1235] make linter happier --- can/io/logger.py | 1 + can/io/player.py | 2 +- can/typechecking.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 33c2460c5..1e0ef5830 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -40,6 +40,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method arguments are passed on to the returned instance. """ + # pylint: disable=too-many-return-statements @staticmethod def __new__( cls, filename: typing.Optional[can.typechecking.PathLike], *args, **kwargs diff --git a/can/io/player.py b/can/io/player.py index baee3cc4c..556abee44 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -10,7 +10,7 @@ from time import time, sleep import typing -import can +import can # pylint: disable=unused-import from .generic import BaseIOHandler from .asc import ASCReader diff --git a/can/typechecking.py b/can/typechecking.py index 1583dd197..df2a68d56 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -3,7 +3,7 @@ import typing -import os +import os # pylint: disable=unused-import import mypy_extensions From 219f482885d5d49979c98ebc0876104cde989e5b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Aug 2019 21:08:20 +0200 Subject: [PATCH 0317/1235] fix grammar in doc string Co-Authored-By: karl ding --- can/io/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/player.py b/can/io/player.py index 556abee44..4f07639ed 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -48,7 +48,7 @@ class LogReader(BaseIOHandler): @staticmethod def __new__(cls, filename: "can.typechecking.PathLike", *args, **kwargs): """ - :param filename: the filename/path the file to read from + :param filename: the filename/path of the file to read from """ suffix = pathlib.PurePath(filename).suffix From 56c6adb21f7c2833f0ec4015e070df183687fc95 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 11 Aug 2019 10:37:39 +0200 Subject: [PATCH 0318/1235] address review --- can/io/logger.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 1e0ef5830..f00bdb27f 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -53,18 +53,16 @@ def __new__( 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 - if suffix == ".asc": - return ASCWriter(filename, *args, **kwargs) - if suffix == ".blf": - return BLFWriter(filename, *args, **kwargs) - if suffix == ".csv": - return CSVWriter(filename, *args, **kwargs) - if suffix == ".db": - return SqliteWriter(filename, *args, **kwargs) - if suffix == ".log": - return CanutilsLogWriter(filename, *args, **kwargs) - if suffix == ".txt": - return Printer(filename, *args, **kwargs) - - raise ValueError(f'unknown file type "{filename}"') + try: + return lookup[suffix](filename, *args, **kwargs) + except KeyError: + raise ValueError(f'unknown file type "{suffix}"') From 329f5bccfc463bdf20341d0363a591595163b144 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 11 Aug 2019 10:47:44 +0200 Subject: [PATCH 0319/1235] address review --- can/io/logger.py | 1 - test/listener_test.py | 1 - 2 files changed, 2 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index f00bdb27f..4213edfc0 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -40,7 +40,6 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method arguments are passed on to the returned instance. """ - # pylint: disable=too-many-return-statements @staticmethod def __new__( cls, filename: typing.Optional[can.typechecking.PathLike], *args, **kwargs diff --git a/test/listener_test.py b/test/listener_test.py index be820892c..8014443d4 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -138,7 +138,6 @@ def test_filetype_to_instance(extension, klass): with can.Logger(None) as logger: self.assertIsInstance(logger, can.Printer) - # test file extensions that should use a fallback should_fail_with = ["", ".", ".some_unknown_extention_42"] for supposed_fail in should_fail_with: with self.assertRaises(ValueError): From 2ae51001634373c07477d46ee8ad9c01d5290e81 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 11 Aug 2019 10:54:17 +0200 Subject: [PATCH 0320/1235] address review on typing of PathLike --- can/typechecking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/typechecking.py b/can/typechecking.py index df2a68d56..e34b6b515 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -24,5 +24,5 @@ # Used by the IO module FileLike = typing.IO[typing.Any] -PathLike = typing.Union[str, "os.PathLike[str]"] -AcceptedIOType = typing.Optional[typing.Union[FileLike, PathLike]] +StringPathLike = typing.Union[str, "os.PathLike[str]"] +AcceptedIOType = typing.Optional[typing.Union[FileLike, StringPathLike]] From 2dda6238d6b1a9455e29d417eaea50300ddea68d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 12 Aug 2019 00:02:06 +0200 Subject: [PATCH 0321/1235] address review comments, cleanup tests --- can/io/generic.py | 2 +- can/io/logger.py | 5 +++-- can/io/player.py | 31 +++++++++++++++++-------------- can/typechecking.py | 3 ++- test/listener_test.py | 14 ++++++++------ 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/can/io/generic.py b/can/io/generic.py index 70d5e92d9..c64051b75 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -33,7 +33,7 @@ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> N self.file = cast(Optional[can.typechecking.FileLike], file) else: # file is some path-like object - self.file = open(cast(can.typechecking.PathLike, file), mode) + self.file = open(cast(can.typechecking.StringPathLike, file), mode) # for multiple inheritance super().__init__() diff --git a/can/io/logger.py b/can/io/logger.py index 4213edfc0..6336fc917 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -42,12 +42,13 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method @staticmethod def __new__( - cls, filename: typing.Optional[can.typechecking.PathLike], *args, **kwargs + cls, filename: typing.Optional[can.typechecking.StringPathLike], *args, **kwargs ): """ :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 is None: return Printer(*args, **kwargs) @@ -64,4 +65,4 @@ def __new__( try: return lookup[suffix](filename, *args, **kwargs) except KeyError: - raise ValueError(f'unknown file type "{suffix}"') + raise ValueError(f'No write support for this unknown log format "{suffix}"') diff --git a/can/io/player.py b/can/io/player.py index 4f07639ed..6e45e50ca 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -10,7 +10,8 @@ from time import time, sleep import typing -import can # pylint: disable=unused-import +if typing.TYPE_CHECKING: + import can from .generic import BaseIOHandler from .asc import ASCReader @@ -49,21 +50,22 @@ class LogReader(BaseIOHandler): def __new__(cls, filename: "can.typechecking.PathLike", *args, **kwargs): """ :param filename: the filename/path of the file to read from + :raises ValueError: if the filename's suffix is of an unknown file type """ suffix = pathlib.PurePath(filename).suffix - if suffix == ".asc": - return ASCReader(filename) - if suffix == ".blf": - return BLFReader(filename) - if suffix == ".csv": - return CSVReader(filename) - if suffix == ".db": - return SqliteReader(filename, *args, **kwargs) - if suffix == ".log": - return CanutilsLogReader(filename) - - raise NotImplementedError(f"No read support for this log format: {filename}") + 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}"') class MessageSync: # pylint: disable=too-few-public-methods @@ -81,7 +83,8 @@ def __init__( """Creates an new **MessageSync** instance. :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 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). """ diff --git a/can/typechecking.py b/can/typechecking.py index e34b6b515..b5b79d500 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -3,7 +3,8 @@ import typing -import os # pylint: disable=unused-import +if typing.TYPE_CHECKING: + import os import mypy_extensions diff --git a/test/listener_test.py b/test/listener_test.py index 8014443d4..00dad1b0a 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -111,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): @@ -138,10 +140,10 @@ def test_filetype_to_instance(extension, klass): with can.Logger(None) as logger: self.assertIsInstance(logger, can.Printer) - should_fail_with = ["", ".", ".some_unknown_extention_42"] - for supposed_fail in should_fail_with: + def testLoggerTypeResolutionUnsupportedFileTypes(self): + for should_fail_with in ["", ".", ".some_unknown_extention_42"]: with self.assertRaises(ValueError): - with can.Logger(supposed_fail): # make sure we close it anyways + with can.Logger(should_fail_with): # make sure we close it anyways pass def testBufferedListenerReceives(self): From a4154a49c75e5e5e8955584f36c6a12139e0320d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 16 Aug 2019 01:51:46 +0200 Subject: [PATCH 0322/1235] fix exception message if filetype is unknown --- can/io/logger.py | 4 +++- can/io/player.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 6336fc917..bc9d2c5f1 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -65,4 +65,6 @@ def __new__( try: return lookup[suffix](filename, *args, **kwargs) except KeyError: - raise ValueError(f'No write support for this unknown log format "{suffix}"') + 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 6e45e50ca..a4089fc32 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -65,7 +65,9 @@ def __new__(cls, filename: "can.typechecking.PathLike", *args, **kwargs): try: return lookup[suffix](filename, *args, **kwargs) except KeyError: - raise ValueError(f'No read support for this unknown log format "{suffix}"') + raise ValueError( + f'No read support for this unknown log format "{suffix}"' + ) from None class MessageSync: # pylint: disable=too-few-public-methods From 31c23bbd7c238e56f2c6a3b44c26d377a73ceb8c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 16 Aug 2019 02:19:23 +0200 Subject: [PATCH 0323/1235] Update Readme Fix typo + rearrange badges + add monthly downloads badge --- README.rst | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) 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 From fca982768ec708179fe933256dd5c3a4341af58a Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Mon, 19 Aug 2019 20:17:22 +0200 Subject: [PATCH 0324/1235] Fix bitrate setting in slcan --- can/interfaces/slcan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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__( From 224c803f3bfc2dcb2862e7e0d5994ecd065e21dd Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 08:18:57 +0200 Subject: [PATCH 0325/1235] add pylint checking for examples --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8ecb4abd5..11a31c10d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,6 +84,8 @@ jobs: # warnings to the .pylintrc-wip file to prevent them from being # re-introduced - pylint --rcfile=.pylintrc-wip can/ + # check examples + - pylint --rcfile=.pylintrc-wip examples/ # mypy checking - mypy --python-version=3.7 From 303ee65efe36914f0835b8db695a23839d10e8a1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 08:19:15 +0200 Subject: [PATCH 0326/1235] cleanup asyncio_demo.py --- examples/asyncio_demo.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index 534f47184..3663507f9 100644 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python + +""" +This example demonstrates how to use async IO with python-can. +""" + import asyncio import can @@ -8,7 +14,9 @@ def print_message(msg): async def main(): - can0 = can.Bus("vcan0", bustype="virtual", receive_own_messages=True) + """The main function that runs in the loop.""" + + bus = can.Bus("vcan0", bustype="virtual", receive_own_messages=True) reader = can.AsyncBufferedReader() logger = can.Logger("logfile.asc") @@ -19,9 +27,9 @@ async def main(): ] # Create Notifier with an explicit loop to use for scheduling of callbacks loop = asyncio.get_event_loop() - notifier = can.Notifier(can0, listeners, loop=loop) + notifier = can.Notifier(bus, listeners, loop=loop) # Start sending first message - can0.send(can.Message(arbitration_id=0)) + bus.send(can.Message(arbitration_id=0)) print("Bouncing 10 messages...") for _ in range(10): @@ -30,18 +38,24 @@ async def main(): # Delay response await asyncio.sleep(0.5) msg.arbitration_id += 1 - can0.send(msg) + bus.send(msg) # Wait for last message to arrive await reader.get_message() print("Done!") # Clean-up notifier.stop() - can0.shutdown() + bus.shutdown() + +if __name__ == "__main": + try: + # Get the default event loop + LOOP = asyncio.get_event_loop() + # Run until main coroutine finishes + LOOP.run_until_complete(main()) + finally: + LOOP.close() -# Get the default event loop -loop = asyncio.get_event_loop() -# Run until main coroutine finishes -loop.run_until_complete(main()) -loop.close() + # or on Python 3.7+ simply + #asyncio.run(main()) From 69d482ee6a46918fa003d1a35cb3f17bb483aa2c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 08:20:28 +0200 Subject: [PATCH 0327/1235] remove coding: utf-8 and __future__ statements from examples --- examples/cyclic.py | 3 --- examples/cyclic_multiple.py | 3 --- examples/receive_all.py | 2 -- examples/send_one.py | 3 --- examples/serial_com.py | 3 --- examples/simple_log_converter.py | 1 - examples/vcan_filtered.py | 1 - examples/virtual_can_demo.py | 1 - 8 files changed, 17 deletions(-) diff --git a/examples/cyclic.py b/examples/cyclic.py index 25c215dda..d0291c016 100755 --- a/examples/cyclic.py +++ b/examples/cyclic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This example exercises the periodic sending capabilities. @@ -10,8 +9,6 @@ """ -from __future__ import print_function - import logging import time diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py index ae32e7416..afe69bd29 100644 --- a/examples/cyclic_multiple.py +++ b/examples/cyclic_multiple.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This example exercises the periodic task's multiple message sending capabilities @@ -10,8 +9,6 @@ """ -from __future__ import print_function - import logging import time diff --git a/examples/receive_all.py b/examples/receive_all.py index ced8841bc..80e324957 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function - import can from can.bus import BusState diff --git a/examples/send_one.py b/examples/send_one.py index 9a3181e35..59ffe142c 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -1,12 +1,9 @@ #!/usr/bin/env python -# coding: utf-8 """ This example shows how sending a single message works. """ -from __future__ import print_function - import can diff --git a/examples/serial_com.py b/examples/serial_com.py index b0dba4fec..c5aaa2f4b 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This example sends every second a messages over the serial interface and also @@ -19,8 +18,6 @@ com0com: http://com0com.sourceforge.net/ """ -from __future__ import print_function - import time import threading diff --git a/examples/simple_log_converter.py b/examples/simple_log_converter.py index 782ac9b7c..0ede69c04 100755 --- a/examples/simple_log_converter.py +++ b/examples/simple_log_converter.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ Use this to convert .can/.asc files to .log files. diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index b351db09b..48a43c201 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This shows how message filtering works. diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index a4869d51d..048972309 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This demo creates multiple processes of producers to spam a socketcan bus. From d321bb41718dd8f7feccd8d0b0bca4321e45cb4c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 08:49:21 +0200 Subject: [PATCH 0328/1235] adapt .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 11a31c10d..4c1858288 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,9 +83,9 @@ jobs: # Slowly enable all pylint warnings by adding addressed classes of # warnings to the .pylintrc-wip file to prevent them from being # re-introduced - - pylint --rcfile=.pylintrc-wip can/ + - pylint --rcfile=.pylintrc-wip can/**.py # check examples - - pylint --rcfile=.pylintrc-wip examples/ + - pylint --rcfile=.pylintrc-wip examples/**.py # mypy checking - mypy --python-version=3.7 From 725bbd05850a2e0c4b7856e167d6a52c25b236e5 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 08:50:03 +0200 Subject: [PATCH 0329/1235] cleanup the examples; they now pass the linter and formatter and always close the bus by using a context manager --- examples/asyncio_demo.py | 2 +- examples/cyclic.py | 24 ++++++------ examples/cyclic_multiple.py | 12 ++---- examples/receive_all.py | 31 ++++++++++------ examples/send_one.py | 37 +++++++++--------- examples/serial_com.py | 64 ++++++++++++++++++-------------- examples/simple_log_converter.py | 22 +++++++---- examples/vcan_filtered.py | 33 ++++++++++------ examples/virtual_can_demo.py | 9 ++--- 9 files changed, 131 insertions(+), 103 deletions(-) diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index 3663507f9..b2c84b9d1 100644 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -58,4 +58,4 @@ async def main(): LOOP.close() # or on Python 3.7+ simply - #asyncio.run(main()) + # asyncio.run(main()) diff --git a/examples/cyclic.py b/examples/cyclic.py index d0291c016..573465d78 100755 --- a/examples/cyclic.py +++ b/examples/cyclic.py @@ -34,13 +34,14 @@ def simple_periodic_send(bus): def limited_periodic_send(bus): + """Send using LimitedDurationCyclicSendTaskABC.""" print("Starting to send a message every 200ms for 1s") msg = can.Message( arbitration_id=0x12345678, data=[0, 0, 0, 0, 0, 0], is_extended_id=True ) task = bus.send_periodic(msg, 0.20, 1, store_task=False) if not isinstance(task, can.LimitedDurationCyclicSendTaskABC): - print("This interface doesn't seem to support a ") + print("This interface doesn't seem to support LimitedDurationCyclicSendTaskABC") task.stop() return @@ -53,7 +54,8 @@ def limited_periodic_send(bus): def test_periodic_send_with_modifying_data(bus): - print("Starting to send a message every 200ms. Initial data is ones") + """Send using ModifiableCyclicTaskABC.""" + print("Starting to send a message every 200ms. Initial data is four consecutive 1s") msg = can.Message(arbitration_id=0x0CF02200, data=[1, 1, 1, 1]) task = bus.send_periodic(msg, 0.20) if not isinstance(task, can.ModifiableCyclicTaskABC): @@ -106,19 +108,13 @@ def test_periodic_send_with_modifying_data(bus): # print("done") -if __name__ == "__main__": - +def main(): + """Test different cyclic sending tasks.""" reset_msg = can.Message( arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False ) - for interface, channel in [ - ("socketcan", "vcan0"), - # ('ixxat', 0) - ]: - print("Carrying out cyclic tests with {} interface".format(interface)) - - bus = can.Bus(interface=interface, channel=channel, bitrate=500000) + with can.Bus(interface="virtual") as bus: bus.send(reset_msg) simple_periodic_send(bus) @@ -133,6 +129,8 @@ def test_periodic_send_with_modifying_data(bus): # can.rc['interface'] = interface # test_dual_rate_periodic_send() - bus.shutdown() - time.sleep(2) + + +if __name__ == "__main__": + main() diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py index afe69bd29..43dc0cd17 100644 --- a/examples/cyclic_multiple.py +++ b/examples/cyclic_multiple.py @@ -131,14 +131,10 @@ def cyclic_multiple_send_modify(bus): if __name__ == "__main__": for interface, channel in [("socketcan", "vcan0")]: - print("Carrying out cyclic multiple tests with {} interface".format(interface)) + print(f"Carrying out cyclic multiple tests with {interface} interface") - bus = can.Bus(interface=interface, channel=channel, bitrate=500000) - - cyclic_multiple_send(bus) - - cyclic_multiple_send_modify(bus) - - bus.shutdown() + with can.Bus(interface=interface, channel=channel, bitrate=500000) as BUS: + cyclic_multiple_send(BUS) + cyclic_multiple_send_modify(BUS) time.sleep(2) diff --git a/examples/receive_all.py b/examples/receive_all.py index 80e324957..7ff532079 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -1,24 +1,33 @@ #!/usr/bin/env python +""" +Shows how the receive messages via polling. +""" + import can from can.bus import BusState def receive_all(): + """Receives all messages and prints them to the console until Ctrl+C is pressed.""" + + with can.interface.Bus( + bustype="pcan", channel="PCAN_USBBUS1", bitrate=250000 + ) as bus: + # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) - bus = can.interface.Bus(bustype="pcan", channel="PCAN_USBBUS1", bitrate=250000) - # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) - # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + # set to read-only, only supported on some interfaces + bus.state = BusState.PASSIVE - bus.state = BusState.ACTIVE # or BusState.PASSIVE + try: + while True: + msg = bus.recv(1) + if msg is not None: + print(msg) - try: - while True: - msg = bus.recv(1) - if msg is not None: - print(msg) - except KeyboardInterrupt: - pass + except KeyboardInterrupt: + pass # exit normally if __name__ == "__main__": diff --git a/examples/send_one.py b/examples/send_one.py index 59ffe142c..49a4f1ee1 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -8,27 +8,28 @@ def send_one(): + """Sends a single message.""" # this uses the default configuration (for example from the config file) # see https://python-can.readthedocs.io/en/stable/configuration.html - bus = can.interface.Bus() - - # Using specific buses works similar: - # bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000) - # bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) - # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) - # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) - # ... - - msg = can.Message( - arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True - ) - - try: - bus.send(msg) - print("Message sent on {}".format(bus.channel_info)) - except can.CanError: - print("Message NOT sent") + with can.interface.Bus() as bus: + + # Using specific buses works similar: + # bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000) + # bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) + # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + # ... + + msg = can.Message( + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True + ) + + try: + bus.send(msg) + print(f"Message sent on {bus.channel_info}") + except can.CanError: + print("Message NOT sent") if __name__ == "__main__": diff --git a/examples/serial_com.py b/examples/serial_com.py index c5aaa2f4b..60aeec4ce 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -This example sends every second a messages over the serial interface and also +This example sends every second a messages over the serial interface and also receives incoming messages. python3 -m examples.serial_com @@ -25,17 +25,19 @@ def send_cyclic(bus, msg, stop_event): + """The loop for sending.""" print("Start to send a message every 1s") start_time = time.time() while not stop_event.is_set(): msg.timestamp = time.time() - start_time bus.send(msg) - print("tx: {}".format(tx_msg)) + print(f"tx: {msg}") time.sleep(1) print("Stopped sending messages") def receive(bus, stop_event): + """The loop for receiving.""" print("Start receiving messages") while not stop_event.is_set(): rx_msg = bus.recv(1) @@ -44,30 +46,36 @@ def receive(bus, stop_event): print("Stopped receiving messages") -if __name__ == "__main__": - server = can.interface.Bus(bustype="serial", channel="/dev/ttyS10") - client = can.interface.Bus(bustype="serial", channel="/dev/ttyS11") - - tx_msg = can.Message( - arbitration_id=0x01, data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] - ) - - # Thread for sending and receiving messages - stop_event = threading.Event() - t_send_cyclic = threading.Thread( - target=send_cyclic, args=(server, tx_msg, stop_event) - ) - t_receive = threading.Thread(target=receive, args=(client, stop_event)) - t_receive.start() - t_send_cyclic.start() - - try: - while True: - pass - except KeyboardInterrupt: - pass - - stop_event.set() - server.shutdown() - client.shutdown() +def main(): + """Controles the sender and receiver.""" + with can.interface.Bus(bustype="serial", channel="/dev/ttyS10") as server: + with can.interface.Bus(bustype="serial", channel="/dev/ttyS11") as client: + + tx_msg = can.Message( + arbitration_id=0x01, + data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + ) + + # Thread for sending and receiving messages + stop_event = threading.Event() + t_send_cyclic = threading.Thread( + target=send_cyclic, args=(server, tx_msg, stop_event) + ) + t_receive = threading.Thread(target=receive, args=(client, stop_event)) + t_receive.start() + t_send_cyclic.start() + + try: + while True: + time.sleep(0) # yield + except KeyboardInterrupt: + pass # exit normally + + stop_event.set() + time.sleep(0.5) + print("Stopped script") + + +if __name__ == "__main__": + main() diff --git a/examples/simple_log_converter.py b/examples/simple_log_converter.py index 0ede69c04..f01546375 100755 --- a/examples/simple_log_converter.py +++ b/examples/simple_log_converter.py @@ -2,17 +2,25 @@ """ Use this to convert .can/.asc files to .log files. +Can be easily adapted for all sorts of files. -Usage: simpleLogConvert.py sourceLog.asc targetLog.log +Usage: python3 simple_log_convert.py sourceLog.asc targetLog.log """ import sys -import can.io.logger -import can.io.player +import can -reader = can.io.player.LogReader(sys.argv[1]) -writer = can.io.logger.Logger(sys.argv[2]) -for msg in reader: - writer.on_message_received(msg) +def main(): + """The transcoder""" + + with can.LogReader(sys.argv[1]) as reader: + with can.Logger(sys.argv[2]) as writer: + + for msg in reader: + writer.on_message_received(msg) + + +if __name__ == "__main__": + main() diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index 48a43c201..fa6c71547 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -8,16 +8,25 @@ import can + +def main(): + """Send some messages to itself and apply filtering.""" + with can.Bus(bustype="virtual", receive_own_messages=True) as bus: + + can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}] + bus.set_filters(can_filters) + + # print all incoming messages, wich includes the ones sent, + # since we set receive_own_messages to True + # assign to some variable so it does not garbage collected + notifier = can.Notifier(bus, [can.Printer()]) # pylint: disable=unused-variable + + bus.send(can.Message(arbitration_id=1, is_extended_id=True)) + bus.send(can.Message(arbitration_id=2, is_extended_id=True)) + bus.send(can.Message(arbitration_id=1, is_extended_id=False)) + + time.sleep(1.0) + + if __name__ == "__main__": - bus = can.interface.Bus( - bustype="socketcan", channel="vcan0", receive_own_messages=True - ) - - can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}] - bus.set_filters(can_filters) - notifier = can.Notifier(bus, [can.Printer()]) - bus.send(can.Message(arbitration_id=1, is_extended_id=True)) - bus.send(can.Message(arbitration_id=2, is_extended_id=True)) - bus.send(can.Message(arbitration_id=1, is_extended_id=False)) - - time.sleep(10) + main() diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index 048972309..6f61af7b9 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -10,23 +10,22 @@ import can -def producer(id, message_count=16): +def producer(thread_id, message_count=16): """Spam the bus with messages including the data id. :param int id: the id of the thread/process """ - with can.Bus(bustype="socketcan", channel="vcan0") as bus: for i in range(message_count): msg = can.Message( - arbitration_id=0x0CF02200 + id, data=[id, i, 0, 1, 3, 1, 4, 1] + arbitration_id=0x0CF02200 + id, data=[thread_id, i, 0, 1, 3, 1, 4, 1] ) bus.send(msg) sleep(1.0) - print("Producer #{} finished sending {} messages".format(id, message_count)) + print(f"Producer #{thread_id} finished sending {message_count} messages") if __name__ == "__main__": - with ProcessPoolExecutor(max_workers=4) as executor: + with ProcessPoolExecutor() as executor: executor.map(producer, range(5)) From 65f46cd703b596ee67d58fdc0999c607cf4bc75d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 09:10:13 +0200 Subject: [PATCH 0330/1235] also enable mypy on the examples; this required two tiny changes somehwere else --- .travis.yml | 2 ++ can/viewer.py | 8 ++++---- examples/cyclic_multiple.py | 2 +- examples/virtual_can_demo.py | 9 +++++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4c1858288..5920f5491 100644 --- a/.travis.yml +++ b/.travis.yml @@ -104,6 +104,8 @@ jobs: can/typechecking.py can/util.py can/io/**.py + scripts/**.py + examples/**.py - stage: linter name: "Formatting Checks" python: "3.7" diff --git a/can/viewer.py b/can/viewer.py index 707192526..95ad953f5 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -43,7 +43,7 @@ logger.warning( "You won't be able to use the viewer program without " "curses installed!" ) - curses = None + curses = None # type: ignore class CanViewer: @@ -153,7 +153,7 @@ def unpack_data( value = cmd_to_struct[key] if isinstance(value, tuple): # The struct is given as the fist argument - struct_t = value[0] # type: struct.Struct + struct_t: struct.Struct = value[0] # The conversion from raw values to SI-units are given in the rest of the tuple values = [ @@ -162,8 +162,8 @@ def unpack_data( ] else: # No conversion from SI-units is needed - struct_t = value # type: struct.Struct - values = list(struct_t.unpack(data)) + as_struct_t: struct.Struct = value + values = list(as_struct_t.unpack(data)) return values else: diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py index 43dc0cd17..fe1e3d060 100644 --- a/examples/cyclic_multiple.py +++ b/examples/cyclic_multiple.py @@ -133,7 +133,7 @@ def cyclic_multiple_send_modify(bus): for interface, channel in [("socketcan", "vcan0")]: print(f"Carrying out cyclic multiple tests with {interface} interface") - with can.Bus(interface=interface, channel=channel, bitrate=500000) as BUS: + with can.Bus(interface=interface, channel=channel, bitrate=500000) as BUS: # type: ignore cyclic_multiple_send(BUS) cyclic_multiple_send_modify(BUS) diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index 6f61af7b9..711cb2cb7 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -10,15 +10,16 @@ import can -def producer(thread_id, message_count=16): +def producer(thread_id: int, message_count: int = 16): """Spam the bus with messages including the data id. - :param int id: the id of the thread/process + :param thread_id: the id of the thread/process + :param message_count: the number of messages that shall be send """ - with can.Bus(bustype="socketcan", channel="vcan0") as bus: + with can.Bus(bustype="socketcan", channel="vcan0") as bus: # type: ignore for i in range(message_count): msg = can.Message( - arbitration_id=0x0CF02200 + id, data=[thread_id, i, 0, 1, 3, 1, 4, 1] + arbitration_id=0x0CF02200 + thread_id, data=[thread_id, i, 0, 1, 3, 1, 4, 1] ) bus.send(msg) sleep(1.0) From 80747e3fee92fa61771167c2f865d8f88b01342f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 09:32:41 +0200 Subject: [PATCH 0331/1235] reformat files --- examples/cyclic_multiple.py | 4 +++- examples/virtual_can_demo.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py index fe1e3d060..c34b75d62 100644 --- a/examples/cyclic_multiple.py +++ b/examples/cyclic_multiple.py @@ -133,7 +133,9 @@ def cyclic_multiple_send_modify(bus): for interface, channel in [("socketcan", "vcan0")]: print(f"Carrying out cyclic multiple tests with {interface} interface") - with can.Bus(interface=interface, channel=channel, bitrate=500000) as BUS: # type: ignore + with can.Bus( + interface=interface, channel=channel, bitrate=500000 + ) as BUS: # type: ignore cyclic_multiple_send(BUS) cyclic_multiple_send_modify(BUS) diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index 711cb2cb7..b3fdefc09 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -19,7 +19,8 @@ def producer(thread_id: int, message_count: int = 16): with can.Bus(bustype="socketcan", channel="vcan0") as bus: # type: ignore for i in range(message_count): msg = can.Message( - arbitration_id=0x0CF02200 + thread_id, data=[thread_id, i, 0, 1, 3, 1, 4, 1] + arbitration_id=0x0CF02200 + thread_id, + data=[thread_id, i, 0, 1, 3, 1, 4, 1], ) bus.send(msg) sleep(1.0) From 1d05ef7c76a929df9e563e1cb55cc1dee5d362c8 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 09:47:09 +0200 Subject: [PATCH 0332/1235] fix a tiny typechecker regression introduced by the formatter --- examples/cyclic_multiple.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py index c34b75d62..64f0862d7 100644 --- a/examples/cyclic_multiple.py +++ b/examples/cyclic_multiple.py @@ -133,9 +133,9 @@ def cyclic_multiple_send_modify(bus): for interface, channel in [("socketcan", "vcan0")]: print(f"Carrying out cyclic multiple tests with {interface} interface") - with can.Bus( + with can.Bus( # type: ignore interface=interface, channel=channel, bitrate=500000 - ) as BUS: # type: ignore + ) as BUS: cyclic_multiple_send(BUS) cyclic_multiple_send_modify(BUS) From 1ac5510f7e18b7c5c9d87576f4aeda906ef7036a Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Sat, 17 Aug 2019 12:40:52 +0200 Subject: [PATCH 0333/1235] Ignore error frames in can.player by default Fixes #683 --- can/player.py | 10 ++++++++++ 1 file changed, 10 insertions(+) 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) From 63572da17f699e83410cc0632d87eba99d93894e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 07:44:35 +0200 Subject: [PATCH 0334/1235] add setup.py to linter check --- .travis.yml | 1 + setup.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8ecb4abd5..0ba635cc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,6 +84,7 @@ jobs: # warnings to the .pylintrc-wip file to prevent them from being # re-introduced - pylint --rcfile=.pylintrc-wip can/ + - pylint --rcfile=.pylintrc setup.py # mypy checking - mypy --python-version=3.7 diff --git a/setup.py b/setup.py index 2b1bf2296..8170f67fa 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,8 @@ python-can requires the setuptools package to be installed. """ +# pylint: disable=invalid-name + from __future__ import absolute_import from os import listdir From 1947e562c6c00bc589c58fa3617c2049f730618c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 07:50:56 +0200 Subject: [PATCH 0335/1235] removing coding: utf-8 directive since that is now the defualt in Python 3 --- can/__init__.py | 1 - can/broadcastmanager.py | 1 - can/bus.py | 1 - can/ctypesutil.py | 1 - can/interface.py | 1 - can/interfaces/__init__.py | 1 - can/interfaces/canalystii.py | 1 - can/interfaces/ics_neovi/__init__.py | 1 - can/interfaces/ics_neovi/neovi_bus.py | 1 - can/interfaces/iscan.py | 1 - can/interfaces/ixxat/__init__.py | 1 - can/interfaces/ixxat/canlib.py | 1 - can/interfaces/ixxat/constants.py | 1 - can/interfaces/ixxat/exceptions.py | 1 - can/interfaces/ixxat/structures.py | 1 - can/interfaces/kvaser/__init__.py | 1 - can/interfaces/kvaser/canlib.py | 1 - can/interfaces/kvaser/constants.py | 1 - can/interfaces/kvaser/structures.py | 1 - can/interfaces/nican.py | 1 - can/interfaces/pcan/__init__.py | 1 - can/interfaces/pcan/basic.py | 1 - can/interfaces/pcan/pcan.py | 1 - can/interfaces/seeedstudio/__init__.py | 1 - can/interfaces/seeedstudio/seeedstudio.py | 1 - can/interfaces/serial/__init__.py | 1 - can/interfaces/serial/serial_can.py | 1 - can/interfaces/slcan.py | 1 - can/interfaces/socketcan/__init__.py | 1 - can/interfaces/socketcan/constants.py | 1 - can/interfaces/socketcan/socketcan.py | 1 - can/interfaces/socketcan/utils.py | 1 - can/interfaces/systec/__init__.py | 1 - can/interfaces/systec/constants.py | 1 - can/interfaces/systec/exceptions.py | 1 - can/interfaces/systec/structures.py | 1 - can/interfaces/systec/ucan.py | 1 - can/interfaces/systec/ucanbus.py | 1 - can/interfaces/usb2can/__init__.py | 1 - can/interfaces/usb2can/serial_selector.py | 1 - can/interfaces/usb2can/usb2canInterface.py | 1 - can/interfaces/usb2can/usb2canabstractionlayer.py | 1 - can/interfaces/vector/__init__.py | 1 - can/interfaces/vector/canlib.py | 1 - can/interfaces/vector/exceptions.py | 1 - can/interfaces/vector/xlclass.py | 1 - can/interfaces/vector/xldefine.py | 1 - can/interfaces/vector/xldriver.py | 1 - can/interfaces/virtual.py | 1 - can/io/__init__.py | 1 - can/io/asc.py | 1 - can/io/blf.py | 1 - can/io/canutils.py | 1 - can/io/csv.py | 1 - can/io/generic.py | 1 - can/io/logger.py | 1 - can/io/player.py | 1 - can/io/printer.py | 1 - can/io/sqlite.py | 1 - can/listener.py | 1 - can/logger.py | 1 - can/message.py | 1 - can/notifier.py | 1 - can/player.py | 1 - can/thread_safe_bus.py | 1 - can/util.py | 1 - can/viewer.py | 1 - 67 files changed, 67 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 3c1ac8d75..ec7a8caf4 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ ``can`` is an object-orient Controller Area Network (CAN) interface module. diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 733693802..118880a66 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Exposes several methods for transmitting cyclic messages. diff --git a/can/bus.py b/can/bus.py index e22bf6157..f1d9133ad 100644 --- a/can/bus.py +++ b/can/bus.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Contains the ABC bus implementation and its documentation. diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 8dca8dc14..dcadff1aa 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module contains common `ctypes` utils. diff --git a/can/interface.py b/can/interface.py index 9e32d9ca3..bd8a5c386 100644 --- a/can/interface.py +++ b/can/interface.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module contains the base implementation of :class:`can.BusABC` as well diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 174b7f7ab..792517500 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Interfaces contain low level implementations that interact with CAN hardware. diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 1134ffe51..8cfa97ca1 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,4 +1,3 @@ -# coding: utf-8 from ctypes import * import logging diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py index 4426b1585..96af50d7e 100644 --- a/can/interfaces/ics_neovi/__init__.py +++ b/can/interfaces/ics_neovi/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 176747ad2..9eec48f45 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ ICS NeoVi interface module. diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index e0774dded..360507976 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH. diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index aef26b729..8fb17844a 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 13c8ef779..68f1b0aba 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index 46daccf8d..9608bb2bf 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index d89a98334..5b4347666 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index 881d47a13..df1ebd8a6 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/kvaser/__init__.py b/can/interfaces/kvaser/__init__.py index 5cbe63386..191179ab3 100644 --- a/can/interfaces/kvaser/__init__.py +++ b/can/interfaces/kvaser/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 89a05af1e..06c27e903 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Contains Python equivalents of the function and constant diff --git a/can/interfaces/kvaser/constants.py b/can/interfaces/kvaser/constants.py index bc31381c6..d1a7ba610 100644 --- a/can/interfaces/kvaser/constants.py +++ b/can/interfaces/kvaser/constants.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Contains Python equivalents of the function and constant diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index 5b6d416d8..e2dd6a6a9 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Contains Python equivalents of the structures in CANLIB's canlib.h, diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 2d5abf0d0..90d396a5c 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ NI-CAN interface module. diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py index ceba250b5..8ae860a85 100644 --- a/can/interfaces/pcan/__init__.py +++ b/can/interfaces/pcan/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index eb29f0e16..3fe5468ea 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ PCAN-Basic API diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 26da9ded9..9a1dae2b2 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Enable basic CAN over a PCAN USB device. diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py index 507ac873e..a7a69c097 100644 --- a/can/interfaces/seeedstudio/__init__.py +++ b/can/interfaces/seeedstudio/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index eebd07753..d9dfe448f 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ To Support the Seeed USB-Can analyzer interface. The device will appear diff --git a/can/interfaces/serial/__init__.py b/can/interfaces/serial/__init__.py index dced63b0f..21553a0da 100644 --- a/can/interfaces/serial/__init__.py +++ b/can/interfaces/serial/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index ba397f7bf..063b09257 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ A text based interface. For example use over serial ports like diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 710c23185..0a1931c16 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Interface for slcan compatible interfaces (win32/linux). diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index 06a9d9959..a39cc1ef7 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ See: https://www.kernel.org/doc/Documentation/networking/can.txt diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index 98bbc39d1..ae6c01519 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Defines shared CAN constants. diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index a0ee5c595..0e9a733cb 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ The main module of the socketcan interface containing most user-facing classes and methods diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 5733443a1..f93ec1420 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Defines common socketcan functions. diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py index ed8eb8eb7..f211bc4ef 100644 --- a/can/interfaces/systec/__init__.py +++ b/can/interfaces/systec/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/systec/constants.py b/can/interfaces/systec/constants.py index 648ae2edc..a35e2ccb5 100644 --- a/can/interfaces/systec/constants.py +++ b/can/interfaces/systec/constants.py @@ -1,4 +1,3 @@ -# coding: utf-8 from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index 49a974cac..fd591d702 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -1,4 +1,3 @@ -# coding: utf-8 from .constants import ReturnCode from can import CanError diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index 9fd542e04..b13ee31d9 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -1,4 +1,3 @@ -# coding: utf-8 from ctypes import Structure, POINTER, sizeof from ctypes import ( diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index 1b08c51a0..16ff6245b 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -1,4 +1,3 @@ -# coding: utf-8 import logging import sys diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 29a76f468..3c65143b3 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -1,4 +1,3 @@ -# coding: utf-8 import logging from threading import Event diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index 623af5bc3..4665c9fb1 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index f124df196..3b0f225b5 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 81ba027ce..29316b771 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This interface is for Windows only, otherwise use socketCAN. diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index 0aaf1b4f2..2811ba235 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This wrapper is for windows or direct access via CANAL API. diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index dac47be4a..19bb42afd 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 7c8d9c7c3..cc674288b 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 8e4de1aff..fb34517c6 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ """ diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py index 90fc611bd..49eb410d0 100644 --- a/can/interfaces/vector/xlclass.py +++ b/can/interfaces/vector/xlclass.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Definition of data types and structures for vxlapi. diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index fcf041683..7cc929052 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Definition of constants for vxlapi. diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index b413e47fb..fde7cca56 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index b153150ee..436988dab 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module implements an OS and hardware independent diff --git a/can/io/__init__.py b/can/io/__init__.py index 3797d4b5d..f4c1b0359 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Read and write CAN bus messages using a range of Readers diff --git a/can/io/asc.py b/can/io/asc.py index 656ac4fbe..41f947c57 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Contains handling of ASC logging files. diff --git a/can/io/blf.py b/can/io/blf.py index 2ac553717..21252f38a 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Implements support for BLF (Binary Logging Format) which is a proprietary diff --git a/can/io/canutils.py b/can/io/canutils.py index a151b56e1..cd56ff32e 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module works with CAN data in ASCII log files (*.log). diff --git a/can/io/csv.py b/can/io/csv.py index af8fee6d8..a3793bdb6 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module contains handling for CSV (comma separated values) files. diff --git a/can/io/generic.py b/can/io/generic.py index c64051b75..10f7fc6a9 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Contains a generic class for file IO. diff --git a/can/io/logger.py b/can/io/logger.py index bc9d2c5f1..6b63f12f5 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ See the :class:`Logger` class. diff --git a/can/io/player.py b/can/io/player.py index a4089fc32..79603455e 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module contains the generic :class:`LogReader` as diff --git a/can/io/printer.py b/can/io/printer.py index d363e6917..57e34db3f 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This Listener simply prints to stdout / the terminal or a file. diff --git a/can/io/sqlite.py b/can/io/sqlite.py index b9e5c2673..abeab6090 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Implements an SQL database writer and reader for storing CAN messages. diff --git a/can/listener.py b/can/listener.py index c33fec70a..44723bb99 100644 --- a/can/listener.py +++ b/can/listener.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module contains the implementation of `can.Listener` and some readers. diff --git a/can/logger.py b/can/logger.py index 00f667979..0a9055259 100644 --- a/can/logger.py +++ b/can/logger.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ logger.py logs CAN traffic to the terminal and to a file on disk. diff --git a/can/message.py b/can/message.py index d1c7c9366..385d2244b 100644 --- a/can/message.py +++ b/can/message.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module contains the implementation of :class:`can.Message`. diff --git a/can/notifier.py b/can/notifier.py index 679af384d..108a645b4 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ This module contains the implementation of :class:`~can.Notifier`. diff --git a/can/player.py b/can/player.py index b1e1c5612..86bd0afe6 100644 --- a/can/player.py +++ b/can/player.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Replays CAN traffic saved with can.logger back diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 9f6346fb8..b31e48407 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -1,4 +1,3 @@ -# coding: utf-8 from threading import RLock diff --git a/can/util.py b/can/util.py index 51a3051bd..473057328 100644 --- a/can/util.py +++ b/can/util.py @@ -1,4 +1,3 @@ -# coding: utf-8 """ Utilities and configuration file parsing. diff --git a/can/viewer.py b/can/viewer.py index 707192526..9f3ac361a 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright (C) 2018 Kristian Sloth Lauszus. # From 9e0520bd98bac6e62f0fba3ee88de800c83103aa Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 07:52:15 +0200 Subject: [PATCH 0336/1235] removing coding: utf-8 directive also from setup.py --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 8170f67fa..d7d2feca6 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ python-can requires the setuptools package to be installed. From 8f1ba2322eb401eab94cab7ae6e8f1128aa7069a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 08:00:44 +0200 Subject: [PATCH 0337/1235] add doc/conf.py to pylint checking, reformat files --- .travis.yml | 5 +++- can/__init__.py | 1 - can/broadcastmanager.py | 1 - can/bus.py | 1 - can/ctypesutil.py | 1 - can/interface.py | 1 - can/interfaces/__init__.py | 1 - can/interfaces/canalystii.py | 1 - can/interfaces/ics_neovi/__init__.py | 1 - can/interfaces/ics_neovi/neovi_bus.py | 1 - can/interfaces/iscan.py | 1 - can/interfaces/ixxat/__init__.py | 1 - can/interfaces/ixxat/canlib.py | 1 - can/interfaces/ixxat/constants.py | 1 - can/interfaces/ixxat/exceptions.py | 1 - can/interfaces/ixxat/structures.py | 1 - can/interfaces/kvaser/__init__.py | 1 - can/interfaces/kvaser/canlib.py | 1 - can/interfaces/kvaser/constants.py | 1 - can/interfaces/kvaser/structures.py | 1 - can/interfaces/nican.py | 1 - can/interfaces/pcan/__init__.py | 1 - can/interfaces/pcan/basic.py | 1 - can/interfaces/pcan/pcan.py | 1 - can/interfaces/seeedstudio/__init__.py | 1 - can/interfaces/seeedstudio/seeedstudio.py | 1 - can/interfaces/serial/__init__.py | 1 - can/interfaces/serial/serial_can.py | 1 - can/interfaces/slcan.py | 1 - can/interfaces/socketcan/__init__.py | 1 - can/interfaces/socketcan/constants.py | 1 - can/interfaces/socketcan/socketcan.py | 1 - can/interfaces/socketcan/utils.py | 1 - can/interfaces/systec/__init__.py | 1 - can/interfaces/systec/constants.py | 1 - can/interfaces/systec/exceptions.py | 1 - can/interfaces/systec/structures.py | 1 - can/interfaces/systec/ucan.py | 1 - can/interfaces/systec/ucanbus.py | 1 - can/interfaces/usb2can/__init__.py | 1 - can/interfaces/usb2can/serial_selector.py | 1 - can/interfaces/usb2can/usb2canInterface.py | 1 - .../usb2can/usb2canabstractionlayer.py | 1 - can/interfaces/vector/__init__.py | 1 - can/interfaces/vector/canlib.py | 1 - can/interfaces/vector/exceptions.py | 1 - can/interfaces/vector/xlclass.py | 1 - can/interfaces/vector/xldefine.py | 1 - can/interfaces/vector/xldriver.py | 1 - can/interfaces/virtual.py | 1 - can/io/__init__.py | 1 - can/io/asc.py | 1 - can/io/blf.py | 1 - can/io/canutils.py | 1 - can/io/csv.py | 1 - can/io/generic.py | 1 - can/io/logger.py | 1 - can/io/player.py | 1 - can/io/printer.py | 1 - can/io/sqlite.py | 1 - can/listener.py | 1 - can/logger.py | 1 - can/message.py | 1 - can/notifier.py | 1 - can/player.py | 1 - can/thread_safe_bus.py | 1 - can/util.py | 1 - can/viewer.py | 1 - doc/conf.py | 24 ++++++++++--------- 69 files changed, 17 insertions(+), 79 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0ba635cc2..b904ebb42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,7 +84,10 @@ jobs: # warnings to the .pylintrc-wip file to prevent them from being # re-introduced - pylint --rcfile=.pylintrc-wip can/ - - pylint --rcfile=.pylintrc setup.py + # check setup.py + - pylint --rcfile=.pylintrc *.py + # check doc/conf.py + - pylint --rcfile=.pylintrc doc/**.py # mypy checking - mypy --python-version=3.7 diff --git a/can/__init__.py b/can/__init__.py index ec7a8caf4..457546307 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -1,4 +1,3 @@ - """ ``can`` is an object-orient Controller Area Network (CAN) interface module. """ diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 118880a66..52f0550f2 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -1,4 +1,3 @@ - """ Exposes several methods for transmitting cyclic messages. diff --git a/can/bus.py b/can/bus.py index f1d9133ad..fb6410cf5 100644 --- a/can/bus.py +++ b/can/bus.py @@ -1,4 +1,3 @@ - """ Contains the ABC bus implementation and its documentation. """ diff --git a/can/ctypesutil.py b/can/ctypesutil.py index dcadff1aa..cba8e797b 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -1,4 +1,3 @@ - """ This module contains common `ctypes` utils. """ diff --git a/can/interface.py b/can/interface.py index bd8a5c386..8d96f651d 100644 --- a/can/interface.py +++ b/can/interface.py @@ -1,4 +1,3 @@ - """ This module contains the base implementation of :class:`can.BusABC` as well as a list of all available backends and some implemented diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 792517500..a163ad101 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -1,4 +1,3 @@ - """ Interfaces contain low level implementations that interact with CAN hardware. """ diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 8cfa97ca1..a57260490 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,4 +1,3 @@ - from ctypes import * import logging import platform diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py index 96af50d7e..1ac666b6c 100644 --- a/can/interfaces/ics_neovi/__init__.py +++ b/can/interfaces/ics_neovi/__init__.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 9eec48f45..df4f5481f 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -1,4 +1,3 @@ - """ ICS NeoVi interface module. diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index 360507976..a0bb413f2 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -1,4 +1,3 @@ - """ Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH. """ diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index 8fb17844a..a4613880b 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -1,4 +1,3 @@ - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 68f1b0aba..aa90ffafe 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,4 +1,3 @@ - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index 9608bb2bf..a24a3f291 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -1,4 +1,3 @@ - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 5b4347666..d548510b1 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -1,4 +1,3 @@ - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index df1ebd8a6..73c01823d 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -1,4 +1,3 @@ - """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems diff --git a/can/interfaces/kvaser/__init__.py b/can/interfaces/kvaser/__init__.py index 191179ab3..36a21db9f 100644 --- a/can/interfaces/kvaser/__init__.py +++ b/can/interfaces/kvaser/__init__.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 06c27e903..d0610019b 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -1,4 +1,3 @@ - """ Contains Python equivalents of the function and constant definitions in CANLIB's canlib.h, with some supporting functionality diff --git a/can/interfaces/kvaser/constants.py b/can/interfaces/kvaser/constants.py index d1a7ba610..9dd3a9163 100644 --- a/can/interfaces/kvaser/constants.py +++ b/can/interfaces/kvaser/constants.py @@ -1,4 +1,3 @@ - """ Contains Python equivalents of the function and constant definitions in CANLIB's canstat.h, with some supporting functionality diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index e2dd6a6a9..c7d363dd4 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -1,4 +1,3 @@ - """ Contains Python equivalents of the structures in CANLIB's canlib.h, with some supporting functionality specific to Python. diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 90d396a5c..cc22c4f12 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -1,4 +1,3 @@ - """ NI-CAN interface module. diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py index 8ae860a85..3627f0a36 100644 --- a/can/interfaces/pcan/__init__.py +++ b/can/interfaces/pcan/__init__.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 3fe5468ea..b10e5404e 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -1,4 +1,3 @@ - """ PCAN-Basic API diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 9a1dae2b2..b4d551a64 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -1,4 +1,3 @@ - """ Enable basic CAN over a PCAN USB device. """ diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py index a7a69c097..cb1c17f1d 100644 --- a/can/interfaces/seeedstudio/__init__.py +++ b/can/interfaces/seeedstudio/__init__.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index d9dfe448f..aecc3c15f 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -1,4 +1,3 @@ - """ To Support the Seeed USB-Can analyzer interface. The device will appear as a serial port, for example "/dev/ttyUSB0" on Linux machines diff --git a/can/interfaces/serial/__init__.py b/can/interfaces/serial/__init__.py index 21553a0da..bd6a45b9c 100644 --- a/can/interfaces/serial/__init__.py +++ b/can/interfaces/serial/__init__.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 063b09257..d7a81c98a 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -1,4 +1,3 @@ - """ A text based interface. For example use over serial ports like "/dev/ttyS1" or "/dev/ttyUSB0" on Linux machines or "COM1" on Windows. diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 0a1931c16..5d7450987 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -1,4 +1,3 @@ - """ Interface for slcan compatible interfaces (win32/linux). diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index a39cc1ef7..e08c18f50 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -1,4 +1,3 @@ - """ See: https://www.kernel.org/doc/Documentation/networking/can.txt """ diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index ae6c01519..0db298371 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -1,4 +1,3 @@ - """ Defines shared CAN constants. """ diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 0e9a733cb..de304e218 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -1,4 +1,3 @@ - """ The main module of the socketcan interface containing most user-facing classes and methods along some internal methods. diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index f93ec1420..338e41dc8 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -1,4 +1,3 @@ - """ Defines common socketcan functions. """ diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py index f211bc4ef..4ecd39b4c 100644 --- a/can/interfaces/systec/__init__.py +++ b/can/interfaces/systec/__init__.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/systec/constants.py b/can/interfaces/systec/constants.py index a35e2ccb5..96952c17e 100644 --- a/can/interfaces/systec/constants.py +++ b/can/interfaces/systec/constants.py @@ -1,4 +1,3 @@ - from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD #: Maximum number of modules that are supported. diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index fd591d702..72ec92cfa 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -1,4 +1,3 @@ - from .constants import ReturnCode from can import CanError diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index b13ee31d9..fbebdcdbd 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -1,4 +1,3 @@ - from ctypes import Structure, POINTER, sizeof from ctypes import ( c_ubyte as BYTE, diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index 16ff6245b..3f90e6cc2 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -1,4 +1,3 @@ - import logging import sys diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 3c65143b3..2d3a23777 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -1,4 +1,3 @@ - import logging from threading import Event diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index 4665c9fb1..4ccff1cb0 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index 3b0f225b5..d9beb5df4 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 29316b771..8e0f54687 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -1,4 +1,3 @@ - """ This interface is for Windows only, otherwise use socketCAN. """ diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index 2811ba235..1f336b241 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -1,4 +1,3 @@ - """ This wrapper is for windows or direct access via CANAL API. Socket CAN is recommended under Unix/Linux systems. diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index 19bb42afd..813608a86 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index cc674288b..40a4ea991 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -1,4 +1,3 @@ - """ Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index fb34517c6..042c9d73a 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -1,4 +1,3 @@ - """ """ diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py index 49eb410d0..90513f11e 100644 --- a/can/interfaces/vector/xlclass.py +++ b/can/interfaces/vector/xlclass.py @@ -1,4 +1,3 @@ - """ Definition of data types and structures for vxlapi. diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index 7cc929052..1d130624b 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -1,4 +1,3 @@ - """ Definition of constants for vxlapi. """ diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index fde7cca56..9bb1a1083 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -1,4 +1,3 @@ - """ Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 436988dab..5e24c6e1f 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -1,4 +1,3 @@ - """ This module implements an OS and hardware independent virtual CAN interface for testing purposes. diff --git a/can/io/__init__.py b/can/io/__init__.py index f4c1b0359..53389e91b 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -1,4 +1,3 @@ - """ Read and write CAN bus messages using a range of Readers and Writers based off the file extension. diff --git a/can/io/asc.py b/can/io/asc.py index 41f947c57..0a7017559 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -1,4 +1,3 @@ - """ Contains handling of ASC logging files. diff --git a/can/io/blf.py b/can/io/blf.py index 21252f38a..6b9ccebce 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -1,4 +1,3 @@ - """ Implements support for BLF (Binary Logging Format) which is a proprietary CAN log format from Vector Informatik GmbH (Germany). diff --git a/can/io/canutils.py b/can/io/canutils.py index cd56ff32e..6333503c3 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -1,4 +1,3 @@ - """ This module works with CAN data in ASCII log files (*.log). It is is compatible with "candump -L" from the canutils program diff --git a/can/io/csv.py b/can/io/csv.py index a3793bdb6..35cfcc697 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -1,4 +1,3 @@ - """ This module contains handling for CSV (comma separated values) files. diff --git a/can/io/generic.py b/can/io/generic.py index 10f7fc6a9..eefa3d028 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,4 +1,3 @@ - """ Contains a generic class for file IO. """ diff --git a/can/io/logger.py b/can/io/logger.py index 6b63f12f5..3c9cf5e46 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -1,4 +1,3 @@ - """ See the :class:`Logger` class. """ diff --git a/can/io/player.py b/can/io/player.py index 79603455e..bd206061c 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -1,4 +1,3 @@ - """ This module contains the generic :class:`LogReader` as well as :class:`MessageSync` which plays back messages diff --git a/can/io/printer.py b/can/io/printer.py index 57e34db3f..ed3006de2 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -1,4 +1,3 @@ - """ This Listener simply prints to stdout / the terminal or a file. """ diff --git a/can/io/sqlite.py b/can/io/sqlite.py index abeab6090..104ceb77d 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -1,4 +1,3 @@ - """ Implements an SQL database writer and reader for storing CAN messages. diff --git a/can/listener.py b/can/listener.py index 44723bb99..d03a7e0c8 100644 --- a/can/listener.py +++ b/can/listener.py @@ -1,4 +1,3 @@ - """ This module contains the implementation of `can.Listener` and some readers. """ diff --git a/can/logger.py b/can/logger.py index 0a9055259..8a3a214e6 100644 --- a/can/logger.py +++ b/can/logger.py @@ -1,4 +1,3 @@ - """ logger.py logs CAN traffic to the terminal and to a file on disk. diff --git a/can/message.py b/can/message.py index 385d2244b..57e0109af 100644 --- a/can/message.py +++ b/can/message.py @@ -1,4 +1,3 @@ - """ This module contains the implementation of :class:`can.Message`. diff --git a/can/notifier.py b/can/notifier.py index 108a645b4..2b909cae7 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -1,4 +1,3 @@ - """ This module contains the implementation of :class:`~can.Notifier`. """ diff --git a/can/player.py b/can/player.py index 86bd0afe6..d7ef866fb 100644 --- a/can/player.py +++ b/can/player.py @@ -1,4 +1,3 @@ - """ Replays CAN traffic saved with can.logger back to a CAN bus. diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index b31e48407..f36119751 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -1,4 +1,3 @@ - from threading import RLock try: diff --git a/can/util.py b/can/util.py index 473057328..2cfebddb1 100644 --- a/can/util.py +++ b/can/util.py @@ -1,4 +1,3 @@ - """ Utilities and configuration file parsing. """ diff --git a/can/viewer.py b/can/viewer.py index 9f3ac361a..feeda7835 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -1,4 +1,3 @@ - # Copyright (C) 2018 Kristian Sloth Lauszus. # # This program is free software; you can redistribute it and/or diff --git a/doc/conf.py b/doc/conf.py index 62fd649b6..568a5641d 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,23 +1,24 @@ -# -*- coding: utf-8 -*- -# -# python-can documentation build configuration file -# -# This file is execfile()d with the current directory set to its containing dir. +""" +python-can documentation build configuration file + +This file is execfile()d with the current directory set to its containing dir. +""" -from __future__ import unicode_literals, absolute_import +# -- Imports ------------------------------------------------------------------- import sys import os -# General information about the project. -project = "python-can" - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath("..")) -import can +import can # pylint: disable=wrong-import-position + +# -- General configuration ----------------------------------------------------- + +# pylint: disable=invalid-name # The version info for the project, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -27,7 +28,8 @@ version = can.__version__.split("-")[0] release = can.__version__ -# -- General configuration ----------------------------------------------------- +# General information about the project. +project = "python-can" primary_domain = "py" From 81c5e157f92133afa8ae2d15fb0c725397510b9e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 4 Sep 2019 08:04:11 +0200 Subject: [PATCH 0338/1235] add scripts/*.py to pylint --- .travis.yml | 2 ++ scripts/can_logger.py | 3 --- scripts/can_player.py | 3 --- scripts/can_viewer.py | 3 --- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index b904ebb42..e0e092d48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,6 +88,8 @@ jobs: - pylint --rcfile=.pylintrc *.py # check doc/conf.py - pylint --rcfile=.pylintrc doc/**.py + # check scripts + - pylint --rcfile=.pylintrc scripts/**.py # mypy checking - mypy --python-version=3.7 diff --git a/scripts/can_logger.py b/scripts/can_logger.py index 72a92b9d0..4202448e6 100644 --- a/scripts/can_logger.py +++ b/scripts/can_logger.py @@ -1,12 +1,9 @@ #!/usr/bin/env python -# coding: utf-8 """ See :mod:`can.logger`. """ -from __future__ import absolute_import - from can.logger import main diff --git a/scripts/can_player.py b/scripts/can_player.py index afbd3df6e..1fe44175d 100644 --- a/scripts/can_player.py +++ b/scripts/can_player.py @@ -1,12 +1,9 @@ #!/usr/bin/env python -# coding: utf-8 """ See :mod:`can.player`. """ -from __future__ import absolute_import - from can.player import main diff --git a/scripts/can_viewer.py b/scripts/can_viewer.py index 3c9ba738c..eef990b0e 100644 --- a/scripts/can_viewer.py +++ b/scripts/can_viewer.py @@ -1,12 +1,9 @@ #!/usr/bin/env python -# coding: utf-8 """ See :mod:`can.viewer`. """ -from __future__ import absolute_import - from can.viewer import main From 2ef710cca38e32f6ac1cc25586b9ae8b40efa600 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Wed, 11 Sep 2019 14:38:10 +0200 Subject: [PATCH 0339/1235] ASCReader: Skip J1939TP messages --- can/io/asc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/io/asc.py b/can/io/asc.py index 0a7017559..c538800d1 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -73,6 +73,7 @@ def __iter__(self): elif ( not isinstance(channel, int) or dummy.strip()[0:10].lower() == "statistic:" + or dummy.split(None, 1)[0] == "J1939TP" ): pass elif dummy[-1:].lower() == "r": From 6caa9d4b7cbf26968b40c6508d53c4c6c38ff7c8 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Thu, 12 Sep 2019 09:46:17 +0200 Subject: [PATCH 0340/1235] Add example J1939TP message to logfile.asc --- test/data/logfile.asc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/data/logfile.asc b/test/data/logfile.asc index 4b7c64363..842c463fe 100644 --- a/test/data/logfile.asc +++ b/test/data/logfile.asc @@ -9,6 +9,7 @@ Begin Triggerblock Sam Sep 30 15:06:13.191 2017 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 3.297743 1 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 From b268712a558e90c5b085c04a9a822ebe11805c0d Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Thu, 12 Sep 2019 10:34:54 +0200 Subject: [PATCH 0341/1235] Document log entry types ignored by ASCReader --- can/io/asc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index c538800d1..9d854ab0f 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -24,7 +24,8 @@ class ASCReader(BaseIOHandler): """ - Iterator of CAN messages from a ASC logging file. + Iterator of CAN messages from a ASC logging file. Meta data (comments, + bus statistics, J1939 Transport Protocol messages) is ignored. TODO: turn relative timestamps back to absolute form """ From 68855df62e3983745d48c7b44df6979ba4bd38b6 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Thu, 12 Sep 2019 10:44:46 +0200 Subject: [PATCH 0342/1235] Add some more J1939TP examples --- test/data/logfile.asc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/data/logfile.asc b/test/data/logfile.asc index 842c463fe..8582cbf05 100644 --- a/test/data/logfile.asc +++ b/test/data/logfile.asc @@ -9,11 +9,20 @@ Begin Triggerblock Sam Sep 30 15:06:13.191 2017 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x + 3.148421 1 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 271910 BitCount = 140 ID = 418119424x + 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 3.248765 1 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283910 BitCount = 146 ID = 418119424x 3.297743 1 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 18.015997 1 Statistic: D 2 R 0 XD 0 XR 0 E 0 O 0 B 0.04% + 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x + 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x + 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x + 20.305233 2 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% End TriggerBlock From 7bf39285967d785843919f12a4f69ba736181586 Mon Sep 17 00:00:00 2001 From: Karl Date: Tue, 6 Aug 2019 17:37:32 -0700 Subject: [PATCH 0343/1235] Add typing annotations for can.util This adds typing annotations for functions in can.util. In addition, this remove the redundant typing information that was previously in the docstring, since we now have sphinx-autodoc-typehints to generate the types for the docs from the annotations in the function signature. This works towards PEP 561 compatibility. --- can/typechecking.py | 2 ++ can/util.py | 41 +++++++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/can/typechecking.py b/can/typechecking.py index b5b79d500..7d7b1d893 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -27,3 +27,5 @@ FileLike = typing.IO[typing.Any] StringPathLike = typing.Union[str, "os.PathLike[str]"] AcceptedIOType = typing.Optional[typing.Union[FileLike, StringPathLike]] + +BusConfig = typing.NewType("BusConfig", dict) diff --git a/can/util.py b/can/util.py index 2cfebddb1..968f1f7fd 100644 --- a/can/util.py +++ b/can/util.py @@ -2,6 +2,10 @@ Utilities and configuration file parsing. """ +from typing import Dict, Optional, Union + +from can import typechecking + import json import os import os.path @@ -29,7 +33,9 @@ CONFIG_FILES.extend(["can.ini", os.path.join(os.getenv("APPDATA", ""), "can.ini")]) -def load_file_config(path=None, section="default"): +def load_file_config( + path: Optional[typechecking.AcceptedIOType] = None, section: str = "default" +) -> Dict[str, str]: """ Loads configuration from file with following content:: @@ -57,7 +63,7 @@ def load_file_config(path=None, section="default"): return _config -def load_environment_config(context=None): +def load_environment_config(context: Optional[str] = None) -> Dict[str, str]: """ Loads config dict from environmental variables (if set): @@ -83,20 +89,22 @@ def load_environment_config(context=None): context_suffix = "_{}".format(context) if context else "" - config = {} - can_config_key = "CAN_CONFIG" + context_suffix - if can_config_key in os.environ: - config = json.loads(os.environ.get(can_config_key)) + config: Dict[str, str] = json.loads(os.environ.get(can_config_key, "{}")) for key, val in mapper.items(): - if val in os.environ: - config[key] = os.environ.get(val + context_suffix) + config_option = os.environ.get(val + context_suffix, None) + if config_option: + config[key] = config_option return config -def load_config(path=None, config=None, context=None): +def load_config( + path: Optional[typechecking.AcceptedIOType] = None, + config=None, + context: Optional[str] = None, +) -> typechecking.BusConfig: """ Returns a dict with configuration details which is loaded from (in this order): @@ -213,26 +221,25 @@ def load_config(path=None, config=None, context=None): return config -def set_logging_level(level_name=None): +def set_logging_level(level_name: Optional[str] = None): """Set the logging level for the "can" logger. Expects one of: 'critical', 'error', 'warning', 'info', 'debug', 'subdebug' """ can_logger = logging.getLogger("can") try: - can_logger.setLevel(getattr(logging, level_name.upper())) + can_logger.setLevel(getattr(logging, level_name.upper())) # type: ignore except AttributeError: can_logger.setLevel(logging.DEBUG) log.debug("Logging set to {}".format(level_name)) -def len2dlc(length): +def len2dlc(length: int) -> int: """Calculate the DLC from data length. :param int length: Length in number of bytes (0-64) :returns: DLC (0-15) - :rtype: int """ if length <= 8: return length @@ -242,25 +249,23 @@ def len2dlc(length): return 15 -def dlc2len(dlc): +def dlc2len(dlc: int) -> int: """Calculate the data length from DLC. - :param int dlc: DLC (0-15) + :param dlc: DLC (0-15) :returns: Data length in number of bytes (0-64) - :rtype: int """ return CAN_FD_DLC[dlc] if dlc <= 15 else 64 -def channel2int(channel): +def channel2int(channel: Optional[Union[typechecking.Channel]]) -> Optional[int]: """Try to convert the channel to an integer. :param channel: Channel string (e.g. can0, CAN1) or integer :returns: Channel integer or `None` if unsuccessful - :rtype: int """ if channel is None: return None From c93a2e0d2a6a43c8fa2e7ccf3ead1a0f0dc3dd8a Mon Sep 17 00:00:00 2001 From: Karl Date: Wed, 23 Oct 2019 08:35:56 -0700 Subject: [PATCH 0344/1235] Fix typing annotation for LogReader constructor A bug was introduced sometime during refactoring of types in the helper typechecking.py file, when PathLike was renamed to StringPathLike. This results in mypy complaining that it can't find the PathLike type: error: Name 'can.typechecking.PathLike' is not defined As such, this changes the accepted type for the LogReader constructor to be a typechecking.StringPathLike. --- can/io/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/player.py b/can/io/player.py index bd206061c..7b4aec7c0 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -45,7 +45,7 @@ class LogReader(BaseIOHandler): """ @staticmethod - def __new__(cls, filename: "can.typechecking.PathLike", *args, **kwargs): + def __new__(cls, filename: "can.typechecking.StringPathLike", *args, **kwargs): """ :param filename: the filename/path of the file to read from :raises ValueError: if the filename's suffix is of an unknown file type From 216ed73d3250b1622dde62a7a2fbbffbfe320880 Mon Sep 17 00:00:00 2001 From: Kai Oberbeckmann Date: Thu, 24 Oct 2019 12:21:06 +0200 Subject: [PATCH 0345/1235] Initialized dataBin in canutils with None to prevent crashes when reading remote frames (#713) --- can/io/canutils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/can/io/canutils.py b/can/io/canutils.py index 6333503c3..5c08e9050 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -56,10 +56,13 @@ def __iter__(self): if data and data[0].lower() == "r": isRemoteFrame = True + if len(data) > 1: dlc = int(data[1:]) else: dlc = 0 + + dataBin = None else: isRemoteFrame = False From 17093cc8b92a7d6ce3a8903851382ba87cc9baae Mon Sep 17 00:00:00 2001 From: Karl Date: Thu, 24 Oct 2019 08:59:51 -0700 Subject: [PATCH 0346/1235] Bump mypy version in lint step to 0.740 Update the version of mypy used in the CI Linter step to the latest release. --- requirements-lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index ce953e68b..d0b374c20 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ pylint==2.3.1 black==19.3b0 -mypy==0.720 +mypy==0.740 mypy-extensions==0.4.1 From 69451507cedf48912211cbc39575e0d88094edfa Mon Sep 17 00:00:00 2001 From: tamenol <37591107+tamenol@users.noreply.github.com> Date: Thu, 7 Nov 2019 08:26:19 +0100 Subject: [PATCH 0347/1235] Add support for error frames on PCAN (#711) Enable reception of error frames on the PCAN interface. Fixes #707 --- can/interfaces/pcan/pcan.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index b4d551a64..5c7b2b9d7 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -209,6 +209,13 @@ def __init__( self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt ) + if result != PCAN_ERROR_OK: + raise PcanError(self._get_formatted_error(result)) + + result = self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_ALLOW_ERROR_FRAMES, PCAN_PARAMETER_ON + ) + if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) From c001ef274316b015f7b068414a541a573ce4ef5c Mon Sep 17 00:00:00 2001 From: karl ding Date: Wed, 6 Nov 2019 23:56:33 -0800 Subject: [PATCH 0348/1235] Add typing annotations for SocketCAN interface (#710) Add typing annotations for the SocketCAN interface. This works towards PEP 561 compatibility. --- .travis.yml | 1 + can/bus.py | 10 +- can/interfaces/socketcan/socketcan.py | 178 ++++++++++++++++---------- can/interfaces/socketcan/utils.py | 10 +- can/typechecking.py | 11 +- 5 files changed, 131 insertions(+), 79 deletions(-) diff --git a/.travis.yml b/.travis.yml index e0a4b0b3f..2bea7900f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,6 +105,7 @@ jobs: can/broadcastmanager.py can/bus.py can/interface.py + can/interfaces/socketcan/**.py can/listener.py can/logger.py can/message.py diff --git a/can/bus.py b/can/bus.py index fb6410cf5..4ef5b5fd6 100644 --- a/can/bus.py +++ b/can/bus.py @@ -2,7 +2,7 @@ Contains the ABC bus implementation and its documentation. """ -from typing import Iterator, List, Optional, Sequence, Tuple, Union +from typing import cast, Iterator, List, Optional, Sequence, Tuple, Union import can.typechecking @@ -369,8 +369,10 @@ def _matches_filters(self, msg: Message) -> bool: for _filter in self._filters: # check if this filter even applies to the message - if "extended" in _filter and _filter["extended"] != msg.is_extended_id: - continue + if "extended" in _filter: + _filter = cast(can.typechecking.CanFilterExtended, _filter) + if _filter["extended"] != msg.is_extended_id: + continue # then check for the mask and id can_id = _filter["can_id"] @@ -416,7 +418,7 @@ def state(self, new_state: BusState): raise NotImplementedError("Property is not implemented.") @staticmethod - def _detect_available_configs() -> Iterator[dict]: + def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: """Detect all configurations/channels that this interface could currently connect with. diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index de304e218..307763d9f 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -5,6 +5,8 @@ At the end of the file the usage of the internal methods is shown. """ +from typing import Dict, List, Optional, Sequence, Tuple, Type, Union + import logging import ctypes import ctypes.util @@ -34,11 +36,17 @@ from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces - # Setup BCM struct -def bcm_header_factory(fields, alignment=8): +def bcm_header_factory( + fields: List[Tuple[str, Union[Type[ctypes.c_uint32], Type[ctypes.c_long]]]], + alignment: int = 8, +): curr_stride = 0 - results = [] + results: List[ + Tuple[ + str, Union[Type[ctypes.c_uint8], Type[ctypes.c_uint32], Type[ctypes.c_long]] + ] + ] = [] pad_index = 0 for field in fields: field_alignment = ctypes.alignment(field[1]) @@ -122,7 +130,7 @@ def bcm_header_factory(fields, alignment=8): CAN_FRAME_HEADER_STRUCT = struct.Struct("=IBB2x") -def build_can_frame(msg): +def build_can_frame(msg: Message) -> bytes: """ CAN frame packing/unpacking (see 'struct can_frame' in ) /** * struct can_frame - basic CAN frame structure @@ -166,16 +174,16 @@ def build_can_frame(msg): def build_bcm_header( - opcode, - flags, - count, - ival1_seconds, - ival1_usec, - ival2_seconds, - ival2_usec, - can_id, - nframes, -): + opcode: int, + flags: int, + count: int, + ival1_seconds: int, + ival1_usec: int, + ival2_seconds: int, + ival2_usec: int, + can_id: int, + nframes: int, +) -> bytes: result = BcmMsgHead( opcode=opcode, flags=flags, @@ -190,14 +198,19 @@ def build_bcm_header( return ctypes.string_at(ctypes.addressof(result), ctypes.sizeof(result)) -def build_bcm_tx_delete_header(can_id, flags): +def build_bcm_tx_delete_header(can_id: int, flags: int) -> bytes: opcode = CAN_BCM_TX_DELETE return build_bcm_header(opcode, flags, 0, 0, 0, 0, 0, can_id, 1) def build_bcm_transmit_header( - can_id, count, initial_period, subsequent_period, msg_flags, nframes=1 -): + can_id: int, + count: int, + initial_period: float, + subsequent_period: float, + msg_flags: int, + nframes: int = 1, +) -> bytes: opcode = CAN_BCM_TX_SETUP flags = msg_flags | SETTIMER | STARTTIMER @@ -206,7 +219,7 @@ def build_bcm_transmit_header( # Note `TX_COUNTEVT` creates the message TX_EXPIRED when count expires flags |= TX_COUNTEVT - def split_time(value): + def split_time(value: float) -> Tuple[int, int]: """Given seconds as a float, return whole seconds and microseconds""" seconds = int(value) microseconds = int(1e6 * (value - seconds)) @@ -228,11 +241,11 @@ def split_time(value): ) -def build_bcm_update_header(can_id, msg_flags, nframes=1): +def build_bcm_update_header(can_id: int, msg_flags: int, nframes: int = 1) -> bytes: return build_bcm_header(CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, nframes) -def dissect_can_frame(frame): +def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]: can_id, can_dlc, flags = CAN_FRAME_HEADER_STRUCT.unpack_from(frame) if len(frame) != CANFD_MTU: # Flags not valid in non-FD frames @@ -240,14 +253,14 @@ def dissect_can_frame(frame): return can_id, can_dlc, flags, frame[8 : 8 + can_dlc] -def create_bcm_socket(channel): +def create_bcm_socket(channel: str) -> socket.socket: """create a broadcast manager socket and connect to the given interface""" s = socket.socket(PF_CAN, socket.SOCK_DGRAM, CAN_BCM) s.connect((channel,)) return s -def send_bcm(bcm_socket, data): +def send_bcm(bcm_socket: socket.socket, data: bytes) -> int: """ Send raw frame to a BCM socket and handle errors. """ @@ -273,7 +286,7 @@ def send_bcm(bcm_socket, data): raise e -def _add_flags_to_can_id(message): +def _add_flags_to_can_id(message: Message) -> int: can_id = message.arbitration_id if message.is_extended_id: log.debug("sending an extended id type message") @@ -300,14 +313,20 @@ class CyclicSendTask( """ - def __init__(self, bcm_socket, messages, period, duration=None): + def __init__( + self, + bcm_socket: socket.socket, + messages: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + ): """ :param bcm_socket: An open BCM socket on the desired CAN channel. - :param Union[Sequence[can.Message], can.Message] messages: + :param messages: The messages to be sent periodically. - :param float period: + :param period: The rate in seconds at which to send the messages. - :param float duration: + :param duration: Approximate duration in seconds to send the messages for. """ # The following are assigned by LimitedDurationCyclicSendTaskABC: @@ -319,9 +338,8 @@ def __init__(self, bcm_socket, messages, period, duration=None): self.bcm_socket = bcm_socket self._tx_setup(self.messages) - def _tx_setup(self, messages): + def _tx_setup(self, messages: Sequence[Message]) -> None: # Create a low level packed frame to pass to the kernel - header = bytearray() body = bytearray() self.can_id_with_flags = _add_flags_to_can_id(messages[0]) self.flags = CAN_FD_FRAME if messages[0].is_fd else 0 @@ -329,10 +347,10 @@ def _tx_setup(self, messages): if self.duration: count = int(self.duration / self.period) ival1 = self.period - ival2 = 0 + ival2 = 0.0 else: count = 0 - ival1 = 0 + ival1 = 0.0 ival2 = self.period # First do a TX_READ before creating a new task, and check if we get @@ -374,7 +392,7 @@ def _tx_setup(self, messages): log.debug("Sending BCM command") send_bcm(self.bcm_socket, header + body) - def stop(self): + def stop(self) -> None: """Send a TX_DELETE message to cancel this task. This will delete the entry for the transmission of the CAN-message @@ -386,7 +404,7 @@ def stop(self): stopframe = build_bcm_tx_delete_header(self.can_id_with_flags, self.flags) send_bcm(self.bcm_socket, stopframe) - def modify_data(self, messages): + def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: """Update the contents of the periodically sent messages. Note: The messages must all have the same @@ -395,7 +413,7 @@ def modify_data(self, messages): Note: The number of new cyclic messages to be sent must be equal to the original number of messages originally specified for this task. - :param Union[Sequence[can.Message], can.Message] messages: + :param messages: The messages with the new :attr:`can.Message.data`. """ messages = self._check_and_convert_messages(messages) @@ -403,7 +421,6 @@ def modify_data(self, messages): self.messages = messages - header = bytearray() body = bytearray() header = build_bcm_update_header( can_id=self.can_id_with_flags, msg_flags=self.flags, nframes=len(messages) @@ -413,7 +430,7 @@ def modify_data(self, messages): log.debug("Sending BCM command") send_bcm(self.bcm_socket, header + body) - def start(self): + def start(self) -> None: self._tx_setup(self.messages) @@ -423,7 +440,14 @@ class MultiRateCyclicSendTask(CyclicSendTask): """ - def __init__(self, channel, messages, count, initial_period, subsequent_period): + def __init__( + self, + channel: socket.socket, + messages: Sequence[Message], + count: int, + initial_period: float, + subsequent_period: float, + ): super().__init__(channel, messages, subsequent_period) # Create a low level packed frame to pass to the kernel @@ -444,7 +468,7 @@ def __init__(self, channel, messages, count, initial_period, subsequent_period): send_bcm(self.bcm_socket, header + body) -def create_socket(): +def create_socket() -> socket.socket: """Creates a raw CAN socket. The socket will be returned unbound to any interface. """ @@ -455,11 +479,11 @@ def create_socket(): return sock -def bind_socket(sock, channel="can0"): +def bind_socket(sock: socket.socket, channel: str = "can0") -> None: """ Binds the given socket to the given interface. - :param socket.socket sock: + :param sock: The socket to be bound :raises OSError: If the specified interface isn't found. @@ -469,13 +493,15 @@ def bind_socket(sock, channel="can0"): log.debug("Bound socket.") -def capture_message(sock, get_channel=False): +def capture_message( + sock: socket.socket, get_channel: bool = False +) -> Optional[Message]: """ Captures a message from given socket. - :param socket.socket sock: + :param sock: The socket to read a message from. - :param bool get_channel: + :param get_channel: Find out which channel the message comes from. :return: The received message, or None on failure. @@ -496,7 +522,7 @@ def capture_message(sock, get_channel=False): # Fetching the timestamp binary_structure = "@LL" - res = fcntl.ioctl(sock, SIOCGSTAMP, struct.pack(binary_structure, 0, 0)) + res = fcntl.ioctl(sock.fileno(), SIOCGSTAMP, struct.pack(binary_structure, 0, 0)) seconds, microseconds = struct.unpack(binary_structure, res) timestamp = seconds + microseconds * 1e-6 @@ -545,17 +571,23 @@ class SocketcanBus(BusABC): Implements :meth:`can.BusABC._detect_available_configs`. """ - def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): + def __init__( + self, + channel: str = "", + receive_own_messages: bool = False, + fd: bool = False, + **kwargs, + ) -> None: """ - :param str channel: + :param channel: The can interface name with which to create this bus. An example channel would be 'vcan0' or 'can0'. An empty string '' will receive messages from all channels. In that case any sent messages must be explicitly addressed to a channel using :attr:`can.Message.channel`. - :param bool receive_own_messages: + :param receive_own_messages: If transmitted messages should also be received by this bus. - :param bool fd: + :param fd: If CAN-FD frames should be supported. :param list can_filters: See :meth:`can.BusABC.set_filters`. @@ -563,7 +595,7 @@ def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): self.socket = create_socket() self.channel = channel self.channel_info = "socketcan channel '%s'" % channel - self._bcm_sockets = {} + self._bcm_sockets: Dict[str, socket.socket] = {} self._is_filtered = False # set the receive_own_messages parameter @@ -585,7 +617,7 @@ def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): kwargs.update({"receive_own_messages": receive_own_messages, "fd": fd}) super().__init__(channel=channel, **kwargs) - def shutdown(self): + def shutdown(self) -> None: """Stops all active periodic tasks and closes the socket.""" self.stop_all_periodic_tasks() for channel in self._bcm_sockets: @@ -595,7 +627,9 @@ def shutdown(self): log.debug("Closing raw can socket") self.socket.close() - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) try: @@ -609,7 +643,7 @@ def _recv_internal(self, timeout): if ready_receive_sockets: # not empty or True get_channel = self.channel == "" msg = capture_message(self.socket, get_channel) - if not msg.channel and self.channel: + if msg and not msg.channel and self.channel: # Default to our own channel msg.channel = self.channel return msg, self._is_filtered @@ -617,11 +651,11 @@ def _recv_internal(self, timeout): # socket wasn't readable or timeout occurred return None, self._is_filtered - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: """Transmit a message to the CAN bus. - :param can.Message msg: A message object. - :param float timeout: + :param msg: A message object. + :param timeout: Wait up to this many seconds for the transmit queue to be ready. If not given, the call may fail immediately. @@ -645,7 +679,8 @@ def send(self, msg, timeout=None): if not ready: # Timeout break - sent = self._send_once(data, msg.channel) + channel = str(msg.channel) if msg.channel else None + sent = self._send_once(data, channel) if sent == len(data): return # Not all data were sent, try again with remaining data @@ -654,7 +689,7 @@ def send(self, msg, timeout=None): raise can.CanError("Transmit buffer full") - def _send_once(self, data, channel=None): + def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: try: if self.channel == "" and channel: # Message must be addressed to a specific channel @@ -665,23 +700,27 @@ def _send_once(self, data, channel=None): raise can.CanError("Failed to transmit: %s" % exc) return sent - 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, + ) -> CyclicSendTask: """Start sending messages at a given period on this bus. The kernel's Broadcast Manager SocketCAN API will be used. - :param Union[Sequence[can.Message], can.Message] messages: + :param messages: The messages to be sent periodically - :param float period: + :param period: The rate in seconds at which to send the messages. - :param float duration: + :param duration: Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. :return: A started task instance. This can be used to modify the data, pause/resume the transmission and to stop the transmission. - :rtype: can.interfaces.socketcan.CyclicSendTask .. note:: @@ -693,19 +732,20 @@ def _send_periodic_internal(self, msgs, period, duration=None): """ msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages(msgs) - bcm_socket = self._get_bcm_socket(msgs[0].channel or self.channel) + msgs_channel = str(msgs[0].channel) if msgs[0].channel else None + bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) # TODO: The SocketCAN BCM interface treats all cyclic tasks sharing an # Arbitration ID as the same Cyclic group. We should probably warn the # user instead of overwriting the old group? task = CyclicSendTask(bcm_socket, msgs, period, duration) return task - def _get_bcm_socket(self, channel): + def _get_bcm_socket(self, channel: str) -> socket.socket: if channel not in self._bcm_sockets: self._bcm_sockets[channel] = create_bcm_socket(self.channel) return self._bcm_sockets[channel] - def _apply_filters(self, filters): + def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: try: self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters)) except socket.error as err: @@ -719,11 +759,11 @@ def _apply_filters(self, filters): else: self._is_filtered = True - def fileno(self): + def fileno(self) -> int: return self.socket.fileno() @staticmethod - def _detect_available_configs(): + def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: return [ {"interface": "socketcan", "channel": channel} for channel in find_available_interfaces() @@ -742,14 +782,14 @@ def _detect_available_configs(): # log.setLevel(logging.DEBUG) - def receiver(event): + def receiver(event: threading.Event) -> None: receiver_socket = create_socket() bind_socket(receiver_socket, "vcan0") print("Receiver is waiting for a message...") event.set() print(f"Receiver got: {capture_message(receiver_socket)}") - def sender(event): + def sender(event: threading.Event) -> None: event.wait() sender_socket = create_socket() bind_socket(sender_socket, "vcan0") diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 338e41dc8..ee89b142e 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -2,6 +2,9 @@ Defines common socketcan functions. """ +from typing import cast, Iterable, Optional +import can.typechecking as typechecking + import logging import os import errno @@ -14,7 +17,7 @@ log = logging.getLogger(__name__) -def pack_filters(can_filters=None): +def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes: if can_filters is None: # Pass all messages can_filters = [{"can_id": 0, "can_mask": 0}] @@ -25,6 +28,7 @@ def pack_filters(can_filters=None): can_id = can_filter["can_id"] can_mask = can_filter["can_mask"] if "extended" in can_filter: + can_filter = cast(typechecking.CanFilterExtended, can_filter) # Match on either 11-bit OR 29-bit messages instead of both can_mask |= CAN_EFF_FLAG if can_filter["extended"]: @@ -38,7 +42,7 @@ def pack_filters(can_filters=None): _PATTERN_CAN_INTERFACE = re.compile(r"v?can\d+") -def find_available_interfaces(): +def find_available_interfaces() -> Iterable[str]: """Returns the names of all open can/vcan interfaces using the ``ip link list`` command. If the lookup fails, an error is logged to the console and an empty list is returned. @@ -64,7 +68,7 @@ def find_available_interfaces(): return filter(_PATTERN_CAN_INTERFACE.match, interface_names) -def error_code_to_str(code): +def error_code_to_str(code: int) -> str: """ Converts a given error code (errno) to a useful and human readable string. diff --git a/can/typechecking.py b/can/typechecking.py index 7d7b1d893..0327874f5 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -8,10 +8,11 @@ import mypy_extensions -CanFilter = mypy_extensions.TypedDict( - "CanFilter", {"can_id": int, "can_mask": int, "extended": bool} +CanFilter = mypy_extensions.TypedDict("CanFilter", {"can_id": int, "can_mask": int}) +CanFilterExtended = mypy_extensions.TypedDict( + "CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool} ) -CanFilters = typing.Iterable[CanFilter] +CanFilters = typing.Sequence[typing.Union[CanFilter, CanFilterExtended]] # 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 @@ -29,3 +30,7 @@ AcceptedIOType = typing.Optional[typing.Union[FileLike, StringPathLike]] BusConfig = typing.NewType("BusConfig", dict) + +AutoDetectedConfig = mypy_extensions.TypedDict( + "AutoDetectedConfig", {"interface": str, "channel": Channel} +) From ac844547b45c19d25002286e4734cfe565e83bf1 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 12 Nov 2019 08:06:53 +0100 Subject: [PATCH 0349/1235] vector xlclass - implement XLbusParams (#718) --- can/interfaces/vector/xlclass.py | 52 ++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py index 90513f11e..de799f12b 100644 --- a/can/interfaces/vector/xlclass.py +++ b/can/interfaces/vector/xlclass.py @@ -169,10 +169,58 @@ class XLcanFdConf(ctypes.Structure): ("sjwDbr", ctypes.c_uint), ("tseg1Dbr", ctypes.c_uint), ("tseg2Dbr", ctypes.c_uint), - ("reserved", ctypes.c_uint * 2), + ("reserved", ctypes.c_ubyte), + ("options", ctypes.c_ubyte), + ("reserved1", ctypes.c_ubyte * 2), + ("reserved2", ctypes.c_ubyte), + ] + + +# channel configuration structures +class s_xl_bus_params_data_can(ctypes.Structure): + _fields_ = [ + ("bitRate", ctypes.c_uint), + ("sjw", ctypes.c_ubyte), + ("tseg1", ctypes.c_ubyte), + ("tseg2", ctypes.c_ubyte), + ("sam", ctypes.c_ubyte), + ("outputMode", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte * 7), + ("canOpMode", ctypes.c_ubyte), + ] + + +class s_xl_bus_params_data_canfd(ctypes.Structure): + _fields_ = [ + ("arbitrationBitRate", ctypes.c_uint), + ("sjwAbr", ctypes.c_ubyte), + ("tseg1Abr", ctypes.c_ubyte), + ("tseg2Abr", ctypes.c_ubyte), + ("samAbr", ctypes.c_ubyte), + ("outputMode", ctypes.c_ubyte), + ("sjwDbr", ctypes.c_ubyte), + ("tseg1Dbr", ctypes.c_ubyte), + ("tseg2Dbr", ctypes.c_ubyte), + ("dataBitRate", ctypes.c_uint), + ("canOpMode", ctypes.c_ubyte), ] +class s_xl_bus_params_data(ctypes.Union): + _fields_ = [ + ("can", s_xl_bus_params_data_can), + ("canFD", s_xl_bus_params_data_canfd), + ("most", ctypes.c_ubyte * 12), + ("flexray", ctypes.c_ubyte * 12), + ("ethernet", ctypes.c_ubyte * 12), + ("a429", ctypes.c_ubyte * 28), + ] + + +class XLbusParams(ctypes.Structure): + _fields_ = [("busType", ctypes.c_uint), ("data", s_xl_bus_params_data)] + + class XLchannelConfig(ctypes.Structure): _pack_ = 1 _fields_ = [ @@ -189,7 +237,7 @@ class XLchannelConfig(ctypes.Structure): ("channelBusCapabilities", ctypes.c_uint), ("isOnBus", ctypes.c_ubyte), ("connectedBusType", ctypes.c_uint), - ("busParams", ctypes.c_ubyte * 32), + ("busParams", XLbusParams), ("_doNotUse", ctypes.c_uint), ("driverVersion", ctypes.c_uint), ("interfaceVersion", ctypes.c_uint), From 3cf42bb20412ed7a054ebd2a89397211429ed052 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 12 Nov 2019 15:27:46 +0100 Subject: [PATCH 0350/1235] VectorBus - add methods to handle non message events (#708) --- can/interfaces/vector/canlib.py | 29 ++++++++++++++++ can/interfaces/vector/xlclass.py | 10 +++++- test/test_vector.py | 58 ++++++++++++++++++++++++++++---- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 40a4ea991..ed366b4c8 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -396,6 +396,9 @@ def _recv_internal(self, timeout): channel=channel, ) return msg, self._is_filtered + else: + self.handle_canfd_event(event) + else: event_count.value = 1 try: @@ -431,6 +434,8 @@ def _recv_internal(self, timeout): channel=channel, ) return msg, self._is_filtered + else: + self.handle_can_event(event) if end_time is not None and time.time() > end_time: return None, self._is_filtered @@ -447,6 +452,30 @@ def _recv_internal(self, timeout): # Wait a short time until we try again time.sleep(self.poll_interval) + def handle_can_event(self, event: xlclass.XLevent) -> None: + """Handle non-message CAN events. + + Method is called by :meth:`~can.interfaces.vector.VectorBus._recv_internal` + when `event.tag` is not `XL_CAN_EV_TAG_RX_OK` or `XL_CAN_EV_TAG_TX_OK`. + Subclasses can implement this method. + + :param event: XLevent that could have a `XL_CHIP_STATE`, `XL_TIMER` or `XL_SYNC_PULSE` tag. + :return: None + """ + pass + + def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: + """Handle non-message CAN FD events. + + Method is called by :meth:`~can.interfaces.vector.VectorBus._recv_internal` + when `event.tag` is not `XL_RECEIVE_MSG`. Subclasses can implement this method. + + :param event: `XLcanRxEvent` that could have a `XL_CAN_EV_TAG_RX_ERROR`, `XL_CAN_EV_TAG_TX_ERROR` + or `XL_CAN_EV_TAG_CHIP_STATE` tag. + :return: None + """ + pass + def send(self, msg, timeout=None): msg_id = msg.arbitration_id diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py index de799f12b..8c55bf058 100644 --- a/can/interfaces/vector/xlclass.py +++ b/can/interfaces/vector/xlclass.py @@ -35,6 +35,14 @@ class s_xl_can_ev_error(ctypes.Structure): _fields_ = [("errorCode", ctypes.c_ubyte), ("reserved", ctypes.c_ubyte * 95)] +class s_xl_chip_state(ctypes.Structure): + _fields_ = [ + ("busStatus", ctypes.c_ubyte), + ("txErrorCounter", ctypes.c_ubyte), + ("rxErrorCounter", ctypes.c_ubyte), + ] + + class s_xl_can_ev_chip_state(ctypes.Structure): _fields_ = [ ("busStatus", ctypes.c_ubyte), @@ -55,7 +63,7 @@ class s_xl_can_ev_sync_pulse(ctypes.Structure): # BASIC bus message structure class s_xl_tag_data(ctypes.Union): - _fields_ = [("msg", s_xl_can_msg)] + _fields_ = [("msg", s_xl_can_msg), ("chipState", s_xl_chip_state)] # CAN FD messages diff --git a/test/test_vector.py b/test/test_vector.py index 639b28de9..d99509df8 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -55,12 +55,6 @@ def setUp(self) -> None: 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) @@ -173,16 +167,42 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: self.assertEqual(canFdConf.tseg2Dbr, 15) def test_receive(self) -> None: + can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive) self.bus = can.Bus(channel=0, bustype="vector", _testing=True) self.bus.recv(timeout=0.05) can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() def test_receive_fd(self) -> None: + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( + side_effect=xlCanReceive + ) + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() + + def test_receive_non_msg_event(self) -> None: + can.interfaces.vector.canlib.xldriver.xlReceive = Mock( + side_effect=xlReceive_chipstate + ) + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.handle_can_event = Mock() + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() + self.bus.handle_can_event.assert_called() + + def test_receive_fd_non_msg_event(self) -> None: + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( + side_effect=xlCanReceive_chipstate + ) self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + self.bus.handle_canfd_event = Mock() self.bus.recv(timeout=0.05) can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() + self.bus.handle_canfd_event.assert_called() def test_send(self) -> None: self.bus = can.Bus(channel=0, bustype="vector", _testing=True) @@ -306,5 +326,31 @@ def xlCanReceive( return 0 +def xlReceive_chipstate( + port_handle: xlclass.XLportHandle, + event_count_p: ctypes.POINTER(ctypes.c_uint), + event: ctypes.POINTER(xlclass.XLevent), +) -> int: + event.tag = xldefine.XL_EventTags.XL_CHIP_STATE.value + event.tagData.chipState.busStatus = 8 + event.tagData.chipState.rxErrorCounter = 0 + event.tagData.chipState.txErrorCounter = 0 + event.timeStamp = 0 + event.chanIndex = 2 + return 0 + + +def xlCanReceive_chipstate( + port_handle: xlclass.XLportHandle, event: ctypes.POINTER(xlclass.XLcanRxEvent) +) -> int: + event.tag = xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_CHIP_STATE.value + event.tagData.canChipState.busStatus = 8 + event.tagData.canChipState.rxErrorCounter = 0 + event.tagData.canChipState.txErrorCounter = 0 + event.timeStamp = 0 + event.chanIndex = 2 + return 0 + + if __name__ == "__main__": unittest.main() From 49373824944addf3425afecb47c156c68971940c Mon Sep 17 00:00:00 2001 From: karl ding Date: Wed, 13 Nov 2019 21:34:31 -0800 Subject: [PATCH 0351/1235] Bump Python 3.8 from dev version in Travis CI (#723) Python 3.8 was released on October 14, 2019 and support was added to pyenv as well, which was the dependency needed by Travis. This promotes the version of Python 3.8 we were using from the development build to the official release. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2bea7900f..7b299feb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ python: # CPython; only 3.6 is supported - "3.6" - "3.7" - - 3.8-dev + - "3.8" - nightly # PyPy: - pypy3 @@ -39,7 +39,6 @@ jobs: allow_failures: # we allow all dev & nightly builds to fail, since these python versions might # still be very unstable - - python: 3.8-dev - python: nightly include: From f342c1c65e882b1254e16a4be6b559c9aeb3bcde Mon Sep 17 00:00:00 2001 From: Daniel Hrisca Date: Mon, 2 Dec 2019 22:44:43 +0200 Subject: [PATCH 0352/1235] fixes #732: add support for VN8900 xlGetChannelTime function (#733) --- can/interfaces/vector/canlib.py | 10 ++++++++-- can/interfaces/vector/xldriver.py | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ed366b4c8..cb1858062 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -282,8 +282,14 @@ def __init__( # Calculate time offset for absolute timestamps offset = xlclass.XLuint64() - xldriver.xlGetSyncTime(self.port_handle, offset) - self._time_offset = time.time() - offset.value * 1e-9 + try: + try: + xldriver.xlGetSyncTime(self.port_handle, offset) + except VectorError: + xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) + self._time_offset = time.time() - offset.value * 1e-9 + except VectorError: + self._time_offset = 0.0 self._is_filtered = False super().__init__(channel=channel, can_filters=can_filters, **kwargs) diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 9bb1a1083..337135755 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -89,6 +89,15 @@ def check_status(result, function, arguments): xlGetSyncTime.restype = xlclass.XLstatus xlGetSyncTime.errcheck = check_status +xlGetChannelTime = _xlapi_dll.xlGetChannelTime +xlGetChannelTime.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.POINTER(xlclass.XLuint64), +] +xlGetChannelTime.restype = xlclass.XLstatus +xlGetChannelTime.errcheck = check_status + xlClosePort = _xlapi_dll.xlClosePort xlClosePort.argtypes = [xlclass.XLportHandle] xlClosePort.restype = xlclass.XLstatus From b0d74607de0e411ea3b19347cee20cf8ccba0dcd Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Fri, 16 Aug 2019 20:53:00 +0200 Subject: [PATCH 0353/1235] Refactoring of BLF module Implement append mode Write a temporary header in case logging is not stopped gracefully Move ZLIB compression level to constructor, default to ZLIB default Improve unit testing Various refactoring to improve readability and slight performance improvements --- can/io/blf.py | 459 ++++++++++++++-------------- test/data/example_data.py | 10 + test/data/logfile.blf | Bin 682 -> 0 bytes test/data/test_CanErrorFrameExt.blf | Bin 0 -> 452 bytes test/data/test_CanFdMessage.blf | Bin 0 -> 564 bytes test/data/test_CanFdMessage64.blf | Bin 0 -> 612 bytes test/data/test_CanMessage.blf | Bin 0 -> 420 bytes test/data/test_CanMessage2.blf | Bin 0 -> 436 bytes test/listener_test.py | 4 +- test/logformats_test.py | 109 +++++-- 10 files changed, 335 insertions(+), 247 deletions(-) delete mode 100644 test/data/logfile.blf create mode 100644 test/data/test_CanErrorFrameExt.blf create mode 100644 test/data/test_CanFdMessage.blf create mode 100644 test/data/test_CanFdMessage64.blf create mode 100644 test/data/test_CanMessage.blf create mode 100644 test/data/test_CanMessage2.blf diff --git a/can/io/blf.py b/can/io/blf.py index 6b9ccebce..8df41ed5a 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -17,6 +17,7 @@ import datetime import time import logging +from typing import List from can.message import Message from can.listener import Listener @@ -30,9 +31,6 @@ class BLFParseError(Exception): LOG = logging.getLogger(__name__) -# 0 = unknown, 2 = CANoe -APPLICATION_ID = 5 - # signature ("LOGG"), header size, # application ID, application major, application minor, application build, # bin log major, bin log minor, bin log build, bin log patch, @@ -49,8 +47,8 @@ class BLFParseError(Exception): # flags, client index, object version, timestamp OBJ_HEADER_V1_STRUCT = struct.Struct(" len(data): - # Object continues in next log container - break - pos += OBJ_HEADER_BASE_STRUCT.size - - # Read rest of header - header_version = header[2] - if header_version == 1: - flags, _, _, timestamp = OBJ_HEADER_V1_STRUCT.unpack_from( - data, pos - ) - pos += OBJ_HEADER_V1_STRUCT.size - elif header_version == 2: - flags, _, _, timestamp, _ = OBJ_HEADER_V2_STRUCT.unpack_from( - data, pos - ) - pos += OBJ_HEADER_V2_STRUCT.size - else: - # Unknown header version - LOG.warning( - "Unknown object header version (%d)", header_version - ) - pos = next_pos - continue - - if flags == TIME_TEN_MICS: - factor = 10 * 1e-6 - else: - factor = 1e-9 - timestamp = timestamp * factor + self.start_timestamp - - # Both CAN message types have the same starting content - if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): - ( - channel, - flags, - dlc, - can_id, - can_data, - ) = CAN_MSG_STRUCT.unpack_from(data, pos) - msg = Message( - timestamp=timestamp, - arbitration_id=can_id & 0x1FFFFFFF, - is_extended_id=bool(can_id & CAN_MSG_EXT), - is_remote_frame=bool(flags & REMOTE_FLAG), - dlc=dlc, - data=can_data[:dlc], - channel=channel - 1, - ) - yield msg - elif obj_type == CAN_FD_MESSAGE: - ( - channel, - flags, - dlc, - can_id, - _, - _, - fd_flags, - _, - can_data, - ) = CAN_FD_MSG_STRUCT.unpack_from(data, pos) - length = dlc2len(dlc) - msg = Message( - timestamp=timestamp, - arbitration_id=can_id & 0x1FFFFFFF, - is_extended_id=bool(can_id & CAN_MSG_EXT), - is_remote_frame=bool(flags & REMOTE_FLAG), - is_fd=bool(fd_flags & EDL), - bitrate_switch=bool(fd_flags & BRS), - error_state_indicator=bool(fd_flags & ESI), - dlc=length, - data=can_data[:length], - channel=channel - 1, - ) - yield msg - elif obj_type == CAN_FD_MESSAGE_64: - ( - channel, - dlc, - _, - _, - can_id, - _, - fd_flags, - ) = CAN_FD_MSG_64_STRUCT.unpack_from(data, pos)[:7] - length = dlc2len(dlc) - can_data = struct.unpack_from( - "<{}s".format(length), data, pos + CAN_FD_MSG_64_STRUCT.size - )[0] - msg = Message( - timestamp=timestamp, - arbitration_id=can_id & 0x1FFFFFFF, - is_extended_id=bool(can_id & CAN_MSG_EXT), - is_remote_frame=bool(fd_flags & REMOTE_FLAG_64), - is_fd=bool(fd_flags & EDL_64), - bitrate_switch=bool(fd_flags & BRS_64), - error_state_indicator=bool(fd_flags & ESI_64), - dlc=length, - data=can_data[:length], - channel=channel - 1, - ) - yield msg - elif obj_type == CAN_ERROR_EXT: - ( - channel, - _, - _, - _, - _, - dlc, - _, - can_id, - _, - can_data, - ) = CAN_ERROR_EXT_STRUCT.unpack_from(data, pos) - msg = Message( - timestamp=timestamp, - is_error_frame=True, - is_extended_id=bool(can_id & CAN_MSG_EXT), - arbitration_id=can_id & 0x1FFFFFFF, - dlc=dlc, - data=can_data[:dlc], - channel=channel - 1, - ) - yield msg - # else: - # LOG.warning("Unknown object type (%d)", obj_type) - - pos = next_pos - - # save the remaining data that could not be processed - tail = data[pos:] + def _parse_container(self, data): + if self._tail: + data = b"".join((self._tail, data)) + self._pos = 0 + try: + yield from self._parse_data(data) + except struct.error: + # Container data exhausted + # Save the remaining data that could not be processed + self._tail = data[self._pos :] + + def _parse_data(self, data): + """Optimized inner loop by making local copies of global variables + and class members and hardcoding some values.""" + unpack_obj_header_base = OBJ_HEADER_BASE_STRUCT.unpack_from + obj_header_base_size = OBJ_HEADER_BASE_STRUCT.size + unpack_obj_header_v1 = OBJ_HEADER_V1_STRUCT.unpack_from + obj_header_v1_size = OBJ_HEADER_V1_STRUCT.size + unpack_obj_header_v2 = OBJ_HEADER_V2_STRUCT.unpack_from + obj_header_v2_size = OBJ_HEADER_V2_STRUCT.size + unpack_can_msg = CAN_MSG_STRUCT.unpack_from + unpack_can_fd_msg = CAN_FD_MSG_STRUCT.unpack_from + unpack_can_fd_64_msg = CAN_FD_MSG_64_STRUCT.unpack_from + can_fd_64_msg_size = CAN_FD_MSG_64_STRUCT.size + unpack_can_error_ext = CAN_ERROR_EXT_STRUCT.unpack_from + + start_timestamp = self.start_timestamp + pos = 0 + + # Loop until a struct unpack raises an exception + while True: + self._pos = pos + header = unpack_obj_header_base(data, pos) + signature, _, header_version, obj_size, obj_type = header + if signature != b"LOBJ": + raise BLFParseError() - self.stop() + # Calculate position of next object + next_pos = pos + obj_size + if obj_type != CAN_FD_MESSAGE_64: + # Add padding bytes + next_pos += obj_size % 4 + pos += obj_header_base_size + + # Read rest of header + if header_version == 1: + flags, _, _, timestamp = unpack_obj_header_v1(data, pos) + pos += obj_header_v1_size + elif header_version == 2: + flags, _, _, timestamp = unpack_obj_header_v2(data, pos) + pos += obj_header_v2_size + else: + LOG.warning("Unknown object header version (%d)", header_version) + pos = next_pos + continue + + # Calculate absolute timestamp in seconds + factor = 1e-5 if flags == 1 else 1e-9 + timestamp = timestamp * factor + start_timestamp + + if obj_type == CAN_MESSAGE or obj_type == CAN_MESSAGE2: + channel, flags, dlc, can_id, can_data = unpack_can_msg(data, pos) + yield Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=bool(can_id & CAN_MSG_EXT), + is_remote_frame=bool(flags & REMOTE_FLAG), + dlc=dlc, + data=can_data[:dlc], + channel=channel - 1, + ) + elif obj_type == CAN_ERROR_EXT: + members = unpack_can_error_ext(data, pos) + channel = members[0] + dlc = members[5] + can_id = members[7] + can_data = members[9] + yield Message( + timestamp=timestamp, + is_error_frame=True, + is_extended_id=bool(can_id & CAN_MSG_EXT), + arbitration_id=can_id & 0x1FFFFFFF, + dlc=dlc, + data=can_data[:dlc], + channel=channel - 1, + ) + elif obj_type == CAN_FD_MESSAGE: + members = unpack_can_fd_msg(data, pos) + channel, flags, dlc, can_id, _, _, fd_flags, valid_bytes, can_data = ( + members + ) + yield Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=bool(can_id & CAN_MSG_EXT), + is_remote_frame=bool(flags & REMOTE_FLAG), + is_fd=bool(fd_flags & 0x1), + bitrate_switch=bool(fd_flags & 0x2), + error_state_indicator=bool(fd_flags & 0x4), + dlc=dlc2len(dlc), + data=can_data[:valid_bytes], + channel=channel - 1, + ) + elif obj_type == CAN_FD_MESSAGE_64: + members = unpack_can_fd_64_msg(data, pos)[:7] + channel, dlc, valid_bytes, _, can_id, _, fd_flags = members + pos += can_fd_64_msg_size + yield Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=bool(can_id & CAN_MSG_EXT), + is_remote_frame=bool(fd_flags & 0x0010), + is_fd=bool(fd_flags & 0x1000), + bitrate_switch=bool(fd_flags & 0x2000), + error_state_indicator=bool(fd_flags & 0x4000), + dlc=dlc2len(dlc), + data=data[pos : pos + valid_bytes], + channel=channel - 1, + ) + + pos = next_pos class BLFWriter(BaseIOHandler, Listener): @@ -352,27 +315,72 @@ class BLFWriter(BaseIOHandler, Listener): """ #: Max log container size of uncompressed data - MAX_CACHE_SIZE = 128 * 1024 + max_container_size = 128 * 1024 - #: ZLIB compression level - COMPRESSION_LEVEL = 9 + #: Application identifier for the log writer + application_id = 5 - def __init__(self, file, channel=1): + def __init__( + self, file, append: bool = False, channel: int = 1, compression_level: int = -1 + ): """ :param file: a path-like object or as file-like object to write to - If this is a file-like object, is has to opened in binary - write mode, not text write mode. + If this is a file-like object, is has to opened in mode "wb+". + :param channel: + Default channel to log as if not specified by the interface. + :param append: + Append messages to an existing log file. + :param compression_level: + An integer from 0 to 9 or -1 controlling the level of compression. + 1 (Z_BEST_SPEED) is fastest and produces the least compression. + 9 (Z_BEST_COMPRESSION) is slowest and produces the most. + 0 means that data will be stored without processing. + The default value is -1 (Z_DEFAULT_COMPRESSION). + Z_DEFAULT_COMPRESSION represents a default compromise between + speed and compression (currently equivalent to level 6). """ - super().__init__(file, mode="wb") + mode = "rb+" if append else "wb" + try: + super().__init__(file, mode=mode) + except FileNotFoundError: + # Trying to append to a non-existing file, create a new one + append = False + mode = "wb" + super().__init__(file, mode=mode) + assert self.file is not None self.channel = channel - # Header will be written after log is done - self.file.write(b"\x00" * FILE_HEADER_SIZE) - self.cache = [] - self.cache_size = 0 - self.count_of_objects = 0 - self.uncompressed_size = FILE_HEADER_SIZE - self.start_timestamp = None - self.stop_timestamp = None + self.compression_level = compression_level + self._buffer: List[bytes] = [] + self._buffer_size = 0 + if append: + # Parse file header + data = self.file.read(FILE_HEADER_STRUCT.size) + header = FILE_HEADER_STRUCT.unpack(data) + if header[0] != b"LOGG": + raise BLFParseError("Unexpected file format") + self.uncompressed_size = header[11] + self.object_count = header[12] + self.start_timestamp = systemtime_to_timestamp(header[14:22]) + self.stop_timestamp = systemtime_to_timestamp(header[22:30]) + # Jump to the end of the file + self.file.seek(0, 2) + else: + self.object_count = 0 + self.uncompressed_size = FILE_HEADER_SIZE + self.start_timestamp = None + self.stop_timestamp = None + # Write a default header which will be updated when stopped + self._write_header(FILE_HEADER_SIZE) + + def _write_header(self, filesize): + header = [b"LOGG", FILE_HEADER_SIZE, self.application_id, 0, 0, 0, 2, 6, 8, 1] + # The meaning of "count of objects read" is unknown + header.extend([filesize, self.uncompressed_size, self.object_count, 0]) + header.extend(timestamp_to_systemtime(self.start_timestamp)) + header.extend(timestamp_to_systemtime(self.stop_timestamp)) + self.file.write(FILE_HEADER_STRUCT.pack(*header)) + # Pad to header size + self.file.write(b"\x00" * (FILE_HEADER_SIZE - FILE_HEADER_STRUCT.size)) def on_message_received(self, msg): channel = channel2int(msg.channel) @@ -386,7 +394,7 @@ def on_message_received(self, msg): if msg.is_extended_id: arb_id |= CAN_MSG_EXT flags = REMOTE_FLAG if msg.is_remote_frame else 0 - data = bytes(msg.data) + can_data = bytes(msg.data) if msg.is_error_frame: data = CAN_ERROR_EXT_STRUCT.pack( @@ -399,7 +407,7 @@ def on_message_received(self, msg): 0, # frame length arb_id, 0, # ext flags - data, + can_data, ) self._add_object(CAN_ERROR_EXT, data, msg.timestamp) elif msg.is_fd: @@ -409,11 +417,19 @@ def on_message_received(self, msg): if msg.error_state_indicator: fd_flags |= ESI data = CAN_FD_MSG_STRUCT.pack( - channel, flags, len2dlc(msg.dlc), arb_id, 0, 0, fd_flags, msg.dlc, data + channel, + flags, + len2dlc(msg.dlc), + arb_id, + 0, + 0, + fd_flags, + len(can_data), + can_data, ) self._add_object(CAN_FD_MESSAGE, data, msg.timestamp) else: - data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, data) + data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, can_data) self._add_object(CAN_MESSAGE, data, msg.timestamp) def log_event(self, text, timestamp=None): @@ -451,60 +467,59 @@ def _add_object(self, obj_type, data, timestamp=None): ) obj_header = OBJ_HEADER_V1_STRUCT.pack(TIME_ONE_NANS, 0, 0, max(timestamp, 0)) - self.cache.append(base_header) - self.cache.append(obj_header) - self.cache.append(data) + self._buffer.append(base_header) + self._buffer.append(obj_header) + self._buffer.append(data) padding_size = len(data) % 4 if padding_size: - self.cache.append(b"\x00" * padding_size) + self._buffer.append(b"\x00" * padding_size) - self.cache_size += obj_size + padding_size - self.count_of_objects += 1 - if self.cache_size >= self.MAX_CACHE_SIZE: + self._buffer_size += obj_size + padding_size + self.object_count += 1 + if self._buffer_size >= self.max_container_size: self._flush() def _flush(self): - """Compresses and writes data in the cache to file.""" + """Compresses and writes data in the buffer to file.""" if self.file.closed: return - cache = b"".join(self.cache) - if not cache: + buffer = b"".join(self._buffer) + if not buffer: # Nothing to write return - uncompressed_data = cache[: self.MAX_CACHE_SIZE] - # Save data that comes after max size to next round - tail = cache[self.MAX_CACHE_SIZE :] - self.cache = [tail] - self.cache_size = len(tail) - compressed_data = zlib.compress(uncompressed_data, self.COMPRESSION_LEVEL) - obj_size = ( - OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size + len(compressed_data) - ) + uncompressed_data = memoryview(buffer)[: self.max_container_size] + # Save data that comes after max size to next container + tail = buffer[self.max_container_size :] + self._buffer = [tail] + self._buffer_size = len(tail) + if not self.compression_level: + data = uncompressed_data + method = NO_COMPRESSION + else: + data = zlib.compress(uncompressed_data, self.compression_level) + method = ZLIB_DEFLATE + obj_size = OBJ_HEADER_BASE_STRUCT.size + LOG_CONTAINER_STRUCT.size + len(data) base_header = OBJ_HEADER_BASE_STRUCT.pack( b"LOBJ", OBJ_HEADER_BASE_STRUCT.size, 1, obj_size, LOG_CONTAINER ) - container_header = LOG_CONTAINER_STRUCT.pack( - ZLIB_DEFLATE, len(uncompressed_data) - ) + container_header = LOG_CONTAINER_STRUCT.pack(method, len(uncompressed_data)) self.file.write(base_header) self.file.write(container_header) - self.file.write(compressed_data) + self.file.write(data) # Write padding bytes self.file.write(b"\x00" * (obj_size % 4)) - self.uncompressed_size += OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size + self.uncompressed_size += OBJ_HEADER_BASE_STRUCT.size + self.uncompressed_size += LOG_CONTAINER_STRUCT.size self.uncompressed_size += len(uncompressed_data) def stop(self): """Stops logging and closes the file.""" self._flush() - filesize = self.file.tell() + if self.file.seekable(): + filesize = self.file.tell() + # Write header in the beginning of the file + self.file.seek(0) + self._write_header(filesize) + else: + LOG.error("Could not write BLF header since file is not seekable") super().stop() - - # Write header in the beginning of the file - header = [b"LOGG", FILE_HEADER_SIZE, APPLICATION_ID, 0, 0, 0, 2, 6, 8, 1] - # The meaning of "count of objects read" is unknown - header.extend([filesize, self.uncompressed_size, self.count_of_objects, 0]) - header.extend(timestamp_to_systemtime(self.start_timestamp)) - header.extend(timestamp_to_systemtime(self.stop_timestamp)) - with open(self.file.name, "r+b") as f: - f.write(FILE_HEADER_STRUCT.pack(*header)) diff --git a/test/data/example_data.py b/test/data/example_data.py index b91fd3bfd..773212c32 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -108,6 +108,16 @@ def sort_messages(messages): ) +TEST_MESSAGES_CAN_FD = sort_messages( + [ + Message(is_fd=True, data=range(64)), + Message(is_fd=True, data=range(8)), + Message(is_fd=True, bitrate_switch=True), + Message(is_fd=True, error_state_indicator=True), + ] +) + + TEST_MESSAGES_REMOTE_FRAMES = sort_messages( [ Message( diff --git a/test/data/logfile.blf b/test/data/logfile.blf deleted file mode 100644 index 98cafe214083ab24fab9f5d927a38728f7f29679..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 682 zcmebAcXyw_z`(!?#7t})jH{S{3?PUA2>^j05UYS$^dWrwoxB7X7#YNY?%)Di33U#` z3Kod{6>~m+EdLagkdR`KkiamBhsTE}pdf3SDRV^6L}gYNW5dlGHf=2a;c(TO;b_E! ztJx|0n^){V!erDgsl^_2nCnQ6!<^J29r0!H%qPTMuFZe?Z>r>)`JIxnb2&Ec5`Lr| zp^%sA6!x4^MA$h@{IT|VuCJ4YE+~m7x5!)U+)-Ka^9L(S<2n(8M31!`$q5c-?lL!J zwu%P|6ia-ZbvWmcLd&~6&Xi(16W`_;hg*dB78Fc(3ES@dO~^%7JEBKYs!idmRKQoS z6R*~P*_)K^z%Ze{Rl?`NE60yZn%3Ao_E2K)s&X?}%ky;kZl{ct-ysvfN*_J%di6Zd zG-p10uI5e?NceGj(~DK7p4v;B$RuR)KC_=Nf8ya(_6hl%=Ikqeh0oO1 z@2J$0dKW9kCetu+qM$R6r@ZEa`}G_f(ivuQraTB>FW$~NA@_L0lmF(XKWjWpgpZgn z@JOn#e(%YrGpD%0Y2ArmU1b~`3e}wl+Z>aWcB`a45*88@6cwI6ValXw6Q@oN4+seg z3k(gue&NccYZtFxHa9RaGBY$a-o9bWCbx6D=bb1!8}v_~VOulr8?%T9jepK^_%PVt z+vw>Q!`1eCBbUbXjy3zUZ-xZi6Am#d&XV!Pg1q>Do4W1pEBFT99fQlcV R(Vqhc7#JG9)g1yxJ^(q8;uZh^ diff --git a/test/data/test_CanErrorFrameExt.blf b/test/data/test_CanErrorFrameExt.blf new file mode 100644 index 0000000000000000000000000000000000000000..f7ea6eb35e738bdbaaaf566e8571178f513b7d77 GIT binary patch literal 452 zcmebAcXyw_z`$@t$__{}FdSh7Q*fFI!X-!=K=lz+NI-#)zmt~$10%x%pgJxjwGB`< zNL~Rb?*Qa`0x`&af`SYTN>CsusH9{J1TLXz5m5g1ep&#VF zDL{LX-B$wSg8d@8l)Gz{nr~G>;2O-v=lg zB(DIJuK@B>fEeUHK|uxvB`6RSR5Eq}f>0nxD|eW=3J4e&nV4Bv+1NQaxwv_F`S=9{ zg@i>!#l$5frKDwK<>VC*v+3@?}w`fO12F~Y+UMcWx31|Fa~ I5D)-j08ooc{r~^~ literal 0 HcmV?d00001 diff --git a/test/data/test_CanFdMessage64.blf b/test/data/test_CanFdMessage64.blf new file mode 100644 index 0000000000000000000000000000000000000000..f26eccfde811a0f386b238197c4736e1f1e8e6e0 GIT binary patch literal 612 zcmebAcXyw_z`$@t$__{}Fr+YnDL4(~LIm-U7Z~wKk|5>d@8l)Gz{ubLG>;2Op90i; zkh}s=9^~d!AO^WlP>_K^2?_+299%+yAPor0fuI8jW&*(~AlMBAXU^Px_a6ut7@3$^ zSlQS)IJvlac=`AR1cih}M8(7`T5gzU++RpGW@Br0;fB+B!0DgLGGXMYp literal 0 HcmV?d00001 diff --git a/test/data/test_CanMessage.blf b/test/data/test_CanMessage.blf new file mode 100644 index 0000000000000000000000000000000000000000..f0ad2036526703ecb5051648b98154c2b888625f GIT binary patch literal 420 zcmebAcXyw_z`$@t$__{}Ff3sNQ*fFI!X-!wK=lz+NI-#)zmt~$10zENP#qVN+5{*Y zB(DIJHvsZMR)X9oD9FH|1O}@KGvufT**^tnF0%b4Kt2e-%(DQp ri-8yxJ}~>wpa2dA4h9K^7t9EKHYoZS;qFG!c7}(62dEAN1b`R-W&bz1 literal 0 HcmV?d00001 diff --git a/test/data/test_CanMessage2.blf b/test/data/test_CanMessage2.blf new file mode 100644 index 0000000000000000000000000000000000000000..6beb4fa43cdac73a28ab7c6ec879a6e5a38e76a8 GIT binary patch literal 436 zcmebAcXyw_z`$@t$__{}Fl=E2Q*fFI!X-#5K=lz+NI-#)zmt~$10w^-O}>oVvzd;1sNEWpg>Si$=C%5Let7SX0F Date: Mon, 16 Dec 2019 22:18:52 +0200 Subject: [PATCH 0354/1235] Add support for "Robotell" USB-CAN interface (#731) --- can/interfaces/__init__.py | 1 + can/interfaces/robotell.py | 400 +++++++++++++++ doc/interfaces.rst | 1 + doc/interfaces/robotell.rst | 37 ++ test/test_robotell.py | 946 ++++++++++++++++++++++++++++++++++++ 5 files changed, 1385 insertions(+) create mode 100644 can/interfaces/robotell.py create mode 100644 doc/interfaces/robotell.rst create mode 100644 test/test_robotell.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index a163ad101..2f00d0309 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -20,6 +20,7 @@ "neovi": ("can.interfaces.ics_neovi", "NeoViBus"), "vector": ("can.interfaces.vector", "VectorBus"), "slcan": ("can.interfaces.slcan", "slcanBus"), + "robotell": ("can.interfaces.robotell", "robotellBus"), "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), "systec": ("can.interfaces.systec", "UcanBus"), "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py new file mode 100644 index 000000000..cb35aa774 --- /dev/null +++ b/can/interfaces/robotell.py @@ -0,0 +1,400 @@ +""" +Interface for Chinese Robotell compatible interfaces (win32/linux). +""" + +import time +import logging + +from can import BusABC, Message + +logger = logging.getLogger(__name__) + +try: + import serial +except ImportError: + logger.warning( + "You won't be able to use the Robotell can backend without " + "the serial module installed!" + ) + serial = None + + +class robotellBus(BusABC): + """ + robotell interface + """ + + _PACKET_HEAD = 0xAA # Frame starts with 2x FRAME_HEAD bytes + _PACKET_TAIL = 0x55 # Frame ends with 2x FRAME_END bytes + _PACKET_ESC = ( + 0xA5 # Escape char before any HEAD, TAIL or ESC chat (including in checksum) + ) + + _CAN_CONFIG_CHANNEL = 0xFF # Configuration channel of CAN + _CAN_SERIALBPS_ID = 0x01FFFE90 # USB Serial port speed + _CAN_ART_ID = 0x01FFFEA0 # Automatic retransmission + _CAN_ABOM_ID = 0x01FFFEB0 # Automatic bus management + _CAN_RESET_ID = 0x01FFFEC0 # ID for initialization + _CAN_BAUD_ID = 0x01FFFED0 # CAN baud rate + _CAN_FILTER_BASE_ID = 0x01FFFEE0 # ID for first filter (filter0) + _CAN_FILTER_MAX_ID = 0x01FFFEE0 + 13 # ID for the last filter (filter13) + _CAN_INIT_FLASH_ID = 0x01FFFEFF # Restore factory settings + _CAN_READ_SERIAL1 = 0x01FFFFF0 # Read first part of device serial number + _CAN_READ_SERIAL2 = 0x01FFFFF1 # Read first part of device serial number + _MAX_CAN_BAUD = 1000000 # Maximum supported CAN baud rate + _FILTER_ID_MASK = 0x0000000F # Filter ID mask + _CAN_FILTER_EXTENDED = 0x40000000 # Enable mask + _CAN_FILTER_ENABLE = 0x80000000 # Enable filter + + _CAN_STANDARD_FMT = 0 # Standard message ID + _CAN_EXTENDED_FMT = 1 # 29 Bit extended format ID + _CAN_DATA_FRAME = 0 # Send data frame + _CAN_REMOTE_FRAME = 1 # Request remote frame + + def __init__( + self, channel, ttyBaudrate=115200, bitrate=None, rtscts=False, **kwargs + ): + """ + :param str channel: + port of underlying serial or usb device (e.g. /dev/ttyUSB0, COM8, ...) + Must not be empty. + :param int ttyBaudrate: + baudrate of underlying serial or usb device + :param int bitrate: + CAN Bitrate in bit/s. Value is stored in the adapter and will be used as default if no bitrate is specified + :param bool rtscts: + turn hardware handshake (RTS/CTS) on and off + """ + + if not channel: # if None or empty + raise TypeError("Must specify a serial port.") + if "@" in channel: + (channel, ttyBaudrate) = channel.split("@") + self.serialPortOrig = serial.serial_for_url( + channel, baudrate=ttyBaudrate, rtscts=rtscts + ) + + ## Disable flushing queued config ACKs on lookup channel (for unit tests) + self._loopback_test = channel == "loop://" + + self._rxbuffer = bytearray() # raw bytes from the serial port + self._rxmsg = [] # extracted CAN messages waiting to be read + self._configmsg = [] # extracted config channel messages + + self._writeconfig(self._CAN_RESET_ID, 0) # Not sure if this is really necessary + + if bitrate is not None: + self.set_bitrate(bitrate) + + self.channel_info = "Robotell USB-CAN s/n %s on %s" % ( + self.get_serial_number(1), + channel, + ) + logger.info("Using device: {}".format(self.channel_info)) + + super().__init__(channel=channel, **kwargs) + + def set_bitrate(self, bitrate): + """ + :raise ValueError: if *bitrate* is greater than 1000000 + :param int bitrate: + Bitrate in bit/s + """ + if bitrate <= self._MAX_CAN_BAUD: + self._writeconfig(self._CAN_BAUD_ID, bitrate) + else: + raise ValueError( + "Invalid bitrate, must be less than " + str(self._MAX_CAN_BAUD) + ) + + def set_auto_retransmit(self, retrans_flag): + """ + :param bool retrans_flag: + Enable/disable automatic retransmission of unacknowledged CAN frames + """ + self._writeconfig(self._CAN_ART_ID, 1 if retrans_flag else 0) + + def set_auto_bus_management(self, auto_man): + """ + :param bool auto_man: + Enable/disable automatic bus management + """ + ## Not sure what "automatic bus managemenet" does. Does not seem to control + ## automatic ACK of CAN frames (listen only mode) + self._writeconfig(self._CAN_ABOM_ID, 1 if auto_man else 0) + + def set_serial_rate(self, serial_bps): + """ + :param int serial_bps: + Set the baud rate of the serial port (not CAN) interface + """ + self._writeconfig(self._CAN_SERIALBPS_ID, serial_bps) + + def set_hw_filter(self, filterid, enabled, msgid_value, msgid_mask, extended_msg): + """ + :raise ValueError: if *filterid* is not between 1 and 14 + :param int filterid: + ID of filter (1-14) + :param bool enabled: + This filter is enabled + :param int msgid_value: + CAN message ID to filter on. The test unit does not accept an extented message ID unless bit 31 of the ID was set. + :param int msgid_mask: + Mask to apply to CAN messagge ID + :param bool extended_msg: + Filter operates on extended format messages + """ + if filterid < 1 or filterid > 14: + raise ValueError("Invalid filter ID. ID must be between 0 and 13") + else: + configid = self._CAN_FILTER_BASE_ID + (filterid - 1) + msgid_value += self._CAN_FILTER_ENABLE if enabled else 0 + msgid_value += self._CAN_FILTER_EXTENDED if extended_msg else 0 + self._writeconfig(configid, msgid_value, msgid_mask) + + def _getconfigsize(self, configid): + if configid == self._CAN_ART_ID or configid == self._CAN_ABOM_ID: + return 1 + if configid == self._CAN_BAUD_ID or configid == self._CAN_INIT_FLASH_ID: + return 4 + if configid == self._CAN_SERIALBPS_ID: + return 4 + if configid == self._CAN_READ_SERIAL1 or configid <= self._CAN_READ_SERIAL2: + return 8 + if configid >= self._CAN_FILTER_BASE_ID and configid <= self._CAN_FILTER_MAX_ID: + return 8 + return 0 + + def _readconfig(self, configid, timeout): + self._writemessage( + msgid=configid, + msgdata=bytearray(8), + datalen=self._getconfigsize(configid), + msgchan=self._CAN_CONFIG_CHANNEL, + msgformat=self._CAN_EXTENDED_FMT, + msgtype=self._CAN_REMOTE_FRAME, + ) + # Read message from config channel with result. Flush any previously pending config messages + newmsg = self._readmessage(not self._loopback_test, True, timeout) + if newmsg is None: + logger.warning( + "Timeout waiting for response when reading config value {:04X}.".format( + configid + ) + ) + return None + return newmsg[4:12] + + def _writeconfig(self, configid, value, value2=0): + configsize = self._getconfigsize(configid) + configdata = bytearray(configsize) + if configsize >= 1: + configdata[0] = value & 0xFF + if configsize >= 4: + configdata[1] = (value >> 8) & 0xFF + configdata[2] = (value >> 16) & 0xFF + configdata[3] = (value >> 24) & 0xFF + if configsize >= 8: + configdata[4] = value2 & 0xFF + configdata[5] = (value2 >> 8) & 0xFF + configdata[6] = (value2 >> 16) & 0xFF + configdata[7] = (value2 >> 24) & 0xFF + self._writemessage( + msgid=configid, + msgdata=configdata, + datalen=configsize, + msgchan=self._CAN_CONFIG_CHANNEL, + msgformat=self._CAN_EXTENDED_FMT, + msgtype=self._CAN_DATA_FRAME, + ) + # Read message from config channel to verify. Flush any previously pending config messages + newmsg = self._readmessage(not self._loopback_test, True, 1) + if newmsg is None: + logger.warning( + "Timeout waiting for response when writing config value " + + str(configid) + ) + + def _readmessage(self, flushold, cfgchannel, timeout): + header = bytearray([self._PACKET_HEAD, self._PACKET_HEAD]) + terminator = bytearray([self._PACKET_TAIL, self._PACKET_TAIL]) + + msgqueue = self._configmsg if cfgchannel else self._rxmsg + if flushold: + del msgqueue[:] + + # read what is already in serial port receive buffer - unless we are doing loopback testing + if not self._loopback_test: + while self.serialPortOrig.in_waiting: + self._rxbuffer += self.serialPortOrig.read() + + # loop until we have read an appropriate message + start = time.time() + time_left = timeout + while True: + # make sure first bytes in RX buffer is a new packet header + headpos = self._rxbuffer.find(header) + if headpos > 0: + # data does not start with expected header bytes. Log error and ignore garbage + logger.warning("Ignoring extra " + str(headpos) + " garbage bytes") + del self._rxbuffer[:headpos] + headpos = self._rxbuffer.find(header) # should now be at index 0! + + # check to see if we have a complete packet in the RX buffer + termpos = self._rxbuffer.find(terminator) + if headpos == 0 and termpos > headpos: + # copy packet into message structure and un-escape bytes + newmsg = bytearray() + idx = headpos + len(header) + while idx < termpos: + if self._rxbuffer[idx] == self._PACKET_ESC: + idx += 1 + newmsg.append(self._rxbuffer[idx]) + idx += 1 + del self._rxbuffer[: termpos + len(terminator)] + + # Check one - make sure message structure is the correct length + if len(newmsg) == 17: + # Check two - verify the checksum + cs = 0 + for idx in range(16): + cs = (cs + newmsg[idx]) & 0xFF + if newmsg[16] == cs: + # OK, valid message - place it in the correct queue + if newmsg[13] == 0xFF: ## Check for config channel + self._configmsg.append(newmsg) + else: + self._rxmsg.append(newmsg) + else: + logger.warning("Incorrect message checksum, discarded message") + else: + logger.warning( + "Invalid message structure length " + + str(len(newmsg)) + + ", ignoring message" + ) + + # Check if we have a message in the desired queue - if so copy and return + if len(msgqueue) > 0: + newmsg = msgqueue[0] + del msgqueue[:1] + return newmsg + + # if we still don't have a complete message, do a blocking read + self.serialPortOrig.timeout = time_left + byte = self.serialPortOrig.read() + if byte: + self._rxbuffer += byte + # If there is time left, try next one with reduced timeout + if timeout is not None: + time_left = timeout - (time.time() - start) + if time_left <= 0: + return None + + def _writemessage(self, msgid, msgdata, datalen, msgchan, msgformat, msgtype): + msgbuf = bytearray(17) # Message structure plus checksum byte + + msgbuf[0] = msgid & 0xFF + msgbuf[1] = (msgid >> 8) & 0xFF + msgbuf[2] = (msgid >> 16) & 0xFF + msgbuf[3] = (msgid >> 24) & 0xFF + + if msgtype == self._CAN_DATA_FRAME: + for idx in range(datalen): + msgbuf[idx + 4] = msgdata[idx] + + msgbuf[12] = datalen + msgbuf[13] = msgchan + msgbuf[14] = msgformat + msgbuf[15] = msgtype + + cs = 0 + for idx in range(16): + cs = (cs + msgbuf[idx]) & 0xFF + msgbuf[16] = cs + + packet = bytearray() + packet.append(self._PACKET_HEAD) + packet.append(self._PACKET_HEAD) + for msgbyte in msgbuf: + if ( + msgbyte == self._PACKET_ESC + or msgbyte == self._PACKET_HEAD + or msgbyte == self._PACKET_TAIL + ): + packet.append(self._PACKET_ESC) + packet.append(msgbyte) + packet.append(self._PACKET_TAIL) + packet.append(self._PACKET_TAIL) + + self.serialPortOrig.write(packet) + self.serialPortOrig.flush() + + def flush(self): + del self._rxbuffer[:] + del self._rxmsg[:] + del self._configmsg[:] + while self.serialPortOrig.in_waiting: + self.serialPortOrig.read() + + def _recv_internal(self, timeout): + msgbuf = self._readmessage(False, False, timeout) + if msgbuf is not None: + msg = Message( + arbitration_id=msgbuf[0] + + (msgbuf[1] << 8) + + (msgbuf[2] << 16) + + (msgbuf[3] << 24), + is_extended_id=(msgbuf[14] == self._CAN_EXTENDED_FMT), + timestamp=time.time(), # Better than nothing... + is_remote_frame=(msgbuf[15] == self._CAN_REMOTE_FRAME), + dlc=msgbuf[12], + data=msgbuf[4 : 4 + msgbuf[12]], + ) + return msg, False + return None, False + + def send(self, msg, timeout=None): + if timeout != self.serialPortOrig.write_timeout: + self.serialPortOrig.write_timeout = timeout + self._writemessage( + msg.arbitration_id, + msg.data, + msg.dlc, + 0, + self._CAN_EXTENDED_FMT if msg.is_extended_id else self._CAN_STANDARD_FMT, + self._CAN_REMOTE_FRAME if msg.is_remote_frame else self._CAN_DATA_FRAME, + ) + + def shutdown(self): + self.serialPortOrig.close() + + def fileno(self): + if hasattr(self.serialPortOrig, "fileno"): + return self.serialPortOrig.fileno() + # Return an invalid file descriptor on Windows + return -1 + + def get_serial_number(self, timeout): + """Get serial number of the slcan interface. + :type timeout: int or None + :param timeout: + seconds to wait for serial number or None to wait indefinitely + :rtype str or None + :return: + None on timeout or a str object. + """ + + sn1 = self._readconfig(self._CAN_READ_SERIAL1, timeout) + if sn1 is None: + return None + sn2 = self._readconfig(self._CAN_READ_SERIAL2, timeout) + if sn2 is None: + return None + + serial = "" + for idx in range(0, 8, 2): + serial += "{:02X}{:02X}-".format(sn1[idx], sn1[idx + 1]) + for idx in range(0, 4, 2): + serial += "{:02X}{:02X}-".format(sn2[idx], sn2[idx + 1]) + return serial[:-1] diff --git a/doc/interfaces.rst b/doc/interfaces.rst index a19dc7e84..bd7a0d1df 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -15,6 +15,7 @@ The available interfaces are: interfaces/kvaser interfaces/serial interfaces/slcan + interfaces/robotell interfaces/ixxat interfaces/pcan interfaces/usb2can diff --git a/doc/interfaces/robotell.rst b/doc/interfaces/robotell.rst new file mode 100644 index 000000000..064e21725 --- /dev/null +++ b/doc/interfaces/robotell.rst @@ -0,0 +1,37 @@ +.. _robotell: + +Chinese CAN-USB interface (mfg. Robotell etc.) +============================================== + +An USB to CAN adapter sold on Aliexpress, etc. with the manufacturer name Robotell printed on the case. +There is also a USB stick version with a clear case. If the description or screenshots refer to ``EmbededDebug`` or ``EmbededConfig`` +the device should be compatible with this driver. +These USB devices are based on a STM32 controller with a CH340 serial interface and use a binary protocol - NOT compatible with SLCAN + +See `https://www.amobbs.com/thread-4651667-1-1.html `_ for some background on these devices. + +This driver directly uses either the local or remote (not tested) serial port. +Remote serial ports will be specified via special URL. Both raw TCP sockets as also RFC2217 ports are supported. + +Usage: use ``port or URL[@baurate]`` to open the device. +For example use ``/dev/ttyUSB0@115200`` or ``COM4@9600`` for local serial ports and +``socket://192.168.254.254:5000`` or ``rfc2217://192.168.254.254:5000`` for remote ports. + + +Supported devices +----------------- + +.. todo:: Document this. + + +Bus +--- + +.. autoclass:: can.interfaces.robotell.robotellBus + :members: + + +Internals +--------- + +.. todo:: Document the internals of robotell interface. diff --git a/test/test_robotell.py b/test/test_robotell.py new file mode 100644 index 000000000..58e2d9a7f --- /dev/null +++ b/test/test_robotell.py @@ -0,0 +1,946 @@ +#!/usr/bin/env python +# coding: utf-8 + +import unittest +import can + + +class robotellTestCase(unittest.TestCase): + def setUp(self): + # will log timeout messages since we are not feeding ack messages to the serial port at this stage + self.bus = can.Bus("loop://", bustype="robotell") + self.serial = self.bus.serialPortOrig + self.serial.read(self.serial.in_waiting) + + def tearDown(self): + self.bus.shutdown() + + def test_recv_extended(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0xA5, + 0xAA, + 0xA5, + 0xA5, + 0xA5, + 0x55, + 0xA5, + 0x55, + 0xA5, + 0xA5, + 0xA5, + 0xAA, + 0x00, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0xEB, + 0x55, + 0x55, + ] + ) + ) + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123456) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 6) + self.assertSequenceEqual(msg.data, [0xAA, 0xA5, 0x55, 0x55, 0xA5, 0xAA]) + data = self.serial.read(self.serial.in_waiting) + + def test_send_extended(self): + msg = can.Message( + arbitration_id=0x123456, + is_extended_id=True, + data=[0xAA, 0xA5, 0x55, 0x55, 0xA5, 0xAA], + ) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0xA5, + 0xAA, + 0xA5, + 0xA5, + 0xA5, + 0x55, + 0xA5, + 0x55, + 0xA5, + 0xA5, + 0xA5, + 0xAA, + 0x00, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0xEB, + 0x55, + 0x55, + ] + ), + ) + + def test_recv_standard(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x7B, + 0x00, + 0x00, + 0x00, + 0x48, + 0x65, + 0x6C, + 0x6C, + 0x6F, + 0x31, + 0x32, + 0x33, + 0x08, + 0x00, + 0x00, + 0x00, + 0x0D, + 0x55, + 0x55, + ] + ) + ) + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 8) + self.assertSequenceEqual( + msg.data, [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x31, 0x32, 0x33] + ) + data = self.serial.read(self.serial.in_waiting) + + def test_send_standard(self): + msg = can.Message( + arbitration_id=123, + is_extended_id=False, + data=[0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x31, 0x32, 0x33], + ) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0x7B, + 0x00, + 0x00, + 0x00, + 0x48, + 0x65, + 0x6C, + 0x6C, + 0x6F, + 0x31, + 0x32, + 0x33, + 0x08, + 0x00, + 0x00, + 0x00, + 0x0D, + 0x55, + 0x55, + ] + ), + ) + + def test_recv_extended_remote(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x01, + 0x01, + 0xA5, + 0xA5, + 0x55, + 0x55, + ] + ) + ) + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123456) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, True) + self.assertEqual(msg.dlc, 7) + data = self.serial.read(self.serial.in_waiting) + + def test_send_extended_remote(self): + msg = can.Message( + arbitration_id=0x123456, is_extended_id=True, is_remote_frame=True, dlc=7 + ) + self.bus.send(msg) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x01, + 0x01, + 0xA5, + 0xA5, + 0x55, + 0x55, + ] + ), + ) + + def test_partial_recv(self): + # write some junk data and then start of message + self.serial.write( + bytearray([0x11, 0x22, 0x33, 0xAA, 0xAA, 0x7B, 0x00, 0x00, 0x00, 0x48]) + ) + msg = self.bus.recv(1) + self.assertIsNone(msg) + + # write rest of first message, and then a second message + self.serial.write( + bytearray( + [ + 0x65, + 0x6C, + 0x6C, + 0x6F, + 0x31, + 0x32, + 0x33, + 0x08, + 0x00, + 0x00, + 0x00, + 0x0D, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x56, + 0x34, + 0x12, + 0x00, + 0xA5, + 0xAA, + 0xA5, + 0xA5, + 0xA5, + 0x55, + 0xA5, + 0x55, + 0xA5, + 0xA5, + 0xA5, + 0xAA, + 0x00, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0xEB, + 0x55, + 0x55, + ] + ) + ) + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 8) + self.assertSequenceEqual( + msg.data, [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x31, 0x32, 0x33] + ) + + # now try to also receive 2nd message + msg = self.bus.recv(1) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123456) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 6) + self.assertSequenceEqual(msg.data, [0xAA, 0xA5, 0x55, 0x55, 0xA5, 0xAA]) + + # test nothing more left + msg = self.bus.recv(1) + self.assertIsNone(msg) + data = self.serial.read(self.serial.in_waiting) + + def test_serial_number(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xF0, + 0xFF, + 0xFF, + 0x01, + 0x53, + 0xFF, + 0x6A, + 0x06, + 0x49, + 0x72, + 0x48, + 0xA5, + 0x55, + 0x08, + 0xFF, + 0x01, + 0x00, + 0x11, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xF1, + 0xFF, + 0xFF, + 0x01, + 0x40, + 0x60, + 0x17, + 0x87, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x00, + 0x36, + 0x55, + 0x55, + ] + ) + ) + sn = self.bus.get_serial_number(1) + self.assertEqual(sn, "53FF-6A06-4972-4855-4060-1787") + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xF0, + 0xFF, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0xF8, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xF1, + 0xFF, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0xF9, + 0x55, + 0x55, + ] + ), + ) + + sn = self.bus.get_serial_number(0) + self.assertIsNone(sn) + data = self.serial.read(self.serial.in_waiting) + + def test_set_bitrate(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xD0, + 0xFE, + 0xFF, + 0x01, + 0x40, + 0x42, + 0x0F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x01, + 0x01, + 0x64, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_bitrate(1000000) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xD0, + 0xFE, + 0xFF, + 0x01, + 0x40, + 0x42, + 0x0F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x01, + 0x00, + 0x63, + 0x55, + 0x55, + ] + ), + ) + + def test_set_auto_retransmit(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xA0, + 0xFE, + 0xFF, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x01, + 0xA1, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xA0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x01, + 0xA0, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_auto_retransmit(True) + self.bus.set_auto_retransmit(False) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xA0, + 0xFE, + 0xFF, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x00, + 0xA0, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xA0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x00, + 0x9F, + 0x55, + 0x55, + ] + ), + ) + + def test_set_auto_bus_management(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xB0, + 0xFE, + 0xFF, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x01, + 0xB1, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xB0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x01, + 0xB0, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_auto_bus_management(True) + self.bus.set_auto_bus_management(False) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xB0, + 0xFE, + 0xFF, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x00, + 0xB0, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xB0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xFF, + 0x01, + 0x00, + 0xAF, + 0x55, + 0x55, + ] + ), + ) + + def test_set_serial_rate(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0x90, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0xC2, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x01, + 0x01, + 0x56, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_serial_rate(115200) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0x90, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0xC2, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0xFF, + 0x01, + 0x00, + 0xA5, + 0x55, + 0x55, + 0x55, + ] + ), + ) + + def test_set_hw_filter(self): + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xE0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0x67, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xE1, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0xC0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0xA8, + 0x55, + 0x55, + ] + ) + ) + self.serial.write( + bytearray( + [ + 0xAA, + 0xAA, + 0xE2, + 0xFE, + 0xFF, + 0x01, + 0xF0, + 0x01, + 0x00, + 0x00, + 0xF0, + 0x01, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x01, + 0xCB, + 0x55, + 0x55, + ] + ) + ) + self.bus.set_hw_filter(1, True, 0, 0, False) + self.bus.set_hw_filter(2, True, 0, 0, True) + self.bus.set_hw_filter(3, False, 0x1F0, 0x1F0, False) + data = self.serial.read(self.serial.in_waiting) + self.assertEqual( + data, + bytearray( + [ + 0xAA, + 0xAA, + 0xE0, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x00, + 0x66, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xE1, + 0xFE, + 0xFF, + 0x01, + 0x00, + 0x00, + 0x00, + 0xC0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x00, + 0xA7, + 0x55, + 0x55, + 0xAA, + 0xAA, + 0xE2, + 0xFE, + 0xFF, + 0x01, + 0xF0, + 0x01, + 0x00, + 0x00, + 0xF0, + 0x01, + 0x00, + 0x00, + 0x08, + 0xFF, + 0x01, + 0x00, + 0xCA, + 0x55, + 0x55, + ] + ), + ) + + +if __name__ == "__main__": + unittest.main() From 638bbeb221bf643e54b6cb13548401d098752ac1 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 23 Dec 2019 20:18:19 +1100 Subject: [PATCH 0355/1235] Update test deps (#745) * Update pinned versions of testing dependencies * Pin version of coverage --- setup.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index d7d2feca6..a035ff97f 100644 --- a/setup.py +++ b/setup.py @@ -33,11 +33,14 @@ } tests_require = [ - "pytest~=4.3", + "pytest~=5.3", "pytest-timeout~=1.3", - "pytest-cov~=2.6", + "pytest-cov~=2.8", + # coveragepy==5.0 fails with `Safety level may not be changed inside a transaction` + # on python 3.6 on MACOS + "coverage<5", "codecov~=2.0", - "hypothesis", + "hypothesis~=4.56", ] + extras_require["serial"] extras_require["test"] = tests_require From e11725d1acbbfd1a09251f368d30d1c58d28f962 Mon Sep 17 00:00:00 2001 From: Philipp <1062119+ptoews@users.noreply.github.com> Date: Mon, 23 Dec 2019 21:13:43 +0100 Subject: [PATCH 0356/1235] Fix example usage (#739) Code example does not work with python 3.6. A colon is required so that the X is recognized as formatting specifier. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0214f2d4b..9fce86c02 100644 --- a/README.rst +++ b/README.rst @@ -95,7 +95,7 @@ Example usage # iterate over received messages for msg in bus: - print("{X}: {}".format(msg.arbitration_id, msg.data)) + print("{:X}: {}".format(msg.arbitration_id, msg.data)) # or use an asynchronous notifier notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()]) From dcd694efb56d503ed9387edc5cfba9c523d559bf Mon Sep 17 00:00:00 2001 From: chrisoro <4160557+chrisoro@users.noreply.github.com> Date: Tue, 24 Dec 2019 07:16:31 +0100 Subject: [PATCH 0357/1235] Exclude all test packages, not just toplevel (#740) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a035ff97f..8327b1432 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ ], # Code version=version, - packages=find_packages(exclude=["test", "doc", "scripts", "examples"]), + packages=find_packages(exclude=["test*", "doc", "scripts", "examples"]), scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), # Author author="Brian Thorne", From 148921a4d566435808f5223c1e1bcba92de87f2a Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Tue, 7 Jan 2020 23:33:30 +0100 Subject: [PATCH 0358/1235] Fix date format to show correct day of month (#754) %m is month as a zero-padded decimal number %d is day of the month as a zero-padded decimal number --- can/io/asc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 9d854ab0f..c83458ab4 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -147,7 +147,7 @@ class ASCWriter(BaseIOHandler, Listener): "{bit_timing_conf_ext_data:>8}", ] ) - FORMAT_DATE = "%a %b %m %I:%M:%S.{} %p %Y" + FORMAT_DATE = "%a %b %d %I:%M:%S.{} %p %Y" FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" def __init__(self, file, channel=1): @@ -162,7 +162,7 @@ def __init__(self, file, channel=1): self.channel = channel # write start of file header - now = datetime.now().strftime("%a %b %m %I:%M:%S.%f %p %Y") + now = datetime.now().strftime("%a %b %d %I:%M:%S.%f %p %Y") self.file.write("date %s\n" % now) self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") From 44b026584e7dfeac44123affbdd06a0fd8035476 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Fri, 4 Oct 2019 10:31:57 +0200 Subject: [PATCH 0359/1235] Add example with cyclic counter and checksum --- examples/crc.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 examples/crc.py diff --git a/examples/crc.py b/examples/crc.py new file mode 100644 index 000000000..fa4493ab0 --- /dev/null +++ b/examples/crc.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +""" +This example exercises the periodic task's multiple message sending capabilities +to send a message containing a counter and a checksum. + +Expects a vcan0 interface: + + python3 -m examples.crc + +""" + +import logging +import time + +import can + +logging.basicConfig(level=logging.INFO) + + +def crc_send(bus): + """ + Sends periodic messages every 1 s with no explicit timeout + Sleeps for 10 seconds then stops the task. + """ + msg = can.Message(arbitration_id=0x12345678, data=[1, 2, 3, 4, 5, 6, 7, 0]) + messages = build_crc_msgs(msg) + + print( + "Starting to send a message with updating counter and checksum every 1 s for 8 s" + ) + task = bus.send_periodic(messages, 1) + assert isinstance(task, can.CyclicSendTaskABC) + time.sleep(8) + + msg = can.Message(arbitration_id=0x12345678, data=[8, 9, 10, 11, 12, 13, 14, 0]) + messages = build_crc_msgs(msg) + + print("Sending modified message data every 1 s for 10 s") + task.modify_data(messages) + time.sleep(10) + task.stop() + print("stopped cyclic send") + + +def build_crc_msgs(msg): + """ + Using the input message as base, create 16 messages with SAE J1939 SPN 3189 counters + and SPN 3188 checksums placed in the final byte. + """ + messages = [] + + for counter in range(16): + checksum = compute_xbr_checksum(msg, counter) + msg.data[7] = counter + (checksum << 4) + messages.append( + can.Message(arbitration_id=msg.arbitration_id, data=msg.data[:]) + ) + + return messages + + +def compute_xbr_checksum(message, counter): + """ + Computes an XBR checksum per SAE J1939 SPN 3188. + """ + checksum = sum(message.data[:7]) + checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder="big")) + checksum += counter & 0x0F + xbr_checksum = ((checksum >> 4) + checksum) & 0x0F + + return xbr_checksum + + +if __name__ == "__main__": + for interface, channel in [("socketcan", "vcan0")]: + print(f"Carrying out crc test with {interface} interface") + + with can.Bus( # type: ignore + interface=interface, channel=channel, bitrate=500000 + ) as BUS: + crc_send(BUS) + + time.sleep(2) From eeb659390b76f49aacef72f8f7fd0dad3303675a Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Fri, 4 Oct 2019 11:40:12 +0200 Subject: [PATCH 0360/1235] Fix crc_send docstring --- examples/crc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/crc.py b/examples/crc.py index fa4493ab0..4344fb2ea 100644 --- a/examples/crc.py +++ b/examples/crc.py @@ -20,8 +20,8 @@ def crc_send(bus): """ - Sends periodic messages every 1 s with no explicit timeout - Sleeps for 10 seconds then stops the task. + Sends periodic messages every 1 s with no explicit timeout. Modifies messages + after 8 seconds, sends for 10 more seconds, then stops. """ msg = can.Message(arbitration_id=0x12345678, data=[1, 2, 3, 4, 5, 6, 7, 0]) messages = build_crc_msgs(msg) From c92fbc24be92548bdafa8f13dab1a00e6a42b3e3 Mon Sep 17 00:00:00 2001 From: Syed Date: Thu, 30 Jan 2020 10:53:11 -0500 Subject: [PATCH 0361/1235] Fixing ASCII reader unable to read FD frames (#741) * Fixing ascii reader to support vector format when reading FD frames --- can/io/asc.py | 30 +++++++++++++++++++++++++++--- test/logformats_test.py | 2 +- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index c83458ab4..d2f8a4ab8 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -55,10 +55,14 @@ def __iter__(self): temp = line.strip() if not temp or not temp[0].isdigit(): continue + is_fd = False try: timestamp, channel, dummy = temp.split( None, 2 ) # , frameType, dlc, frameData + if channel == "CANFD": + timestamp, _, channel, _, dummy = temp.split(None, 4) + is_fd = True except ValueError: # we parsed an empty comment continue @@ -89,15 +93,32 @@ def __iter__(self): ) yield msg else: + brs = None + esi = None + data_length = 0 try: - # this only works if dlc > 0 and thus data is availabe - can_id_str, _, _, dlc, data = dummy.split(None, 4) + # this only works if dlc > 0 and thus data is available + if not is_fd: + can_id_str, _, _, dlc, data = dummy.split(None, 4) + else: + can_id_str, frame_name, brs, esi, dlc, data_length, data = dummy.split( + None, 6 + ) + if frame_name.isdigit(): + # Empty frame_name + can_id_str, brs, esi, dlc, data_length, data = dummy.split( + None, 5 + ) except ValueError: # but if not, we only want to get the stuff up to the dlc can_id_str, _, _, dlc = dummy.split(None, 3) # and we set data to an empty sequence manually data = "" - dlc = int(dlc) + dlc = int(dlc, 16) + if is_fd: + # For fd frames, dlc and data length might not be equal and + # data_length is the actual size of the data + dlc = int(data_length) frame = bytearray() data = data.split() for byte in data[0:dlc]: @@ -111,7 +132,10 @@ def __iter__(self): is_remote_frame=False, dlc=dlc, data=frame, + is_fd=is_fd, channel=channel, + bitrate_switch=is_fd and brs == "1", + error_state_indicator=is_fd and esi == "1", ) self.stop() diff --git a/test/logformats_test.py b/test/logformats_test.py index 3bc2695bb..4a5c408b5 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -338,7 +338,7 @@ def _setup_instance(self): super()._setup_instance_helper( can.ASCWriter, can.ASCReader, - check_fd=False, + check_fd=True, check_comments=True, preserves_channel=False, adds_default_channel=0, From 8ace40bf26c780dfccf1dc2bea4dd618c0ffe6b1 Mon Sep 17 00:00:00 2001 From: tamenol <37591107+tamenol@users.noreply.github.com> Date: Wed, 5 Feb 2020 14:44:35 +0100 Subject: [PATCH 0362/1235] Added keycheck for windows platform (#724) * Added keycheck for windows platform PCANBasic will log an error when the PEAK-Driver key could not be found. * Forgot the import winreg statement * added empty line between imports and rest of code * added finally statement for closing registery --- can/interfaces/pcan/basic.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index b10e5404e..6d0f80fad 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -14,6 +14,8 @@ from string import * import platform import logging +import winreg + logger = logging.getLogger("can.pcan") @@ -498,9 +500,17 @@ class PCANBasic: """ def __init__(self): - # Loads the PCANBasic.dll + # Loads the PCANBasic.dll and checks if driver is available if platform.system() == "Windows": self.__m_dllBasic = windll.LoadLibrary("PCANBasic") + aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + try: + aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers") + winreg.CloseKey(aKey) + except WindowsError: + logger.error("Exception: The PEAK-driver couldn't be found!") + finally: + winreg.CloseKey(aReg) elif platform.system() == "Darwin": self.__m_dllBasic = cdll.LoadLibrary("libPCBUSB.dylib") else: From e495478bc7eb9a8066ec4a551f0c7fb84ff85f66 Mon Sep 17 00:00:00 2001 From: tamenol <37591107+tamenol@users.noreply.github.com> Date: Wed, 5 Feb 2020 15:17:45 +0100 Subject: [PATCH 0363/1235] Added status_string method to return simple status strings (#725) * Added status_string method to return simple status strings status_string returns the current status of the bus in a single word * Return None when error code is not implemented Co-authored-by: Felix Divo --- can/interfaces/pcan/basic.py | 32 ++++++++++++++++++++++++++++++++ can/interfaces/pcan/pcan.py | 11 +++++++++++ 2 files changed, 43 insertions(+) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 6d0f80fad..90599910a 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -402,6 +402,38 @@ PCAN_TYPE_DNG_SJA = TPCANType(0x05) # PCAN-Dongle SJA1000 PCAN_TYPE_DNG_SJA_EPP = TPCANType(0x06) # PCAN-Dongle EPP SJA1000 +# string description of the error codes +PCAN_DICT_STATUS = { + PCAN_ERROR_OK: "OK", + PCAN_ERROR_XMTFULL: "XMTFULL", + PCAN_ERROR_OVERRUN: "OVERRUN", + PCAN_ERROR_BUSLIGHT: "BUSLIGHT", + PCAN_ERROR_BUSHEAVY: "BUSHEAVY", + PCAN_ERROR_BUSWARNING: "BUSWARNING", + PCAN_ERROR_BUSPASSIVE: "BUSPASSIVE", + PCAN_ERROR_BUSOFF: "BUSOFF", + PCAN_ERROR_ANYBUSERR: "ANYBUSERR", + PCAN_ERROR_QRCVEMPTY: "QRCVEMPTY", + PCAN_ERROR_QOVERRUN: "QOVERRUN", + PCAN_ERROR_QXMTFULL: "QXMTFULL", + PCAN_ERROR_REGTEST: "ERR_REGTEST", + PCAN_ERROR_NODRIVER: "NODRIVER", + PCAN_ERROR_HWINUSE: "HWINUSE", + PCAN_ERROR_NETINUSE: "NETINUSE", + PCAN_ERROR_ILLHW: "ILLHW", + PCAN_ERROR_ILLNET: "ILLNET", + PCAN_ERROR_ILLCLIENT: "ILLCLIENT", + PCAN_ERROR_ILLHANDLE: "ILLHANDLE", + PCAN_ERROR_RESOURCE: "ERR_RESOURCE", + PCAN_ERROR_ILLPARAMTYPE: "ILLPARAMTYPE", + PCAN_ERROR_ILLPARAMVAL: "ILLPARAMVAL", + PCAN_ERROR_UNKNOWN: "UNKNOWN", + PCAN_ERROR_ILLDATA: "ILLDATA", + PCAN_ERROR_CAUTION: "CAUTION", + PCAN_ERROR_INITIALIZE: "ERR_INITIALIZE", + PCAN_ERROR_ILLOPERATION: "ILLOPERATION", +} + class TPCANMsg(Structure): """ diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 5c7b2b9d7..14d8dc774 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -5,6 +5,7 @@ import logging import time +from typing import Optional from can import CanError, Message, BusABC from can.bus import BusState from can.util import len2dlc, dlc2len @@ -528,6 +529,16 @@ def _detect_available_configs(): ) return channels + def status_string(self) -> Optional[str]: + """ + Query the PCAN bus status. + :return: The status in string. + """ + if self.status() in PCAN_DICT_STATUS: + return PCAN_DICT_STATUS[self.status()] + else: + return None + class PcanError(CanError): """ From 2282bfc2b3b308a9b473b7bd6393684fa0e8e15a Mon Sep 17 00:00:00 2001 From: Auden RovelleQuartz Date: Fri, 7 Feb 2020 07:28:09 -0600 Subject: [PATCH 0364/1235] 'can.interface' NOT 'can.interfaces.interface' (#762) I was getting the following error (see below) #################################################################### C:\Users\arovellequartz>python auden_scratchpad_002.py Traceback (most recent call last): File "auden_scratchpad_002.py", line 18, in from can.interfaces.interface import Bus ModuleNotFoundError: No module named 'can.interfaces.interface' #################################################################### Co-authored-by: Brian Thorne --- doc/configuration.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index 0ce8f85a7..ea56c73ee 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -11,7 +11,7 @@ In Code ------- The ``can`` object exposes an ``rc`` dictionary which can be used to set -the **interface** and **channel** before importing from ``can.interfaces``. +the **interface** and **channel**. :: @@ -19,7 +19,7 @@ the **interface** and **channel** before importing from ``can.interfaces``. can.rc['interface'] = 'socketcan' can.rc['channel'] = 'vcan0' can.rc['bitrate'] = 500000 - from can.interfaces.interface import Bus + from can.interface import Bus bus = Bus() @@ -79,7 +79,7 @@ The configuration can also contain additional sections (or context): :: - from can.interfaces.interface import Bus + from can.interface import Bus hs_bus = Bus(context='HS') ms_bus = Bus(context='MS') From 651f1620af623b81bb46e17222fed95f708bdb0c Mon Sep 17 00:00:00 2001 From: yo <32378889+typecprint@users.noreply.github.com> Date: Sun, 9 Feb 2020 03:59:05 +0900 Subject: [PATCH 0365/1235] Supports other base number(radix) at ASCReader. (#764) --- can/io/asc.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index d2f8a4ab8..709230389 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -18,6 +18,8 @@ CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF +BASE_HEX = 16 +BASE_DEC = 10 logger = logging.getLogger("can.io.asc") @@ -30,27 +32,40 @@ class ASCReader(BaseIOHandler): TODO: turn relative timestamps back to absolute form """ - def __init__(self, file): + def __init__(self, file, base="hex"): """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text read mode, not binary read mode. + :param base: Select the base(hex or dec) of id and data. + If the header of the asc file contains base information, + this value will be overwritten. Default "hex". """ super().__init__(file, mode="r") + self.base = base @staticmethod - def _extract_can_id(str_can_id): + def _extract_can_id(str_can_id, base): if str_can_id[-1:].lower() == "x": is_extended = True - can_id = int(str_can_id[0:-1], 16) + can_id = int(str_can_id[0:-1], base) else: is_extended = False - can_id = int(str_can_id, 16) + can_id = int(str_can_id, base) return can_id, is_extended + @staticmethod + def _check_base(base): + if base not in ["hex", "dec"]: + raise ValueError('base should be either "hex" or "dec"') + return BASE_DEC if base == "dec" else BASE_HEX + def __iter__(self): + base = self._check_base(self.base) for line in self.file: # logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0]) + if line.split(" ")[0] == "base": + base = self._check_base(line.split(" ")[1]) temp = line.strip() if not temp or not temp[0].isdigit(): @@ -83,7 +98,7 @@ def __iter__(self): 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) + can_id_num, is_extended_id = self._extract_can_id(can_id_str, base) msg = Message( timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, @@ -114,7 +129,7 @@ def __iter__(self): can_id_str, _, _, dlc = dummy.split(None, 3) # and we set data to an empty sequence manually data = "" - dlc = int(dlc, 16) + dlc = int(dlc, base) if is_fd: # For fd frames, dlc and data length might not be equal and # data_length is the actual size of the data @@ -122,8 +137,8 @@ def __iter__(self): 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) + frame.append(int(byte, base)) + can_id_num, is_extended_id = self._extract_can_id(can_id_str, base) yield Message( timestamp=timestamp, From 01e8c87fcec106526a2319a569492b1673e92746 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 11 Feb 2020 15:44:40 +0100 Subject: [PATCH 0366/1235] clean up utils.py --- can/interfaces/socketcan/utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index ee89b142e..c6fa0c84e 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -46,8 +46,6 @@ def find_available_interfaces() -> Iterable[str]: """Returns the names of all open can/vcan interfaces using the ``ip link list`` command. If the lookup fails, an error is logged to the console and an empty list is returned. - - :rtype: an iterable of :class:`str` """ try: @@ -72,8 +70,7 @@ def error_code_to_str(code: int) -> str: """ Converts a given error code (errno) to a useful and human readable string. - :param int code: a possibly invalid/unknown error code - :rtype: str + :param code: a possibly invalid/unknown error code :returns: a string explaining and containing the given error code, or a string explaining that the errorcode is unknown if that is the case """ From 8fc1b62b74cca039263f9756cdd6086f5df751f6 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 11 Feb 2020 15:53:15 +0100 Subject: [PATCH 0367/1235] cleanups in socketcan helpers --- can/interfaces/socketcan/utils.py | 25 ++++++++----------------- test/test_socketcan_helpers.py | 3 ++- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index c6fa0c84e..f96c0b344 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -2,17 +2,16 @@ Defines common socketcan functions. """ -from typing import cast, Iterable, Optional -import can.typechecking as typechecking - import logging import os import errno import struct import subprocess import re +from typing import cast, Iterable, Optional from can.interfaces.socketcan.constants import CAN_EFF_FLAG +import can.typechecking as typechecking log = logging.getLogger(__name__) @@ -49,11 +48,11 @@ def find_available_interfaces() -> Iterable[str]: """ try: - # it might be good to add "type vcan", but that might (?) exclude physical can devices + # adding "type vcan" would exclude physical can devices command = ["ip", "-o", "link", "list", "up"] output = subprocess.check_output(command, universal_newlines=True) - except Exception as e: # subprocess.CalledProcessError was too specific + except Exception as e: # subprocess.CalledProcessError is too specific log.error("failed to fetch opened can devices: %s", e) return [] @@ -66,7 +65,7 @@ def find_available_interfaces() -> Iterable[str]: return filter(_PATTERN_CAN_INTERFACE.match, interface_names) -def error_code_to_str(code: int) -> str: +def error_code_to_str(code: Optional[int]) -> str: """ Converts a given error code (errno) to a useful and human readable string. @@ -74,15 +73,7 @@ def error_code_to_str(code: int) -> str: :returns: a string explaining and containing the given error code, or a string explaining that the errorcode is unknown if that is the case """ + name = errno.errorcode.get(code, "UNKNOWN") + description = os.strerror(code) - try: - name = errno.errorcode[code] - except KeyError: - name = "UNKNOWN" - - try: - description = os.strerror(code) - except ValueError: - description = "no description available" - - return "{} (errno {}): {}".format(name, code, description) + return f"{name} (errno {code}): {description}" diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index 311398657..d5126af21 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -9,10 +9,11 @@ from can.interfaces.socketcan.utils import find_available_interfaces, error_code_to_str -from .config import * +from .config import IS_LINUX, TEST_INTERFACE_SOCKETCAN class TestSocketCanHelpers(unittest.TestCase): + @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_error_code_to_str(self): """ From a16de0cf3ef322a349f1173ec660619b3f3fdf98 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 11 Feb 2020 15:53:35 +0100 Subject: [PATCH 0368/1235] fix formatting --- test/test_socketcan_helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index d5126af21..551b4dc1a 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -13,7 +13,6 @@ class TestSocketCanHelpers(unittest.TestCase): - @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_error_code_to_str(self): """ From 4cb5dcbb75716e426764ccd8ad27ce80cf70f598 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 11 Feb 2020 16:14:29 +0100 Subject: [PATCH 0369/1235] simplify implementation and handle errors --- can/interfaces/socketcan/socketcan.py | 35 +++++++++++++++------------ 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 307763d9f..2e49198f2 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -603,15 +603,21 @@ def __init__( self.socket.setsockopt( SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 1 if receive_own_messages else 0 ) - except socket.error as e: - log.error("Could not receive own messages (%s)", e) + except socket.error as error: + log.error("Could not receive own messages (%s)", error) + # enable CAN-FD frames if fd: - # TODO handle errors - self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1) + try: + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1) + except socket.error as error: + log.error("Could not enable CAN-FD frames (%s)", error) - # Enable error frames - self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF) + # enable error frames + try: + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF) + except socket.error as error: + log.error("Could not enable error frames (%s)", error) bind_socket(self.socket, channel) kwargs.update({"receive_own_messages": receive_own_messages, "fd": fd}) @@ -620,9 +626,8 @@ def __init__( def shutdown(self) -> None: """Stops all active periodic tasks and closes the socket.""" self.stop_all_periodic_tasks() - for channel in self._bcm_sockets: - log.debug("Closing bcm socket for channel {}".format(channel)) - bcm_socket = self._bcm_sockets[channel] + for channel, bcm_socket in self._bcm_sockets.items(): + log.debug("Closing bcm socket for channel %s", channel) bcm_socket.close() log.debug("Closing raw can socket") self.socket.close() @@ -630,26 +635,24 @@ def shutdown(self) -> None: def _recv_internal( self, timeout: Optional[float] ) -> Tuple[Optional[Message], bool]: - # get all sockets that are ready (can be a list with a single value - # being self.socket or an empty list if self.socket is not ready) try: # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout) except socket.error as exc: # something bad happened (e.g. the interface went down) - raise can.CanError("Failed to receive: %s" % exc) + raise can.CanError(f"Failed to receive: {exc}") - if ready_receive_sockets: # not empty or True + if ready_receive_sockets: # not empty get_channel = self.channel == "" msg = capture_message(self.socket, get_channel) if msg and not msg.channel and self.channel: # Default to our own channel msg.channel = self.channel return msg, self._is_filtered - else: - # socket wasn't readable or timeout occurred - return None, self._is_filtered + + # socket wasn't readable or timeout occurred + return None, self._is_filtered def send(self, msg: Message, timeout: Optional[float] = None) -> None: """Transmit a message to the CAN bus. From 915c2cb7433093d4465c5b8a14b0eebf08ff169b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 11 Feb 2020 16:21:53 +0100 Subject: [PATCH 0370/1235] add docs --- can/interfaces/socketcan/socketcan.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 2e49198f2..166d1b8cb 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -578,10 +578,17 @@ def __init__( fd: bool = False, **kwargs, ) -> None: - """ + """Creates a new socketcan bus. + + It setting some socket options fails, an error will be printed but no exception will be thrown. + This includes enabling: + - that own messages should be received, + - CAN-FD frames and + - error frames. + :param channel: - The can interface name with which to create this bus. An example channel - would be 'vcan0' or 'can0'. + The can interface name with which to create this bus. + An example channel would be 'vcan0' or 'can0'. An empty string '' will receive messages from all channels. In that case any sent messages must be explicitly addressed to a channel using :attr:`can.Message.channel`. @@ -589,7 +596,7 @@ def __init__( If transmitted messages should also be received by this bus. :param fd: If CAN-FD frames should be supported. - :param list can_filters: + :param List can_filters: See :meth:`can.BusABC.set_filters`. """ self.socket = create_socket() @@ -751,13 +758,12 @@ def _get_bcm_socket(self, channel: str) -> socket.socket: def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: try: self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters)) - except socket.error as err: + except socket.error as error: # fall back to "software filtering" (= not in kernel) self._is_filtered = False - # TODO Is this serious enough to raise a CanError exception? log.error( "Setting filters failed; falling back to software filtering (not in kernel): %s", - err, + error, ) else: self._is_filtered = True From 6de3d92787f56339cdf46da1d1637e0ea25fceb7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 11 Feb 2020 16:34:11 +0100 Subject: [PATCH 0371/1235] debugging commit (enable archiving of coverage artifacts) --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 16168f521..b9b9b52b6 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,7 +1,7 @@ # Validate with curl --data-binary @.codecov.yml https://codecov.io/validate codecov: archive: - uploads: no + uploads: yes coverage: precision: 2 From b87329983d03b91b16920d47a8b4f90e650ca433 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 11 Feb 2020 20:45:45 +0100 Subject: [PATCH 0372/1235] Fix some BLF files can't be read (#765) Fixes #763 --- can/io/blf.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index 8df41ed5a..8ac79ddb8 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -189,13 +189,13 @@ def __iter__(self): def _parse_container(self, data): if self._tail: data = b"".join((self._tail, data)) - self._pos = 0 try: yield from self._parse_data(data) except struct.error: - # Container data exhausted - # Save the remaining data that could not be processed - self._tail = data[self._pos :] + # There was not enough data in the container to unpack a struct + pass + # Save the remaining data that could not be processed + self._tail = data[self._pos :] def _parse_data(self, data): """Optimized inner loop by making local copies of global variables @@ -213,12 +213,14 @@ def _parse_data(self, data): unpack_can_error_ext = CAN_ERROR_EXT_STRUCT.unpack_from start_timestamp = self.start_timestamp + max_pos = len(data) pos = 0 # Loop until a struct unpack raises an exception while True: self._pos = pos header = unpack_obj_header_base(data, pos) + # print(header) signature, _, header_version, obj_size, obj_type = header if signature != b"LOBJ": raise BLFParseError() @@ -228,6 +230,9 @@ def _parse_data(self, data): if obj_type != CAN_FD_MESSAGE_64: # Add padding bytes next_pos += obj_size % 4 + if next_pos > max_pos: + # This object continues in the next container + return pos += obj_header_base_size # Read rest of header From cb138c09567b9502d1040b93ad2af9c16b0cafd7 Mon Sep 17 00:00:00 2001 From: Syed Date: Fri, 21 Feb 2020 14:37:44 -0500 Subject: [PATCH 0373/1235] Changes to add direction to CAN messages (#773) * Changes to add direction to CAN messages * Adding relevant changes to ascii reader * Reverting accidental change * Black changes * Adding more changes needed for rx_attribute & documentation * Adding more changes needed for rx_attribute & documentation2 * Adding changes to __repr__ and adding rx to equals --- can/interfaces/ics_neovi/neovi_bus.py | 2 ++ can/io/asc.py | 21 +++++++++++++++------ can/io/blf.py | 5 +++++ can/message.py | 17 ++++++++++++++++- doc/message.rst | 7 +++++++ 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index df4f5481f..30c9dd8c9 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -306,6 +306,7 @@ def _ics_msg_to_message(self, ics_msg): dlc=ics_msg.NumberBytesData, is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), is_fd=is_fd, + is_rx=not bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG), is_remote_frame=bool( ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME ), @@ -325,6 +326,7 @@ def _ics_msg_to_message(self, ics_msg): dlc=ics_msg.NumberBytesData, is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), is_fd=is_fd, + is_rx=not bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG), is_remote_frame=bool( ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME ), diff --git a/can/io/asc.py b/can/io/asc.py index 709230389..c8a2aada3 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -71,13 +71,15 @@ def __iter__(self): if not temp or not temp[0].isdigit(): continue is_fd = False + is_rx = True try: timestamp, channel, dummy = temp.split( None, 2 ) # , frameType, dlc, frameData if channel == "CANFD": - timestamp, _, channel, _, dummy = temp.split(None, 4) + timestamp, _, channel, direction, dummy = temp.split(None, 4) is_fd = True + is_rx = direction == "Rx" except ValueError: # we parsed an empty comment continue @@ -97,13 +99,14 @@ def __iter__(self): ): pass elif dummy[-1:].lower() == "r": - can_id_str, _ = dummy.split(None, 1) + can_id_str, direction, _ = dummy.split(None, 2) can_id_num, is_extended_id = self._extract_can_id(can_id_str, base) msg = Message( timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, is_extended_id=is_extended_id, is_remote_frame=True, + is_rx=direction == "Rx", channel=channel, ) yield msg @@ -114,7 +117,8 @@ def __iter__(self): try: # this only works if dlc > 0 and thus data is available if not is_fd: - can_id_str, _, _, dlc, data = dummy.split(None, 4) + can_id_str, direction, _, dlc, data = dummy.split(None, 4) + is_rx = direction == "Rx" else: can_id_str, frame_name, brs, esi, dlc, data_length, data = dummy.split( None, 6 @@ -148,6 +152,7 @@ def __iter__(self): dlc=dlc, data=frame, is_fd=is_fd, + is_rx=is_rx, channel=channel, bitrate_switch=is_fd and brs == "1", error_state_indicator=is_fd and esi == "1", @@ -164,7 +169,7 @@ class ASCWriter(BaseIOHandler, Listener): It the first message does not have a timestamp, it is set to zero. """ - FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" + FORMAT_MESSAGE = "{channel} {id:<15} {dir:<4} {dtype} {data}" FORMAT_MESSAGE_FD = " ".join( [ "CANFD", @@ -276,7 +281,7 @@ def on_message_received(self, msg): serialized = self.FORMAT_MESSAGE_FD.format( channel=channel, id=arb_id, - dir="Rx", + dir="Rx" if msg.is_rx else "Tx", symbolic_name="", brs=1 if msg.bitrate_switch else 0, esi=1 if msg.error_state_indicator else 0, @@ -294,6 +299,10 @@ def on_message_received(self, msg): ) else: serialized = self.FORMAT_MESSAGE.format( - channel=channel, id=arb_id, dtype=dtype, data=" ".join(data) + channel=channel, + id=arb_id, + dir="Rx" if msg.is_rx else "Tx", + dtype=dtype, + data=" ".join(data), ) self.log_event(serialized, msg.timestamp) diff --git a/can/io/blf.py b/can/io/blf.py index 8ac79ddb8..ca0b5bd38 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -91,6 +91,7 @@ class BLFParseError(Exception): EDL = 0x1 BRS = 0x2 ESI = 0x4 +DIR = 0x1 TIME_TEN_MICS = 0x00000001 TIME_ONE_NANS = 0x00000002 @@ -258,6 +259,7 @@ def _parse_data(self, data): arbitration_id=can_id & 0x1FFFFFFF, is_extended_id=bool(can_id & CAN_MSG_EXT), is_remote_frame=bool(flags & REMOTE_FLAG), + is_rx=not bool(flags & DIR), dlc=dlc, data=can_data[:dlc], channel=channel - 1, @@ -288,6 +290,7 @@ def _parse_data(self, data): is_extended_id=bool(can_id & CAN_MSG_EXT), is_remote_frame=bool(flags & REMOTE_FLAG), is_fd=bool(fd_flags & 0x1), + is_rx=not bool(flags & DIR), bitrate_switch=bool(fd_flags & 0x2), error_state_indicator=bool(fd_flags & 0x4), dlc=dlc2len(dlc), @@ -399,6 +402,8 @@ def on_message_received(self, msg): if msg.is_extended_id: arb_id |= CAN_MSG_EXT flags = REMOTE_FLAG if msg.is_remote_frame else 0 + if not msg.is_rx: + flags |= DIR can_data = bytes(msg.data) if msg.is_error_frame: diff --git a/can/message.py b/can/message.py index 57e0109af..7ceaca489 100644 --- a/can/message.py +++ b/can/message.py @@ -42,6 +42,7 @@ class Message: "dlc", "data", "is_fd", + "is_rx", "bitrate_switch", "error_state_indicator", "__weakref__", # support weak references to messages @@ -58,6 +59,7 @@ def __init__( dlc: Optional[int] = None, data: Optional[typechecking.CanData] = None, is_fd: bool = False, + is_rx: bool = True, bitrate_switch: bool = False, error_state_indicator: bool = False, check: bool = False, @@ -81,6 +83,7 @@ def __init__( self.is_error_frame = is_error_frame self.channel = channel self.is_fd = is_fd + self.is_rx = is_rx self.bitrate_switch = bitrate_switch self.error_state_indicator = error_state_indicator @@ -114,6 +117,7 @@ def __str__(self) -> str: flag_string = " ".join( [ "X" if self.is_extended_id else "S", + "Rx" if self.is_rx else "Tx", "E" if self.is_error_frame else " ", "R" if self.is_remote_frame else " ", "F" if self.is_fd else " ", @@ -159,6 +163,9 @@ def __repr__(self) -> str: "is_extended_id={}".format(self.is_extended_id), ] + if not self.is_rx: + args.append("is_rx=False") + if self.is_remote_frame: args.append("is_remote_frame={}".format(self.is_remote_frame)) @@ -198,6 +205,7 @@ def __copy__(self) -> "Message": dlc=self.dlc, data=self.data, is_fd=self.is_fd, + is_rx=self.is_rx, bitrate_switch=self.bitrate_switch, error_state_indicator=self.error_state_indicator, ) @@ -214,6 +222,7 @@ def __deepcopy__(self, memo: dict) -> "Message": dlc=self.dlc, data=deepcopy(self.data, memo), is_fd=self.is_fd, + is_rx=self.is_rx, bitrate_switch=self.bitrate_switch, error_state_indicator=self.error_state_indicator, ) @@ -280,7 +289,10 @@ def _check(self): ) def equals( - self, other: "Message", timestamp_delta: Optional[Union[float, int]] = 1.0e-6 + self, + other: "Message", + timestamp_delta: Optional[Union[float, int]] = 1.0e-6, + check_direction: bool = True, ) -> bool: """ Compares a given message with this one. @@ -290,6 +302,8 @@ def equals( :param timestamp_delta: the maximum difference at which two timestamps are still considered equal or None to not compare timestamps + :param check_direction: do we compare the messages' directions (Tx/Rx) + :return: True iff the given message equals this one """ # see https://github.com/hardbyte/python-can/pull/413 for a discussion @@ -304,6 +318,7 @@ def equals( timestamp_delta is None or abs(self.timestamp - other.timestamp) <= timestamp_delta ) + and (self.is_rx == other.is_rx or not check_direction) and self.arbitration_id == other.arbitration_id and self.is_extended_id == other.is_extended_id and self.dlc == other.dlc diff --git a/doc/message.rst b/doc/message.rst index 921748cb9..e5745f6b5 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -145,6 +145,13 @@ Message Indicates that this message is a CAN FD message. + .. attribute:: is_rx + + :type: bool + + Indicates whether this message is a transmitted (Tx) or received (Rx) frame + + .. attribute:: bitrate_switch :type: bool From 780db5cf3ef068c0e1d5b43637cc20726980aafe Mon Sep 17 00:00:00 2001 From: Syed Date: Mon, 2 Mar 2020 16:46:54 -0500 Subject: [PATCH 0374/1235] Adding plugin support to can.io Reader/Writer (#783) --- can/io/logger.py | 30 +++++++++++++++++++++--------- can/io/player.py | 29 ++++++++++++++++++++--------- doc/listeners.rst | 18 ++++++++++++++++++ 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 3c9cf5e46..a4544b87d 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -5,6 +5,7 @@ import pathlib import typing +from pkg_resources import iter_entry_points import can.typechecking from ..listener import Listener @@ -38,6 +39,16 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method arguments are passed on to the returned instance. """ + fetched_plugins = False + message_writers = { + ".asc": ASCWriter, + ".blf": BLFWriter, + ".csv": CSVWriter, + ".db": SqliteWriter, + ".log": CanutilsLogWriter, + ".txt": Printer, + } + @staticmethod def __new__( cls, filename: typing.Optional[can.typechecking.StringPathLike], *args, **kwargs @@ -51,17 +62,18 @@ def __new__( if filename is None: return Printer(*args, **kwargs) - lookup = { - ".asc": ASCWriter, - ".blf": BLFWriter, - ".csv": CSVWriter, - ".db": SqliteWriter, - ".log": CanutilsLogWriter, - ".txt": Printer, - } + if not Logger.fetched_plugins: + Logger.message_writers.update( + { + writer.name: writer.load() + for writer in iter_entry_points("can.io.message_writer") + } + ) + Logger.fetched_plugins = True + suffix = pathlib.PurePath(filename).suffix try: - return lookup[suffix](filename, *args, **kwargs) + return Logger.message_writers[suffix](filename, *args, **kwargs) except KeyError: raise ValueError( f'No write support for this unknown log format "{suffix}"' diff --git a/can/io/player.py b/can/io/player.py index 7b4aec7c0..88d3497fa 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -8,6 +8,8 @@ from time import time, sleep import typing +from pkg_resources import iter_entry_points + if typing.TYPE_CHECKING: import can @@ -44,24 +46,33 @@ class LogReader(BaseIOHandler): arguments are passed on to the returned instance. """ + fetched_plugins = False + message_readers = { + ".asc": ASCReader, + ".blf": BLFReader, + ".csv": CSVReader, + ".db": SqliteReader, + ".log": CanutilsLogReader, + } + @staticmethod def __new__(cls, filename: "can.typechecking.StringPathLike", *args, **kwargs): """ :param filename: the filename/path of the file to read from :raises ValueError: if the filename's suffix is of an unknown file type """ - suffix = pathlib.PurePath(filename).suffix + if not LogReader.fetched_plugins: + LogReader.message_readers.update( + { + reader.name: reader.load() + for reader in iter_entry_points("can.io.message_reader") + } + ) + LogReader.fetched_plugins = True - lookup = { - ".asc": ASCReader, - ".blf": BLFReader, - ".csv": CSVReader, - ".db": SqliteReader, - ".log": CanutilsLogReader, - } suffix = pathlib.PurePath(filename).suffix try: - return lookup[suffix](filename, *args, **kwargs) + return LogReader.message_readers[suffix](filename, *args, **kwargs) except KeyError: raise ValueError( f'No read support for this unknown log format "{suffix}"' diff --git a/doc/listeners.rst b/doc/listeners.rst index fcdc32f52..8e2a79a8b 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -30,6 +30,24 @@ readers are also documented here. completely unchanged message again, since some properties are not (yet) supported by some file formats. +.. note :: + + Additional file formats for both reading/writing log files can be added via + a plugin reader/writer. An external package can register a new reader + by using the ``can.io.message_reader`` entry point. Similarly, a writer can + be added using the ``can.io.message_writer`` entry point. + + The format of the entry point is ``reader_name=module:classname`` where ``classname`` + is a :class:`can.io.generic.BaseIOHandler` concrete implementation. + + :: + + entry_points={ + 'can.io.message_reader': [ + '.asc = my_package.io.asc:ASCReader' + ] + }, + BufferedReader -------------- From 0c698cce8524a9228384543014baa5bd37e76e19 Mon Sep 17 00:00:00 2001 From: Syed Date: Tue, 3 Mar 2020 13:35:31 -0500 Subject: [PATCH 0375/1235] Adding direction to virutal bus messages (#779) * Adding direction to virutal bus messages * Creaing a new msg instance for each virtual interface * Update can/interfaces/virtual.py Co-Authored-By: pierreluctg * Adding some unit tests for message direction checking as well as msg.data mutation * Changing how we were skipping test for socketcan * Splitting test case into two different test cases * Update test/back2back_test.py Co-Authored-By: pierreluctg Co-authored-by: pierreluctg Co-authored-by: Brian Thorne --- can/interfaces/virtual.py | 20 +++++++------ test/back2back_test.py | 60 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 5e24c6e1f..e84fa5853 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -85,18 +85,20 @@ def _recv_internal(self, timeout): def send(self, msg, timeout=None): self._check_if_open() - msg_copy = deepcopy(msg) - msg_copy.timestamp = time.time() - msg_copy.channel = self.channel_id - + timestamp = time.time() # Add message to all listening on this channel all_sent = True for bus_queue in self.channel: - if bus_queue is not self.queue or self.receive_own_messages: - try: - bus_queue.put(msg_copy, block=True, timeout=timeout) - except queue.Full: - all_sent = False + if bus_queue is self.queue and not self.receive_own_messages: + continue + msg_copy = deepcopy(msg) + msg_copy.timestamp = timestamp + msg_copy.channel = self.channel_id + msg_copy.is_rx = bus_queue is not self.queue + try: + bus_queue.put(msg_copy, block=True, timeout=timeout) + except queue.Full: + all_sent = False if not all_sent: raise CanError("Could not send message to one or more recipients") diff --git a/test/back2back_test.py b/test/back2back_test.py index b707988ec..8c1ded6c5 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -125,6 +125,66 @@ def test_dlc_less_than_eight(self): msg = can.Message(is_extended_id=False, arbitration_id=0x300, data=[4, 5, 6]) self._send_and_receive(msg) + def test_message_direction(self): + # Verify that own message received has is_rx set to False while message + # received on the other virtual interfaces have is_rx set to True + if self.INTERFACE_1 != "virtual": + raise unittest.SkipTest( + "Message direction not yet implemented for socketcan" + ) + bus3 = can.Bus( + channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + receive_own_messages=True, + ) + try: + msg = can.Message( + is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3] + ) + bus3.send(msg) + recv_msg_bus1 = self.bus1.recv(self.TIMEOUT) + recv_msg_bus2 = self.bus2.recv(self.TIMEOUT) + self_recv_msg_bus3 = bus3.recv(self.TIMEOUT) + + self.assertTrue(recv_msg_bus1.is_rx) + self.assertTrue(recv_msg_bus2.is_rx) + self.assertFalse(self_recv_msg_bus3.is_rx) + finally: + bus3.shutdown() + + def test_unique_message_instances(self): + # Verify that we have a different instances of message for each bus + if self.INTERFACE_1 != "virtual": + raise unittest.SkipTest("Not relevant for socketcan") + bus3 = can.Bus( + channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, + fd=TEST_CAN_FD, + single_handle=True, + receive_own_messages=True, + ) + try: + msg = can.Message( + is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3] + ) + bus3.send(msg) + recv_msg_bus1 = self.bus1.recv(self.TIMEOUT) + recv_msg_bus2 = self.bus2.recv(self.TIMEOUT) + self_recv_msg_bus3 = bus3.recv(self.TIMEOUT) + + self._check_received_message(recv_msg_bus1, recv_msg_bus2) + self._check_received_message(recv_msg_bus2, self_recv_msg_bus3) + + recv_msg_bus1.data[0] = 4 + self.assertNotEqual(recv_msg_bus1.data, recv_msg_bus2.data) + self.assertEqual(recv_msg_bus2.data, self_recv_msg_bus3.data) + finally: + bus3.shutdown() + @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message(self): msg = can.Message( From ffa1800b1933264bf4eae57bea2a5175ddd36140 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 23 Mar 2020 16:11:25 -0400 Subject: [PATCH 0376/1235] caching msg.data value (#798) Avoid calling msg.data multiple times by caching msg.data value --- can/interfaces/ics_neovi/neovi_bus.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 30c9dd8c9..638460a4c 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -363,11 +363,12 @@ def send(self, msg, timeout=None): flag3 |= ics.SPY_STATUS3_CANFD_ESI message.ArbIDOrHeader = msg.arbitration_id - message.NumberBytesData = len(msg.data) - message.Data = tuple(msg.data[:8]) - if msg.is_fd and len(msg.data) > 8: + msg_data = msg.data + message.NumberBytesData = len(msg_data) + message.Data = tuple(msg_data[:8]) + if msg.is_fd and len(msg_data) > 8: message.ExtraDataPtrEnabled = 1 - message.ExtraDataPtr = tuple(msg.data) + message.ExtraDataPtr = tuple(msg_data) message.StatusBitField = flag0 message.StatusBitField2 = 0 message.StatusBitField3 = flag3 From f4c2cff4bf26e825512c034adbb717625ef08b3e Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 28 Mar 2020 19:54:28 +0100 Subject: [PATCH 0377/1235] add vector hardware config popup (#774) Co-authored-by: zariiii9003 Co-authored-by: Christian Sandberg --- can/interfaces/vector/canlib.py | 9 +++++++++ can/interfaces/vector/xldriver.py | 5 +++++ test/test_vector.py | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index cb1858062..14205327b 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -577,6 +577,15 @@ def _detect_available_configs(): ) return configs + @staticmethod + def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: + """Open vector hardware configuration window. + + :param int wait_for_finish: + Time to wait for user input in milliseconds. + """ + xldriver.xlPopupHwConfig(ctypes.c_char_p(), ctypes.c_uint(wait_for_finish)) + def get_channel_configs(): if xldriver is None: diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 337135755..7a361a29d 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -229,3 +229,8 @@ def check_status(result, function, arguments): xlCanSetChannelOutput.argtypes = [xlclass.XLportHandle, xlclass.XLaccess, ctypes.c_char] xlCanSetChannelOutput.restype = xlclass.XLstatus xlCanSetChannelOutput.errcheck = check_status + +xlPopupHwConfig = _xlapi_dll.xlPopupHwConfig +xlPopupHwConfig.argtypes = [ctypes.c_char_p, ctypes.c_uint] +xlPopupHwConfig.restype = xlclass.XLstatus +xlPopupHwConfig.errcheck = check_status diff --git a/test/test_vector.py b/test/test_vector.py index d99509df8..a2d47c74e 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -240,6 +240,14 @@ def test_reset(self) -> None: can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called() + def test_popup_hw_cfg(self) -> None: + canlib.xldriver.xlPopupHwConfig = Mock() + canlib.VectorBus.popup_vector_hw_configuration(10) + assert canlib.xldriver.xlPopupHwConfig.called + args, kwargs = canlib.xldriver.xlPopupHwConfig.call_args + assert isinstance(args[0], ctypes.c_char_p) + assert isinstance(args[1], ctypes.c_uint) + def test_called_without_testing_argument(self) -> None: """This tests if an exception is thrown when we are not running on Windows.""" if os.name != "nt": From a35ab980ebd85ffd24139e986a206f4e1bb35f0f Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Sat, 4 Apr 2020 03:41:05 +0300 Subject: [PATCH 0378/1235] Add TX/RX message direction to socketcan (#780) --- can/interfaces/socketcan/socketcan.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 307763d9f..e6ca07f5c 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -509,10 +509,10 @@ def capture_message( # Fetching the Arb ID, DLC and Data try: if get_channel: - cf, addr = sock.recvfrom(CANFD_MTU) + cf, _, msg_flags, addr = sock.recvmsg(CANFD_MTU) channel = addr[0] if isinstance(addr, tuple) else addr else: - cf = sock.recv(CANFD_MTU) + cf, _, msg_flags, _ = sock.recvmsg(CANFD_MTU) channel = None except socket.error as exc: raise can.CanError("Error receiving: %s" % exc) @@ -539,6 +539,9 @@ def capture_message( bitrate_switch = bool(flags & CANFD_BRS) error_state_indicator = bool(flags & CANFD_ESI) + # Section 4.7.1: MSG_DONTROUTE: set when the received frame was created on the local host. + is_rx = not bool(msg_flags & socket.MSG_DONTROUTE) + if is_extended_frame_format: # log.debug("CAN: Extended") # TODO does this depend on SFF or EFF? @@ -555,6 +558,7 @@ def capture_message( is_remote_frame=is_remote_transmission_request, is_error_frame=is_error_frame, is_fd=is_fd, + is_rx=is_rx, bitrate_switch=bitrate_switch, error_state_indicator=error_state_indicator, dlc=can_dlc, From 2d672b340fc38f531a4a6470c8c2e288e5e50041 Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Fri, 10 Apr 2020 00:41:21 +0300 Subject: [PATCH 0379/1235] Relax restriction of arbitration ID uniqueness for SocketCAN (#785) Socketcan bcm now supports sending periodic messages with the same arbitration ID. Updates documentation and tests for socketcan interface. Closes #721 --- can/interfaces/socketcan/socketcan.py | 119 ++++++++++++++++---------- doc/bus.rst | 2 +- doc/interfaces/socketcan.rst | 89 ++++++++++++------- test/test_cyclic_socketcan.py | 60 +++++++++++-- 4 files changed, 186 insertions(+), 84 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index e6ca07f5c..e16ad0c94 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -14,6 +14,7 @@ import socket import struct import time +import threading import errno log = logging.getLogger(__name__) @@ -162,7 +163,7 @@ def build_can_frame(msg: Message) -> bytes: __u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8))); }; """ - can_id = _add_flags_to_can_id(msg) + can_id = _compose_arbitration_id(msg) flags = 0 if msg.bitrate_switch: flags |= CANFD_BRS @@ -286,7 +287,7 @@ def send_bcm(bcm_socket: socket.socket, data: bytes) -> int: raise e -def _add_flags_to_can_id(message: Message) -> int: +def _compose_arbitration_id(message: Message) -> int: can_id = message.arbitration_id if message.is_extended_id: log.debug("sending an extended id type message") @@ -297,7 +298,6 @@ def _add_flags_to_can_id(message: Message) -> int: if message.is_error_frame: log.debug("sending error frame") can_id |= CAN_ERR_FLAG - return can_id @@ -310,18 +310,22 @@ class CyclicSendTask( - setting of a task duration - modifying the data - stopping then subsequent restarting of the task - """ def __init__( self, bcm_socket: socket.socket, + task_id: int, messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, ): - """ + """Construct and :meth:`~start` a task. + :param bcm_socket: An open BCM socket on the desired CAN channel. + :param task_id: + The identifier used to uniquely reference particular cyclic send task + within Linux BCM. :param messages: The messages to be sent periodically. :param period: @@ -336,12 +340,12 @@ def __init__( super().__init__(messages, period, duration) self.bcm_socket = bcm_socket + self.task_id = task_id self._tx_setup(self.messages) def _tx_setup(self, messages: Sequence[Message]) -> None: # Create a low level packed frame to pass to the kernel body = bytearray() - self.can_id_with_flags = _add_flags_to_can_id(messages[0]) self.flags = CAN_FD_FRAME if messages[0].is_fd else 0 if self.duration: @@ -353,9 +357,19 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: ival1 = 0.0 ival2 = self.period - # First do a TX_READ before creating a new task, and check if we get - # EINVAL. If so, then we are referring to a CAN message with the same - # ID + self._check_bcm_task() + + header = build_bcm_transmit_header( + self.task_id, count, ival1, ival2, self.flags, nframes=len(messages) + ) + for message in messages: + body += build_can_frame(message) + log.debug("Sending BCM command") + send_bcm(self.bcm_socket, header + body) + + def _check_bcm_task(self): + # Do a TX_READ on a task ID, and check if we get EINVAL. If so, + # then we are referring to a CAN message with the existing ID check_header = build_bcm_header( opcode=CAN_BCM_TX_READ, flags=0, @@ -364,7 +378,7 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: ival1_usec=0, ival2_seconds=0, ival2_usec=0, - can_id=self.can_id_with_flags, + can_id=self.task_id, nframes=0, ) try: @@ -374,45 +388,33 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: raise e else: raise ValueError( - "A periodic Task for Arbitration ID {} has already been created".format( - messages[0].arbitration_id + "A periodic task for Task ID {} is already in progress by SocketCAN Linux layer".format( + self.task_id ) ) - header = build_bcm_transmit_header( - self.can_id_with_flags, - count, - ival1, - ival2, - self.flags, - nframes=len(messages), - ) - for message in messages: - body += build_can_frame(message) - log.debug("Sending BCM command") - send_bcm(self.bcm_socket, header + body) - def stop(self) -> None: - """Send a TX_DELETE message to cancel this task. + """Stop a task by sending TX_DELETE message to Linux kernel. This will delete the entry for the transmission of the CAN-message - with the specified can_id CAN identifier. The message length for the command - TX_DELETE is {[bcm_msg_head]} (only the header). + with the specified :attr:`~task_id` identifier. The message length + for the command TX_DELETE is {[bcm_msg_head]} (only the header). """ log.debug("Stopping periodic task") - stopframe = build_bcm_tx_delete_header(self.can_id_with_flags, self.flags) + stopframe = build_bcm_tx_delete_header(self.task_id, self.flags) send_bcm(self.bcm_socket, stopframe) def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: - """Update the contents of the periodically sent messages. + """Update the contents of the periodically sent CAN messages by + sending TX_SETUP message to Linux kernel. - Note: The messages must all have the same - :attr:`~can.Message.arbitration_id` like the first message. - - Note: The number of new cyclic messages to be sent must be equal to the + The number of new cyclic messages to be sent must be equal to the original number of messages originally specified for this task. + .. note:: The messages must all have the same + :attr:`~can.Message.arbitration_id` like the first message. + :param messages: The messages with the new :attr:`can.Message.data`. """ @@ -423,7 +425,7 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: body = bytearray() header = build_bcm_update_header( - can_id=self.can_id_with_flags, msg_flags=self.flags, nframes=len(messages) + can_id=self.task_id, msg_flags=self.flags, nframes=len(messages) ) for message in messages: body += build_can_frame(message) @@ -431,6 +433,14 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: send_bcm(self.bcm_socket, header + body) def start(self) -> None: + """Start a periodic task by sending TX_SETUP message to Linux kernel. + + It verifies presence of the particular BCM task through sending TX_READ + message to Linux kernel prior to scheduling. + + :raises ValueError: + If the task referenced by :attr:`~task_id` is already running. + """ self._tx_setup(self.messages) @@ -443,16 +453,17 @@ class MultiRateCyclicSendTask(CyclicSendTask): def __init__( self, channel: socket.socket, + task_id: int, messages: Sequence[Message], count: int, initial_period: float, subsequent_period: float, ): - super().__init__(channel, messages, subsequent_period) + super().__init__(channel, task_id, messages, subsequent_period) # Create a low level packed frame to pass to the kernel header = build_bcm_transmit_header( - self.can_id_with_flags, + self.task_id, count, initial_period, subsequent_period, @@ -571,8 +582,10 @@ def capture_message( class SocketcanBus(BusABC): - """ - Implements :meth:`can.BusABC._detect_available_configs`. + """ A SocketCAN interface to CAN. + + It implements :meth:`can.BusABC._detect_available_configs` to search for + available interfaces. """ def __init__( @@ -601,6 +614,8 @@ def __init__( self.channel_info = "socketcan channel '%s'" % channel self._bcm_sockets: Dict[str, socket.socket] = {} self._is_filtered = False + self._task_id = 0 + self._task_id_guard = threading.Lock() # set the receive_own_messages parameter try: @@ -712,18 +727,26 @@ def _send_periodic_internal( ) -> CyclicSendTask: """Start sending messages at a given period on this bus. - The kernel's Broadcast Manager SocketCAN API will be used. + The Linux kernel's Broadcast Manager SocketCAN API is used to schedule + periodic sending of CAN messages. The wrapping 32-bit counter (see + :meth:`~_get_next_task_id()`) designated to distinguish different + :class:`CyclicSendTask` within BCM provides flexibility to schedule + CAN messages sending with the same CAN ID, but different CAN data. :param messages: - The messages to be sent periodically + The message(s) to be sent periodically. :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. + :raises ValueError: + If task identifier passed to :class:`CyclicSendTask` can't be used + to schedule new task in Linux BCM. + :return: - A started task instance. This can be used to modify the data, + A :class:`CyclicSendTask` task instance. This can be used to modify the data, pause/resume the transmission and to stop the transmission. .. note:: @@ -732,18 +755,20 @@ def _send_periodic_internal( be exactly the same as the duration specified by the user. In general the message will be sent at the given rate until at least *duration* seconds. - """ msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages(msgs) msgs_channel = str(msgs[0].channel) if msgs[0].channel else None bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) - # TODO: The SocketCAN BCM interface treats all cyclic tasks sharing an - # Arbitration ID as the same Cyclic group. We should probably warn the - # user instead of overwriting the old group? - task = CyclicSendTask(bcm_socket, msgs, period, duration) + task_id = self._get_next_task_id() + task = CyclicSendTask(bcm_socket, task_id, msgs, period, duration) return task + def _get_next_task_id(self) -> int: + with self._task_id_guard: + self._task_id = (self._task_id + 1) % (2 ** 32 - 1) + return self._task_id + def _get_bcm_socket(self, channel: str) -> socket.socket: if channel not in self._bcm_sockets: self._bcm_sockets[channel] = create_bcm_socket(self.channel) diff --git a/doc/bus.rst b/doc/bus.rst index 5c1e95606..b524d4868 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -67,7 +67,7 @@ This thread safe version of the :class:`~can.BusABC` class can be used by multip Sending and receiving is locked separately to avoid unnecessary delays. Conflicting calls are executed by blocking until the bus is accessible. -It can be used exactly like the normal :class:`~can.BusABC`: +It can be used exactly like the normal :class:`~can.BusABC`:: # 'socketcan' is only an example interface, it works with all the others too my_bus = can.ThreadSafeBus(interface='socketcan', channel='vcan0') diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index bdd934ca7..1783c388c 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -1,16 +1,26 @@ SocketCAN ========= -The full documentation for socketcan can be found in the kernel docs at -`networking/can.txt `_. - - -.. note:: - - Versions before 2.2 had two different implementations named +The `SocketCAN`_ documentation can be found in the Linux kernel docs at +``networking`` directory. Quoting from the SocketCAN Linux documentation:: + +> The socketcan package is an implementation of CAN protocols +> (Controller Area Network) for Linux. CAN is a networking technology +> which has widespread use in automation, embedded devices, and +> automotive fields. While there have been other CAN implementations +> for Linux based on character devices, SocketCAN uses the Berkeley +> socket API, the Linux network stack and implements the CAN device +> drivers as network interfaces. The CAN socket API has been designed +> as similar as possible to the TCP/IP protocols to allow programmers, +> familiar with network programming, to easily learn how to use CAN +> sockets. + +.. important:: + + `python-can` versions before 2.2 had two different implementations named ``socketcan_ctypes`` and ``socketcan_native``. These are now deprecated and the aliases to ``socketcan`` will be removed in - version 4.0. 3.x releases raise a DeprecationWarning. + version 4.0. 3.x releases raise a DeprecationWarning. Socketcan Quickstart @@ -53,7 +63,8 @@ existing ``can0`` interface with a bitrate of 1MB: PCAN ~~~~ -Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketcan`, so there is no need to install any drivers. The CAN interface can be brought like so: +Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketcan`, +so there is no need to install any drivers. The CAN interface can be brought like so: :: @@ -61,12 +72,22 @@ Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketc sudo modprobe peak_pci sudo ip link set can0 up type can bitrate 500000 +Intrepid +~~~~~~~~ + +The Intrepid Control Systems, Inc provides several devices (e.g. ValueCAN) as well +as Linux module and user-space daemon to make it possible to use them via SocketCAN. + +Refer to below repositories for installation instructions: + +- `Intrepid kernel module`_ +- `Intrepid user-space daemon`_ + Send Test Message ^^^^^^^^^^^^^^^^^ -The `can-utils `_ library for linux -includes a script `cansend` which is useful to send known payloads. For -example to send a message on `vcan0`: +The `can-utils`_ library for Linux includes a `cansend` tool which is useful to +send known payloads. For example to send a message on `vcan0`: .. code-block:: bash @@ -138,7 +159,7 @@ To spam a bus: def producer(id): """:param id: Spam the bus with messages including the data id.""" - bus = can.interface.Bus(channel=channel, bustype=bustype) + bus = can.Bus(channel=channel, interface=bustype) for i in range(10): msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], is_extended_id=False) bus.send(msg) @@ -170,8 +191,7 @@ function: import can - can_interface = 'vcan0' - bus = can.interface.Bus(can_interface, bustype='socketcan') + bus = can.Bus(channel='vcan0', interface='socketcan') message = bus.recv() By default, this performs a blocking read, which means ``bus.recv()`` won't @@ -204,30 +224,39 @@ socket api. This allows the cyclic transmission of CAN messages at given interva The overhead for periodic message sending is extremely low as all the heavy lifting occurs within the linux kernel. -send_periodic() -~~~~~~~~~~~~~~~ +The :class:`~can.BusABC` initialized for `socketcan` interface transparently handles +scheduling of CAN messages to Linux BCM via :meth:`~can.BusABC.send_periodic`: -An example that uses the send_periodic is included in ``python-can/examples/cyclic.py`` +.. code-block:: python -The object returned can be used to halt, alter or cancel the periodic message task. + with can.interface.Bus(interface="socketcan", channel="can0") as bus: + task = bus.send_periodic(...) -.. autoclass:: can.interfaces.socketcan.CyclicSendTask +More examples that uses :meth:`~can.BusABC.send_periodic` are included +in ``python-can/examples/cyclic.py``. + +The `task` object returned by :meth:`~can.BusABC.send_periodic` can be used to halt, +alter or cancel the periodic message task: +.. autoclass:: can.interfaces.socketcan.CyclicSendTask + :members: Bus --- -.. autoclass:: can.interfaces.socketcan.SocketcanBus +The :class:`~can.interfaces.socketcan.SocketcanBus` specializes :class:`~can.BusABC` +to ensure usage of SocketCAN Linux API. The most important differences are: - .. method:: recv(timeout=None) +- usage of SocketCAN BCM for periodic messages scheduling; +- filtering of CAN messages on Linux kernel level. - Block waiting for a message from the Bus. +.. autoclass:: can.interfaces.socketcan.SocketcanBus + :members: + :inherited-members: - :param float timeout: - seconds to wait for a message or None to wait indefinitely +.. External references - :rtype: can.Message or None - :return: - None on timeout or a :class:`can.Message` object. - :raises can.CanError: - if an error occurred while reading +.. _SocketCAN: https://www.kernel.org/doc/Documentation/networking/can.txt +.. _Intrepid kernel module: https://github.com/intrepidcs/intrepid-socketcan-kernel-module +.. _Intrepid user-space daemon: https://github.com/intrepidcs/icsscand +.. _can-utils: https://github.com/linux-can/can-utils diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index bb5411be9..40c7af582 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -244,7 +244,27 @@ def test_cyclic_initializer_different_arbitration_ids(self): with self.assertRaises(ValueError): task = self._send_bus.send_periodic(messages, self.PERIOD) - def test_create_same_id_raises_exception(self): + def test_start_already_started_task(self): + messages_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + task_a = self._send_bus.send_periodic(messages_a, self.PERIOD) + time.sleep(0.1) + + # Try to start it again, task_id is not incremented in this case + with self.assertRaises(ValueError) as ctx: + task_a.start() + self.assertEqual( + "A periodic task for Task ID 1 is already in progress by SocketCAN Linux layer", + str(ctx.exception), + ) + + task_a.stop() + + def test_create_same_id(self): messages_a = can.Message( arbitration_id=0x401, data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], @@ -257,13 +277,41 @@ def test_create_same_id_raises_exception(self): is_extended_id=False, ) - task_a = self._send_bus.send_periodic(messages_a, 1) + task_a = self._send_bus.send_periodic(messages_a, self.PERIOD) self.assertIsInstance(task_a, can.broadcastmanager.CyclicSendTaskABC) + task_b = self._send_bus.send_periodic(messages_b, self.PERIOD) + self.assertIsInstance(task_b, can.broadcastmanager.CyclicSendTaskABC) - # The second one raises a ValueError when we attempt to create a new - # Task, since it has the same arbitration ID. - with self.assertRaises(ValueError): - task_b = self._send_bus.send_periodic(messages_b, 1) + time.sleep(self.PERIOD * 4) + + task_a.stop() + task_b.stop() + + msgs = [] + for _ in range(4): + msg = self._recv_bus.recv(self.PERIOD * 2) + self.assertIsNotNone(msg) + + msgs.append(msg) + + self.assertTrue(len(msgs) >= 4) + + # Both messages should be recevied on the bus, + # even with the same arbitration id + msg_a_data_present = msg_b_data_present = False + for rx_message in msgs: + self.assertTrue( + rx_message.arbitration_id + == messages_a.arbitration_id + == messages_b.arbitration_id + ) + if rx_message.data == messages_a.data: + msg_a_data_present = True + if rx_message.data == messages_b.data: + msg_b_data_present = True + + self.assertTrue(msg_a_data_present) + self.assertTrue(msg_b_data_present) def test_modify_data_list(self): messages_odd = [] From 56c410cda07fa62d8037da857a9b3c1446a0a638 Mon Sep 17 00:00:00 2001 From: Joey5337 <63902653+Joey5337@users.noreply.github.com> Date: Sun, 19 Apr 2020 04:44:44 +0200 Subject: [PATCH 0380/1235] Update api.rst (#812) Clarification for Notifier docs --- doc/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.rst b/doc/api.rst index 193d1c707..b3191cad2 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -33,7 +33,7 @@ Utilities Notifier -------- -The Notifier object is used as a message distributor for a bus. +The Notifier object is used as a message distributor for a bus. Notifier creates a thread to read messages from the bus and distributes them to listeners. .. autoclass:: can.Notifier :members: From 0ee67040673e64d7d5d1d210af7012d41ef0a3d3 Mon Sep 17 00:00:00 2001 From: karl ding Date: Sat, 18 Apr 2020 19:56:59 -0700 Subject: [PATCH 0381/1235] Add typing annotations for Virtual interface (#813) This works towards PEP 561 compatibility. --- .travis.yml | 1 + can/bus.py | 4 ++-- can/interfaces/virtual.py | 31 ++++++++++++++++++++++--------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7b299feb7..76195ce51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,6 +105,7 @@ jobs: can/bus.py can/interface.py can/interfaces/socketcan/**.py + can/interfaces/virtual.py can/listener.py can/logger.py can/message.py diff --git a/can/bus.py b/can/bus.py index 4ef5b5fd6..e315ee9d5 100644 --- a/can/bus.py +++ b/can/bus.py @@ -2,7 +2,7 @@ Contains the ABC bus implementation and its documentation. """ -from typing import cast, Iterator, List, Optional, Sequence, Tuple, Union +from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union import can.typechecking @@ -43,7 +43,7 @@ class BusABC(metaclass=ABCMeta): @abstractmethod def __init__( self, - channel: can.typechecking.Channel, + channel: Any, can_filters: Optional[can.typechecking.CanFilters] = None, **kwargs: object ): diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index e84fa5853..937084095 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -5,6 +5,8 @@ Any VirtualBus instances connecting to the same channel and reside in the same process will receive the same messages. """ +from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING +from can import typechecking from copy import deepcopy import logging @@ -13,14 +15,19 @@ from threading import RLock from random import randint -from can.bus import BusABC from can import CanError +from can.bus import BusABC +from can.message import Message logger = logging.getLogger(__name__) # Channels are lists of queues, one for each connection -channels = {} +if TYPE_CHECKING: + # https://mypy.readthedocs.io/en/stable/common_issues.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime + channels: Dict[Optional[Any], List[queue.Queue[Message]]] = {} +else: + channels = {} channels_lock = RLock() @@ -43,8 +50,12 @@ class VirtualBus(BusABC): """ def __init__( - self, channel=None, receive_own_messages=False, rx_queue_size=0, **kwargs - ): + self, + channel: Any = None, + receive_own_messages: bool = False, + rx_queue_size: int = 0, + **kwargs: Any + ) -> None: super().__init__( channel=channel, receive_own_messages=receive_own_messages, **kwargs ) @@ -62,10 +73,10 @@ def __init__( channels[self.channel_id] = [] self.channel = channels[self.channel_id] - self.queue = queue.Queue(rx_queue_size) + self.queue: queue.Queue[Message] = queue.Queue(rx_queue_size) self.channel.append(self.queue) - def _check_if_open(self): + def _check_if_open(self) -> None: """Raises CanError if the bus is not open. Has to be called in every method that accesses the bus. @@ -73,7 +84,9 @@ def _check_if_open(self): if not self._open: raise CanError("Operation on closed bus") - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: self._check_if_open() try: msg = self.queue.get(block=True, timeout=timeout) @@ -82,7 +95,7 @@ def _recv_internal(self, timeout): else: return msg, False - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: self._check_if_open() timestamp = time.time() @@ -102,7 +115,7 @@ def send(self, msg, timeout=None): if not all_sent: raise CanError("Could not send message to one or more recipients") - def shutdown(self): + def shutdown(self) -> None: self._check_if_open() self._open = False From 7780242183302cadb15e1f8337a94303097657f6 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Sun, 19 Apr 2020 05:32:46 +0200 Subject: [PATCH 0382/1235] Seek for start of object instead of calculating it (#806) Fixes #803 Fixes #786 --- can/io/blf.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index ca0b5bd38..064a95f7d 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -175,7 +175,7 @@ def __iter__(self): if obj_type == LOG_CONTAINER: method, uncompressed_size = LOG_CONTAINER_STRUCT.unpack_from(obj_data) - container_data = memoryview(obj_data)[LOG_CONTAINER_STRUCT.size :] + container_data = obj_data[LOG_CONTAINER_STRUCT.size :] if method == NO_COMPRESSION: data = container_data elif method == ZLIB_DEFLATE: @@ -220,6 +220,14 @@ def _parse_data(self, data): # Loop until a struct unpack raises an exception while True: self._pos = pos + # Find next object after padding (depends on object type) + try: + pos = data.index(b"LOBJ", pos, pos + 8) + except ValueError: + if pos + 8 > max_pos: + # Not enough data in container + return + raise BLFParseError("Could not find next object") header = unpack_obj_header_base(data, pos) # print(header) signature, _, header_version, obj_size, obj_type = header @@ -228,9 +236,6 @@ def _parse_data(self, data): # Calculate position of next object next_pos = pos + obj_size - if obj_type != CAN_FD_MESSAGE_64: - # Add padding bytes - next_pos += obj_size % 4 if next_pos > max_pos: # This object continues in the next container return From b8ca2126c63b34ac1125a7f78649eb05ddd9ec34 Mon Sep 17 00:00:00 2001 From: Aajn Date: Sun, 19 Apr 2020 06:58:33 +0200 Subject: [PATCH 0383/1235] Notifier no longer raises handled exceptions in rx_thread (#789) --- can/notifier.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index 2b909cae7..de2894f64 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -118,9 +118,9 @@ def _rx_thread(self, bus: BusABC): self.exception = exc if self._loop is not None: self._loop.call_soon_threadsafe(self._on_error, exc) - else: - self._on_error(exc) - raise + raise + elif not self._on_error(exc): + raise def _on_message_available(self, bus: BusABC): msg = bus.recv(0) @@ -134,10 +134,15 @@ def _on_message_received(self, msg: Message): # Schedule coroutine self._loop.create_task(res) - def _on_error(self, exc: Exception): - for listener in self.listeners: - if hasattr(listener, "on_error"): - listener.on_error(exc) + def _on_error(self, exc: Exception) -> bool: + listeners_with_on_error = [ + listener for listener in self.listeners if hasattr(listener, "on_error") + ] + + for listener in listeners_with_on_error: + listener.on_error(exc) + + return bool(listeners_with_on_error) def add_listener(self, listener: Listener): """Add new Listener to the notification list. From b418245e2826e7d7aac86153a20abe132e413954 Mon Sep 17 00:00:00 2001 From: tamenol <37591107+tamenol@users.noreply.github.com> Date: Sun, 19 Apr 2020 08:21:25 +0200 Subject: [PATCH 0384/1235] fix winreg bug in pcan (#802) only import winreg when on Windows --- can/interfaces/pcan/basic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 90599910a..94a9ec950 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -14,7 +14,9 @@ from string import * import platform import logging -import winreg + +if platform.system() == "Windows": + import winreg logger = logging.getLogger("can.pcan") From 8c1b3f3ab481ed98b9c45ebdff645023fde0a0b3 Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 19 Apr 2020 00:30:15 -0700 Subject: [PATCH 0385/1235] Fix Vector CANlib treatment of empty app name In Python 2, the str type was used for text and bytes, whereas in Python 3, these are separate and incompatible types. This broke instantiation of a VectorBus when the app_name parameter in __init__ was set to None. This correctly sets it to a bytes object. Fixes #796 --- can/interfaces/vector/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 14205327b..ac85ef4ce 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -112,7 +112,7 @@ def __init__( else: # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(",")] - self._app_name = app_name.encode() if app_name is not None else "" + self._app_name = app_name.encode() if app_name is not None else b"" self.channel_info = "Application %s: %s" % ( app_name, ", ".join("CAN %d" % (ch + 1) for ch in self.channels), From 50869acaf5afc77cf52dc3a63d55341b142d0040 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 29 Apr 2020 12:05:58 +0200 Subject: [PATCH 0386/1235] fix linter complaints --- can/interfaces/socketcan/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index f96c0b344..80ba37d29 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -2,12 +2,12 @@ Defines common socketcan functions. """ +import errno import logging import os -import errno +import re import struct import subprocess -import re from typing import cast, Iterable, Optional from can.interfaces.socketcan.constants import CAN_EFF_FLAG @@ -73,7 +73,7 @@ def error_code_to_str(code: Optional[int]) -> str: :returns: a string explaining and containing the given error code, or a string explaining that the errorcode is unknown if that is the case """ - name = errno.errorcode.get(code, "UNKNOWN") - description = os.strerror(code) + name = errno.errorcode.get(code, "UNKNOWN") if code is not None else "UNKNOWN" + description = os.strerror(code) if code is not None else "NO DESCRIPTION AVAILABLE" return f"{name} (errno {code}): {description}" From de0613ee32be4826f833f09f79405cf651a4ec4b Mon Sep 17 00:00:00 2001 From: Bruno Kremel Date: Wed, 29 Apr 2020 17:33:14 +0200 Subject: [PATCH 0387/1235] Add _detect_available_configs to serial_can (#811) Co-authored-by: Felix Divo --- can/interfaces/serial/serial_can.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index d7a81c98a..63bca5477 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -21,6 +21,11 @@ ) serial = None +try: + from serial.tools import list_ports +except ImportError: + list_ports = None + class SerialBus(BusABC): """ @@ -162,3 +167,15 @@ def fileno(self): return self.ser.fileno() # Return an invalid file descriptor on Windows return -1 + + @staticmethod + def _detect_available_configs(): + channels = [] + serial_ports = [] + + if list_ports: + serial_ports = list_ports.comports() + + for port in serial_ports: + channels.append({"interface": "serial", "channel": port.device}) + return channels From 5cada6db701724899c69db4a0c3638a66832631a Mon Sep 17 00:00:00 2001 From: karl ding Date: Sat, 2 May 2020 12:57:56 -0700 Subject: [PATCH 0388/1235] Add typing annotations for ASC module (#818) This works towards PEP 561 compatibility. --- can/io/asc.py | 50 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index c8a2aada3..1ad3acef6 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -6,6 +6,9 @@ - under `test/data/logfile.asc` """ +from typing import cast, Any, Generator, IO, List, Optional, Tuple, Union +from can import typechecking + from datetime import datetime import time import logging @@ -32,7 +35,11 @@ class ASCReader(BaseIOHandler): TODO: turn relative timestamps back to absolute form """ - def __init__(self, file, base="hex"): + def __init__( + self, + file: Union[typechecking.FileLike, typechecking.StringPathLike], + base: str = "hex", + ) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text @@ -42,10 +49,13 @@ def __init__(self, file, base="hex"): this value will be overwritten. Default "hex". """ super().__init__(file, mode="r") + + if not self.file: + raise ValueError("The given file cannot be None") self.base = base @staticmethod - def _extract_can_id(str_can_id, base): + def _extract_can_id(str_can_id: str, base: int) -> Tuple[int, bool]: if str_can_id[-1:].lower() == "x": is_extended = True can_id = int(str_can_id[0:-1], base) @@ -55,13 +65,15 @@ def _extract_can_id(str_can_id, base): return can_id, is_extended @staticmethod - def _check_base(base): + def _check_base(base: str) -> int: if base not in ["hex", "dec"]: raise ValueError('base should be either "hex" or "dec"') return BASE_DEC if base == "dec" else BASE_HEX - def __iter__(self): + def __iter__(self) -> Generator[Message, None, None]: base = self._check_base(self.base) + # This is guaranteed to not be None since we raise ValueError in __init__ + self.file = cast(IO[Any], self.file) for line in self.file: # logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0]) if line.split(" ")[0] == "base": @@ -194,7 +206,11 @@ class ASCWriter(BaseIOHandler, Listener): FORMAT_DATE = "%a %b %d %I:%M:%S.{} %p %Y" FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" - def __init__(self, file, channel=1): + def __init__( + self, + file: Union[typechecking.FileLike, typechecking.StringPathLike], + channel: int = 1, + ) -> None: """ :param file: a path-like object or as file-like object to write to If this is a file-like object, is has to opened in text @@ -203,6 +219,9 @@ def __init__(self, file, channel=1): have a channel set """ super().__init__(file, mode="w") + if not self.file: + raise ValueError("The given file cannot be None") + self.channel = channel # write start of file header @@ -213,24 +232,29 @@ def __init__(self, file, channel=1): # the last part is written with the timestamp of the first message self.header_written = False - self.last_timestamp = None - self.started = None + self.last_timestamp = 0.0 + self.started = 0.0 - def stop(self): + def stop(self) -> None: + # This is guaranteed to not be None since we raise ValueError in __init__ + self.file = cast(IO[Any], self.file) if not self.file.closed: self.file.write("End TriggerBlock\n") super().stop() - def log_event(self, message, timestamp=None): + def log_event(self, message: str, timestamp: Optional[float] = None) -> None: """Add a message to the log file. - :param str message: an arbitrary message - :param float timestamp: the absolute timestamp of the event + :param message: an arbitrary message + :param timestamp: the absolute timestamp of the event """ if not message: # if empty or None logger.debug("ASCWriter: ignoring empty message") return + # This is guaranteed to not be None since we raise ValueError in __init__ + self.file = cast(IO[Any], self.file) + # this is the case for the very first message: if not self.header_written: self.last_timestamp = timestamp or 0.0 @@ -251,14 +275,14 @@ def log_event(self, message, timestamp=None): line = self.FORMAT_EVENT.format(timestamp=timestamp, message=message) self.file.write(line) - def on_message_received(self, msg): + def on_message_received(self, msg: Message) -> None: if msg.is_error_frame: self.log_event("{} ErrorFrame".format(self.channel), msg.timestamp) return if msg.is_remote_frame: dtype = "r" - data = [] + data: List[str] = [] else: dtype = "d {}".format(msg.dlc) data = ["{:02X}".format(byte) for byte in msg.data] From 18239565cb1971f81392e0d87ce4b2f2e73df02d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 3 May 2020 11:36:19 +0200 Subject: [PATCH 0389/1235] Fix typo in can/interfaces/socketcan/socketcan.py Co-authored-by: karl ding --- can/interfaces/socketcan/socketcan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 7e6e2342f..cb17b773a 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -597,7 +597,7 @@ def __init__( ) -> None: """Creates a new socketcan bus. - It setting some socket options fails, an error will be printed but no exception will be thrown. + If setting some socket options fails, an error will be printed but no exception will be thrown. This includes enabling: - that own messages should be received, - CAN-FD frames and From 59139e9129dfcb127a9f449caf6dfc4e57c837e9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 3 May 2020 11:41:38 +0200 Subject: [PATCH 0390/1235] revert change to codecov config --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index b9b9b52b6..16168f521 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,7 +1,7 @@ # Validate with curl --data-binary @.codecov.yml https://codecov.io/validate codecov: archive: - uploads: yes + uploads: no coverage: precision: 2 From 41e47064a80abda2892a3fe68b7115b9e4bf4469 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 3 May 2020 11:59:14 +0200 Subject: [PATCH 0391/1235] add can_filters with typing information to the signature of SocketcanBus --- can/interfaces/socketcan/socketcan.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index cb17b773a..28c16482a 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -34,6 +34,7 @@ RestartableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, ) +from can.typechecking import CanFilters from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces @@ -593,6 +594,7 @@ def __init__( channel: str = "", receive_own_messages: bool = False, fd: bool = False, + can_filters: Optional[CanFilters] = None, **kwargs, ) -> None: """Creates a new socketcan bus. @@ -613,7 +615,7 @@ def __init__( If transmitted messages should also be received by this bus. :param fd: If CAN-FD frames should be supported. - :param List can_filters: + :param can_filters: See :meth:`can.BusABC.set_filters`. """ self.socket = create_socket() @@ -647,7 +649,7 @@ def __init__( bind_socket(self.socket, channel) kwargs.update({"receive_own_messages": receive_own_messages, "fd": fd}) - super().__init__(channel=channel, **kwargs) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) def shutdown(self) -> None: """Stops all active periodic tasks and closes the socket.""" From 96a2707b7333d83911e3252b79e2041d8dba2434 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 4 May 2020 20:43:53 +0200 Subject: [PATCH 0392/1235] address review --- can/interfaces/socketcan/utils.py | 8 ++++---- test/test_socketcan_helpers.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 80ba37d29..912580422 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -60,9 +60,9 @@ def find_available_interfaces() -> Iterable[str]: # log.debug("find_available_interfaces(): output=\n%s", output) # output contains some lines like "1: vcan42: ..." # extract the "vcan42" of each line - interface_names = [line.split(": ", 3)[1] for line in output.splitlines()] - log.debug("find_available_interfaces(): detected: %s", interface_names) - return filter(_PATTERN_CAN_INTERFACE.match, interface_names) + interfaces = [line.split(": ", 3)[1] for line in output.splitlines()] + log.debug("find_available_interfaces(): detected these interfaces (before filtering): %s", interfaces) + return filter(_PATTERN_CAN_INTERFACE.match, interfaces) def error_code_to_str(code: Optional[int]) -> str: @@ -73,7 +73,7 @@ def error_code_to_str(code: Optional[int]) -> str: :returns: a string explaining and containing the given error code, or a string explaining that the errorcode is unknown if that is the case """ - name = errno.errorcode.get(code, "UNKNOWN") if code is not None else "UNKNOWN" + name = errno.errorcode.get(code, "UNKNOWN") # type: ignore description = os.strerror(code) if code is not None else "NO DESCRIPTION AVAILABLE" return f"{name} (errno {code}): {description}" diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index 551b4dc1a..669491f43 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -21,7 +21,7 @@ def test_error_code_to_str(self): """ # all possible & also some invalid error codes - test_data = list(range(0, 256)) + [-1, 256, 5235, 346264] + test_data = list(range(0, 256)) + [-1, 256, 5235, 346264, None] for error_code in test_data: string = error_code_to_str(error_code) From fa9df7bb61f202d627b34aa28589c266994398db Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Tue, 5 May 2020 12:43:11 +0300 Subject: [PATCH 0393/1235] Add an error callback to ThreadBasedCyclicSendTask (#781) * Add an error callback to ThreadBasedCyclicSendTask * Fix formatting error reported by black * Add return value for on_error * unit tests * black * review comments Co-authored-by: Felix Divo --- can/broadcastmanager.py | 25 ++++++++++++++++++++++--- test/simplecyclic_test.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 52f0550f2..173727789 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -5,7 +5,7 @@ :meth:`can.BusABC.send_periodic`. """ -from typing import Optional, Sequence, Tuple, Union, TYPE_CHECKING +from typing import Optional, Sequence, Tuple, Union, Callable, TYPE_CHECKING from can import typechecking @@ -198,7 +198,7 @@ def __init__( class ThreadBasedCyclicSendTask( ModifiableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC ): - """Fallback cyclic send task using thread.""" + """Fallback cyclic send task using daemon thread.""" def __init__( self, @@ -207,13 +207,28 @@ def __init__( messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + on_error: Optional[Callable[[Exception], bool]] = None, ): + """Transmits `messages` with a `period` seconds for `duration` seconds on a `bus`. + + The `on_error` is called if any error happens on `bus` while sending `messages`. + If `on_error` present, and returns ``False`` when invoked, thread is + stopped immediately, otherwise, thread continuiously tries to send `messages` + ignoring errors on a `bus`. Absence of `on_error` means that thread exits immediately + on error. + + :param on_error: The callable that accepts an exception if any + error happened on a `bus` while sending `messages`, + it shall return either ``True`` or ``False`` depending + on desired behaviour of `ThreadBasedCyclicSendTask`. + """ super().__init__(messages, period, duration) self.bus = bus self.send_lock = lock self.stopped = True self.thread = None self.end_time = time.perf_counter() + duration if duration else None + self.on_error = on_error if HAS_EVENTS: self.period_ms: int = int(round(period * 1000, 0)) @@ -250,7 +265,11 @@ def _run(self): self.bus.send(self.messages[msg_index]) except Exception as exc: log.exception(exc) - break + if self.on_error: + if not self.on_error(exc): + break + else: + break if self.end_time is not None and time.perf_counter() >= self.end_time: break msg_index = (msg_index + 1) % len(self.messages) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 95bbd0a99..4e41e5e03 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -7,6 +7,7 @@ from time import sleep import unittest +from unittest.mock import MagicMock import gc import can @@ -151,6 +152,43 @@ def test_stopping_perodic_tasks(self): bus.shutdown() + def test_thread_based_cyclic_send_task(self): + bus = can.ThreadSafeBus(bustype="virtual") + msg = can.Message( + is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] + ) + + # good case, bus is up + on_error_mock = MagicMock(return_value=False) + task = can.broadcastmanager.ThreadBasedCyclicSendTask( + bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + ) + task.start() + sleep(1) + on_error_mock.assert_not_called() + task.stop() + bus.shutdown() + + # bus has been shutted down + on_error_mock.reset_mock() + task = can.broadcastmanager.ThreadBasedCyclicSendTask( + bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + ) + task.start() + sleep(1) + self.assertTrue(on_error_mock.call_count is 1) + task.stop() + + # bus is still shutted down, but on_error returns True + on_error_mock = MagicMock(return_value=True) + task = can.broadcastmanager.ThreadBasedCyclicSendTask( + bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + ) + task.start() + sleep(1) + self.assertTrue(on_error_mock.call_count > 1) + task.stop() + if __name__ == "__main__": unittest.main() From 397e487875eaa0f96ce77da82c324d9cbf2406d7 Mon Sep 17 00:00:00 2001 From: Syed Raza Date: Wed, 6 May 2020 11:01:04 -0400 Subject: [PATCH 0394/1235] Possible fix for tests sometimes failing --- test/simplecyclic_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 4e41e5e03..e522468a0 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -168,6 +168,7 @@ def test_thread_based_cyclic_send_task(self): on_error_mock.assert_not_called() task.stop() bus.shutdown() + sleep(.5) # bus has been shutted down on_error_mock.reset_mock() From ffbdab657ede8552e576198c33db6cbce09be47f Mon Sep 17 00:00:00 2001 From: Syed Raza Date: Wed, 6 May 2020 11:13:39 -0400 Subject: [PATCH 0395/1235] Same idea as before but faster --- test/simplecyclic_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index e522468a0..927855867 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -168,10 +168,9 @@ def test_thread_based_cyclic_send_task(self): on_error_mock.assert_not_called() task.stop() bus.shutdown() - sleep(.5) # bus has been shutted down - on_error_mock.reset_mock() + on_error_mock = MagicMock(return_value=False) task = can.broadcastmanager.ThreadBasedCyclicSendTask( bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock ) From bc9a7be122410883ae75035353c8f686720e0056 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 8 May 2020 11:33:43 -0400 Subject: [PATCH 0396/1235] Update windows APPDATA location (#831) * Update windows APPDATA location * Update %USERPROFILE% path --- doc/configuration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index ea56c73ee..9bda3030f 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -43,9 +43,9 @@ On Linux systems the config file is searched in the following paths: On Windows systems the config file is searched in the following paths: -#. ``~/can.conf`` +#. ``%USERPROFILE%/can.conf`` #. ``can.ini`` (current working directory) -#. ``$APPDATA/can.ini`` +#. ``%APPDATA%/can.ini`` The configuration file sets the default interface and channel: From ef2ec5f26d9dff0428f1cce2b8d85794b9bbbfd5 Mon Sep 17 00:00:00 2001 From: leventerevesz <35273710+leventerevesz@users.noreply.github.com> Date: Mon, 11 May 2020 18:06:10 +0200 Subject: [PATCH 0397/1235] Fix two typos in the BufferedReader docstring (#815) Co-authored-by: Brian Thorne --- can/listener.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/listener.py b/can/listener.py index d03a7e0c8..b9f6b5b8c 100644 --- a/can/listener.py +++ b/can/listener.py @@ -82,10 +82,10 @@ class BufferedReader(Listener): be serviced. The messages can then be fetched with :meth:`~can.BufferedReader.get_message`. - Putting in messages after :meth:`~can.BufferedReader.stop` has be called will raise + Putting in messages after :meth:`~can.BufferedReader.stop` has been called will raise an exception, see :meth:`~can.BufferedReader.on_message_received`. - :attr bool is_stopped: ``True`` iff the reader has been stopped + :attr bool is_stopped: ``True`` if the reader has been stopped """ def __init__(self): From 99f1e37ca9f5adec6a976126392f48d7308dc8e5 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 11 May 2020 23:07:02 +0200 Subject: [PATCH 0398/1235] implement xlSetApplConfig and xlGetApplConfig --- can/interfaces/vector/canlib.py | 74 ++++++++++++++++++++++++++++++- can/interfaces/vector/xldefine.py | 57 ++++++++++++++++++++++++ can/interfaces/vector/xldriver.py | 24 ++++++++++ 3 files changed, 153 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ac85ef4ce..a566a4929 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -586,15 +586,85 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: """ xldriver.xlPopupHwConfig(ctypes.c_char_p(), ctypes.c_uint(wait_for_finish)) + @staticmethod + def get_application_config( + app_name: str, app_channel: int, bus_type: xldefine.XL_BusTypes, + ): + """Retrieve information for an application in Vector Hardware Configuration. + + :param app_name: + The name of the application. + :param app_channel: + The channel of the application. + :param bus_type: + The bus type Enum e.g. `XL_BusTypes.XL_BUS_TYPE_CAN` + :return: + Retruns a tuple of the hardware type, the hardware index and the + hardware channel. + :raises VectorError: + Raises a VectorError when the application name does not exist in + Vector Hardware Configuration. + """ + hw_type = ctypes.c_uint() + hw_index = ctypes.c_uint() + hw_channel = ctypes.c_uint() + + xldriver.xlGetApplConfig( + app_name.encode(), + app_channel, + hw_type, + hw_index, + hw_channel, + bus_type.value, + ) + return xldefine.XL_HardwareType(hw_type.value), hw_index, hw_channel + + @staticmethod + def set_application_config( + app_name: str, + app_channel: int, + hw_type: xldefine.XL_HardwareType, + hw_index: int, + hw_channel: int, + bus_type: xldefine.XL_BusTypes, + ) -> None: + """Modify the application settings in Vector Hardware Configuration. + + :param app_name: + The name of the application. Creates a new application if it does + not exist yet. + :param hw_type: + The hardware type of the interface. + E.g XL_HardwareType.XL_HWTYPE_VIRTUAL + :param hw_index: + The index of the interface if multiple interface with the same + hardware type are present. + :param hw_channel: + The channel index of the interface. + :param bus_type: + The bus type of the interfaces, which should be + XL_BusTypes.XL_BUS_TYPE_CAN for most cases. + :return: + """ + + xldriver.xlSetApplConfig( + app_name.encode(), + app_channel, + hw_type.value, + hw_index, + hw_channel, + bus_type.value, + ) + def get_channel_configs(): if xldriver is None: return [] driver_config = xlclass.XLdriverConfig() try: - xldriver.xlOpenDriver() + # xldriver.xlOpenDriver() xldriver.xlGetDriverConfig(driver_config) - xldriver.xlCloseDriver() + # xldriver.xlCloseDriver() except Exception: pass return [driver_config.channel[i] for i in range(driver_config.channelCount)] diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index 1d130624b..d4134001b 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -168,3 +168,60 @@ class XL_TimeSyncNewValue(Enum): XL_SET_TIMESYNC_NO_CHANGE = 0 XL_SET_TIMESYNC_ON = 1 XL_SET_TIMESYNC_OFF = 2 + + +class XL_HardwareType(Enum): + XL_HWTYPE_NONE = 0 + XL_HWTYPE_VIRTUAL = 1 + XL_HWTYPE_CANCARDX = 2 + XL_HWTYPE_CANAC2PCI = 6 + XL_HWTYPE_CANCARDY = 12 + XL_HWTYPE_CANCARDXL = 15 + XL_HWTYPE_CANCASEXL = 21 + XL_HWTYPE_CANCASEXL_LOG_OBSOLETE = 23 + XL_HWTYPE_CANBOARDXL = 25 + XL_HWTYPE_CANBOARDXL_PXI = 27 + XL_HWTYPE_VN2600 = 29 + XL_HWTYPE_VN2610 = XL_HWTYPE_VN2600 + XL_HWTYPE_VN3300 = 37 + XL_HWTYPE_VN3600 = 39 + XL_HWTYPE_VN7600 = 41 + XL_HWTYPE_CANCARDXLE = 43 + XL_HWTYPE_VN8900 = 45 + XL_HWTYPE_VN8950 = 47 + XL_HWTYPE_VN2640 = 53 + XL_HWTYPE_VN1610 = 55 + XL_HWTYPE_VN1630 = 57 + XL_HWTYPE_VN1640 = 59 + XL_HWTYPE_VN8970 = 61 + XL_HWTYPE_VN1611 = 63 + XL_HWTYPE_VN5610 = 65 + XL_HWTYPE_VN5620 = 66 + XL_HWTYPE_VN7570 = 67 + XL_HWTYPE_IPCLIENT = 69 + XL_HWTYPE_IPSERVER = 71 + XL_HWTYPE_VX1121 = 73 + XL_HWTYPE_VX1131 = 75 + XL_HWTYPE_VT6204 = 77 + XL_HWTYPE_VN1630_LOG = 79 + XL_HWTYPE_VN7610 = 81 + XL_HWTYPE_VN7572 = 83 + XL_HWTYPE_VN8972 = 85 + XL_HWTYPE_VN0601 = 87 + XL_HWTYPE_VN5640 = 89 + XL_HWTYPE_VX0312 = 91 + XL_HWTYPE_VH6501 = 94 + XL_HWTYPE_VN8800 = 95 + XL_HWTYPE_IPCL8800 = 96 + XL_HWTYPE_IPSRV8800 = 97 + XL_HWTYPE_CSMCAN = 98 + XL_HWTYPE_VN5610A = 101 + XL_HWTYPE_VN7640 = 102 + XL_HWTYPE_VX1135 = 104 + XL_HWTYPE_VN4610 = 105 + XL_HWTYPE_VT6306 = 107 + XL_HWTYPE_VT6104A = 108 + XL_HWTYPE_VN5430 = 109 + XL_HWTYPE_VN1530 = 112 + XL_HWTYPE_VN1531 = 113 + XL_MAX_HWTYPE = 113 diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 7a361a29d..c33f835bb 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -234,3 +234,27 @@ def check_status(result, function, arguments): xlPopupHwConfig.argtypes = [ctypes.c_char_p, ctypes.c_uint] xlPopupHwConfig.restype = xlclass.XLstatus xlPopupHwConfig.errcheck = check_status + +xlSetApplConfig = _xlapi_dll.xlSetApplConfig +xlSetApplConfig.argtypes = [ + ctypes.c_char_p, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, +] +xlSetApplConfig.restype = xlclass.XLstatus +xlSetApplConfig.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 From 0cd0f4a3199e559c9c91b1386b60739d4f38178c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 11 May 2020 23:29:10 +0200 Subject: [PATCH 0399/1235] implement xlSetTimerRate --- can/interfaces/vector/canlib.py | 4 ++++ can/interfaces/vector/xldriver.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ac85ef4ce..b741a0c34 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -586,6 +586,10 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: """ xldriver.xlPopupHwConfig(ctypes.c_char_p(), ctypes.c_uint(wait_for_finish)) + def set_timer_rate(self, timer_rate_ms: int): + timer_rate_10us = timer_rate_ms * 100 + xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) + def get_channel_configs(): if xldriver is None: diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 7a361a29d..0928049c5 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -234,3 +234,8 @@ def check_status(result, function, arguments): xlPopupHwConfig.argtypes = [ctypes.c_char_p, ctypes.c_uint] xlPopupHwConfig.restype = xlclass.XLstatus xlPopupHwConfig.errcheck = check_status + +xlSetTimerRate = _xlapi_dll.xlSetTimerRate +xlSetTimerRate.argtypes = [xlclass.XLportHandle, ctypes.c_ulong] +xlSetTimerRate.restype = xlclass.XLstatus +xlSetTimerRate.errcheck = check_status From 30ef0c31202d57afbec7a54ffca36d12385b64b9 Mon Sep 17 00:00:00 2001 From: Syed Date: Tue, 12 May 2020 05:28:42 -0400 Subject: [PATCH 0400/1235] ASCII Reader/Writer enhancements (#820) Added support for new formats for CAN remote frames Reworked and cleaned existing implementation Some minor fixes (e.g. dlc != len(data) for FD frames) Added parsing ASCII header parsing --- can/io/asc.py | 239 +++++++++++++++----------- test/data/example_data.py | 6 +- test/data/logfile.asc | 10 ++ test/data/test_CanErrorFrames.asc | 11 ++ test/data/test_CanFdMessage.asc | 10 ++ test/data/test_CanFdMessage64.asc | 9 + test/data/test_CanFdRemoteMessage.asc | 8 + test/data/test_CanMessage.asc | 9 + test/data/test_CanRemoteMessage.asc | 10 ++ test/logformats_test.py | 151 ++++++++++++++++ 10 files changed, 363 insertions(+), 100 deletions(-) create mode 100644 test/data/test_CanErrorFrames.asc create mode 100644 test/data/test_CanFdMessage.asc create mode 100644 test/data/test_CanFdMessage64.asc create mode 100644 test/data/test_CanFdRemoteMessage.asc create mode 100644 test/data/test_CanMessage.asc create mode 100644 test/data/test_CanRemoteMessage.asc diff --git a/can/io/asc.py b/can/io/asc.py index 1ad3acef6..f28806478 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -6,7 +6,7 @@ - under `test/data/logfile.asc` """ -from typing import cast, Any, Generator, IO, List, Optional, Tuple, Union +from typing import cast, Any, Generator, IO, List, Optional, Union, Dict from can import typechecking from datetime import datetime @@ -53,16 +53,40 @@ def __init__( if not self.file: raise ValueError("The given file cannot be None") self.base = base + self._converted_base = self._check_base(base) + self.date = None + self.timestamps_format = None + self.internal_events_logged = None - @staticmethod - def _extract_can_id(str_can_id: str, base: int) -> Tuple[int, bool]: + def _extract_header(self): + for line in self.file: + line = line.strip() + lower_case = line.lower() + if lower_case.startswith("date"): + self.date = line[5:] + elif lower_case.startswith("base"): + try: + _, base, _, timestamp_format = line.split() + except ValueError: + raise Exception("Unsupported header string format: {}".format(line)) + self.base = base + self._converted_base = self._check_base(self.base) + self.timestamps_format = timestamp_format + elif lower_case.endswith("internal events logged"): + self.internal_events_logged = not lower_case.startswith("no") + # Currently the last line in the header which is parsed + break + else: + break + + def _extract_can_id(self, str_can_id: str, msg_kwargs: Dict[str, Any]) -> None: if str_can_id[-1:].lower() == "x": - is_extended = True - can_id = int(str_can_id[0:-1], base) + msg_kwargs["is_extended_id"] = True + can_id = int(str_can_id[0:-1], self._converted_base) else: - is_extended = False - can_id = int(str_can_id, base) - return can_id, is_extended + msg_kwargs["is_extended_id"] = False + can_id = int(str_can_id, self._converted_base) + msg_kwargs["arbitration_id"] = can_id @staticmethod def _check_base(base: str) -> int: @@ -70,105 +94,124 @@ def _check_base(base: str) -> int: raise ValueError('base should be either "hex" or "dec"') return BASE_DEC if base == "dec" else BASE_HEX + def _process_data_string( + self, data_str: str, data_length: int, msg_kwargs: Dict[str, Any] + ) -> None: + frame = bytearray() + data = data_str.split() + for byte in data[:data_length]: + frame.append(int(byte, self._converted_base)) + msg_kwargs["data"] = frame + + def _process_classic_can_frame( + self, line: str, msg_kwargs: Dict[str, Any] + ) -> Message: + + # CAN error frame + if line.strip()[0:10].lower() == "errorframe": + # Error Frame + msg_kwargs["is_error_frame"] = True + else: + abr_id_str, dir, rest_of_message = line.split(None, 2) + msg_kwargs["is_rx"] = dir == "Rx" + self._extract_can_id(abr_id_str, msg_kwargs) + + if rest_of_message[0].lower() == "r": + # CAN Remote Frame + msg_kwargs["is_remote_frame"] = True + remote_data = rest_of_message.split() + if len(remote_data) > 1: + dlc_str = remote_data[1] + if dlc_str.isdigit(): + msg_kwargs["dlc"] = int(dlc_str, self._converted_base) + else: + # Classic CAN Message + try: + # There is data after DLC + _, dlc_str, data = rest_of_message.split(None, 2) + except ValueError: + # No data after DLC + _, dlc_str = rest_of_message.split(None, 1) + data = "" + + dlc = int(dlc_str, self._converted_base) + msg_kwargs["dlc"] = dlc + self._process_data_string(data, dlc, msg_kwargs) + + return Message(**msg_kwargs) + + def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Message: + channel, dir, rest_of_message = line.split(None, 2) + # See ASCWriter + msg_kwargs["channel"] = int(channel) - 1 + msg_kwargs["is_rx"] = dir == "Rx" + + # CAN FD error frame + if rest_of_message.strip()[:10].lower() == "errorframe": + # Error Frame + # TODO: maybe use regex to parse BRS, ESI, etc? + msg_kwargs["is_error_frame"] = True + else: + can_id_str, frame_name_or_brs, rest_of_message = rest_of_message.split( + None, 2 + ) + + if frame_name_or_brs.isdigit(): + brs = frame_name_or_brs + esi, dlc_str, data_length_str, data = rest_of_message.split(None, 3) + else: + brs, esi, dlc_str, data_length_str, data = rest_of_message.split( + None, 4 + ) + + self._extract_can_id(can_id_str, msg_kwargs) + msg_kwargs["bitrate_switch"] = brs == "1" + msg_kwargs["error_state_indicator"] = esi == "1" + dlc = int(dlc_str, self._converted_base) + msg_kwargs["dlc"] = dlc + data_length = int(data_length_str) + + # CAN remote Frame + msg_kwargs["is_remote_frame"] = data_length == 0 + + self._process_data_string(data, data_length, msg_kwargs) + + return Message(**msg_kwargs) + def __iter__(self) -> Generator[Message, None, None]: - base = self._check_base(self.base) # This is guaranteed to not be None since we raise ValueError in __init__ self.file = cast(IO[Any], self.file) - for line in self.file: - # logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0]) - if line.split(" ")[0] == "base": - base = self._check_base(line.split(" ")[1]) + self._extract_header() + for line in self.file: temp = line.strip() if not temp or not temp[0].isdigit(): + # Could be a comment continue - is_fd = False - is_rx = True + msg_kwargs = {} try: - timestamp, channel, dummy = temp.split( - None, 2 - ) # , frameType, dlc, frameData + timestamp, channel, rest_of_message = temp.split(None, 2) + timestamp = float(timestamp) + msg_kwargs["timestamp"] = timestamp if channel == "CANFD": - timestamp, _, channel, direction, dummy = temp.split(None, 4) - is_fd = True - is_rx = direction == "Rx" + msg_kwargs["is_fd"] = True + elif channel.isdigit(): + # See ASCWriter + msg_kwargs["channel"] = int(channel) - 1 + else: + # Not a CAN message. Possible values include "statistic", J1939TP + continue except ValueError: - # we parsed an empty comment + # Some other unprocessed or unknown format 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:" - or dummy.split(None, 1)[0] == "J1939TP" - ): - pass - elif dummy[-1:].lower() == "r": - can_id_str, direction, _ = dummy.split(None, 2) - can_id_num, is_extended_id = self._extract_can_id(can_id_str, base) - msg = Message( - timestamp=timestamp, - arbitration_id=can_id_num & CAN_ID_MASK, - is_extended_id=is_extended_id, - is_remote_frame=True, - is_rx=direction == "Rx", - channel=channel, - ) - yield msg + + if "is_fd" not in msg_kwargs: + msg = self._process_classic_can_frame(rest_of_message, msg_kwargs) else: - brs = None - esi = None - data_length = 0 - try: - # this only works if dlc > 0 and thus data is available - if not is_fd: - can_id_str, direction, _, dlc, data = dummy.split(None, 4) - is_rx = direction == "Rx" - else: - can_id_str, frame_name, brs, esi, dlc, data_length, data = dummy.split( - None, 6 - ) - if frame_name.isdigit(): - # Empty frame_name - can_id_str, brs, esi, dlc, data_length, data = dummy.split( - None, 5 - ) - except ValueError: - # but if not, we only want to get the stuff up to the dlc - can_id_str, _, _, dlc = dummy.split(None, 3) - # and we set data to an empty sequence manually - data = "" - dlc = int(dlc, base) - if is_fd: - # For fd frames, dlc and data length might not be equal and - # data_length is the actual size of the data - dlc = int(data_length) - frame = bytearray() - data = data.split() - for byte in data[0:dlc]: - frame.append(int(byte, base)) - can_id_num, is_extended_id = self._extract_can_id(can_id_str, base) - - yield Message( - timestamp=timestamp, - arbitration_id=can_id_num & CAN_ID_MASK, - is_extended_id=is_extended_id, - is_remote_frame=False, - dlc=dlc, - data=frame, - is_fd=is_fd, - is_rx=is_rx, - channel=channel, - bitrate_switch=is_fd and brs == "1", - error_state_indicator=is_fd and esi == "1", - ) + msg = self._process_fd_can_frame(rest_of_message, msg_kwargs) + if msg is not None: + yield msg + self.stop() @@ -190,7 +233,7 @@ class ASCWriter(BaseIOHandler, Listener): "{id:>8} {symbolic_name:>32}", "{brs}", "{esi}", - "{dlc}", + "{dlc:x}", "{data_length:>2}", "{data}", "{message_duration:>8}", @@ -281,10 +324,10 @@ def on_message_received(self, msg: Message) -> None: self.log_event("{} ErrorFrame".format(self.channel), msg.timestamp) return if msg.is_remote_frame: - dtype = "r" + dtype = "r {:x}".format(msg.dlc) # New after v8.5 data: List[str] = [] else: - dtype = "d {}".format(msg.dlc) + dtype = "d {:x}".format(msg.dlc) data = ["{:02X}".format(byte) for byte in msg.data] arb_id = "{:X}".format(msg.arbitration_id) if msg.is_extended_id: diff --git a/test/data/example_data.py b/test/data/example_data.py index 773212c32..c682d35f5 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -112,8 +112,10 @@ def sort_messages(messages): [ Message(is_fd=True, data=range(64)), Message(is_fd=True, data=range(8)), - Message(is_fd=True, bitrate_switch=True), - Message(is_fd=True, error_state_indicator=True), + Message(is_fd=True, bitrate_switch=True, is_remote_frame=True), + Message(is_fd=True, error_state_indicator=True, is_remote_frame=True), + Message(is_fd=True, data=range(8), bitrate_switch=True), + Message(is_fd=True, data=range(8), error_state_indicator=True), ] ) diff --git a/test/data/logfile.asc b/test/data/logfile.asc index 8582cbf05..77ebdb78a 100644 --- a/test/data/logfile.asc +++ b/test/data/logfile.asc @@ -9,6 +9,12 @@ Begin Triggerblock Sam Sep 30 15:06:13.191 2017 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 2.501000 1 ErrorFrame + 2.501010 1 ErrorFrame ECC: 10100010 + 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300 + 2.510001 2 100 Tx r + 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x + 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x 3.148421 1 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 271910 BitCount = 140 ID = 418119424x 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x @@ -23,6 +29,10 @@ Begin Triggerblock Sam Sep 30 15:06:13.191 2017 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x 20.305233 2 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF + 30.005071 CANFD 2 Rx 300 Generic_Name_12 1 0 8 8 01 02 03 04 05 06 07 08 102203 133 303000 e0006659 46500250 4b140250 20011736 2001040d + 30.300981 CANFD 3 Tx 50005x 0 0 5 0 140000 73 200050 7a60 46500250 460a0250 20011736 20010205 + 30.506898 CANFD 4 Rx 4EE 0 0 f 64 01 02 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 1331984 11 0 46500250 460a0250 20011736 20010205 + 30.806898 CANFD 5 Tx ErrorFrame Not Acknowledge error, dominant error flag fffe c7 31ca Arb. 556 44 0 0 f 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1331984 11 0 46500250 460a0250 20011736 20010205 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% End TriggerBlock diff --git a/test/data/test_CanErrorFrames.asc b/test/data/test_CanErrorFrames.asc new file mode 100644 index 000000000..0d400c18c --- /dev/null +++ b/test/data/test_CanErrorFrames.asc @@ -0,0 +1,11 @@ +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 2.501000 1 ErrorFrame + 3.501000 1 ErrorFrame ECC: 10100010 + 4.501000 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300 + 30.806898 CANFD 5 Tx ErrorFrame Not Acknowledge error, dominant error flag fffe c7 31ca Arb. 556 44 0 0 f 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1331984 11 0 46500250 460a0250 20011736 20010205 +End TriggerBlock diff --git a/test/data/test_CanFdMessage.asc b/test/data/test_CanFdMessage.asc new file mode 100644 index 000000000..51fbcb1ce --- /dev/null +++ b/test/data/test_CanFdMessage.asc @@ -0,0 +1,10 @@ +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 30.005021 CANFD 1 Rx 300 1 0 8 8 11 c2 03 04 05 06 07 08 102203 133 303000 e0006659 46500250 4b140250 20011736 2001040d + 30.005041 CANFD 2 Tx 1C4D80A7x 0 1 8 8 12 c2 03 04 05 06 07 08 102203 133 303000 e0006659 46500250 4b140250 20011736 2001040d + 30.005071 CANFD 3 Rx 30a Generic_Name_12 1 1 8 8 01 02 03 04 05 06 07 08 102203 133 303000 e0006659 46500250 4b140250 20011736 2001040d +End TriggerBlock diff --git a/test/data/test_CanFdMessage64.asc b/test/data/test_CanFdMessage64.asc new file mode 100644 index 000000000..ab34ee7ae --- /dev/null +++ b/test/data/test_CanFdMessage64.asc @@ -0,0 +1,9 @@ +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 30.506898 CANFD 4 Rx 4EE 0 1 f 64 A1 02 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 1331984 11 0 46500250 460a0250 20011736 20010205 + 31.506898 CANFD 4 Rx 1C4D80A7x AlphaNumericName_2 1 0 f 64 b1 02 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 1331984 11 0 46500250 460a0250 20011736 20010205 +End TriggerBlock diff --git a/test/data/test_CanFdRemoteMessage.asc b/test/data/test_CanFdRemoteMessage.asc new file mode 100644 index 000000000..7c78f5d06 --- /dev/null +++ b/test/data/test_CanFdRemoteMessage.asc @@ -0,0 +1,8 @@ +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 30.300981 CANFD 3 Tx 50005x 0 1 5 0 140000 73 200050 7a60 46500250 460a0250 20011736 20010205 +End TriggerBlock diff --git a/test/data/test_CanMessage.asc b/test/data/test_CanMessage.asc new file mode 100644 index 000000000..52dda34d9 --- /dev/null +++ b/test/data/test_CanMessage.asc @@ -0,0 +1,9 @@ +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 2.5010 2 C8 Tx d 8 09 08 07 06 05 04 03 02 + 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 +End TriggerBlock diff --git a/test/data/test_CanRemoteMessage.asc b/test/data/test_CanRemoteMessage.asc new file mode 100644 index 000000000..4e6431576 --- /dev/null +++ b/test/data/test_CanRemoteMessage.asc @@ -0,0 +1,10 @@ +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 2.510001 2 100 Rx r + 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x + 2.584921 4 300 Rx r 8 Length = 1704000 BitCount = 145 ID = 88888888x +End TriggerBlock diff --git a/test/logformats_test.py b/test/logformats_test.py index 4a5c408b5..08a9e929d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -344,6 +344,157 @@ def _setup_instance(self): adds_default_channel=0, ) + def _read_log_file(self, filename): + logfile = os.path.join(os.path.dirname(__file__), "data", filename) + with can.ASCReader(logfile) as reader: + return list(reader) + + def test_can_message(self): + expected_messages = [ + can.Message( + timestamp=2.5010, + arbitration_id=0xC8, + is_extended_id=False, + is_rx=False, + channel=1, + dlc=8, + data=[9, 8, 7, 6, 5, 4, 3, 2], + ), + can.Message( + timestamp=17.876708, + arbitration_id=0x6F9, + is_extended_id=False, + channel=0, + dlc=0x8, + data=[5, 0xC, 0, 0, 0, 0, 0, 0], + ), + ] + actual = self._read_log_file("test_CanMessage.asc") + self.assertMessagesEqual(actual, expected_messages) + + def test_can_remote_message(self): + expected_messages = [ + can.Message( + timestamp=2.510001, + arbitration_id=0x100, + is_extended_id=False, + channel=1, + is_remote_frame=True, + ), + can.Message( + timestamp=2.520002, + arbitration_id=0x200, + is_extended_id=False, + is_rx=False, + channel=2, + is_remote_frame=True, + ), + can.Message( + timestamp=2.584921, + arbitration_id=0x300, + is_extended_id=False, + channel=3, + dlc=8, + is_remote_frame=True, + ), + ] + actual = self._read_log_file("test_CanRemoteMessage.asc") + self.assertMessagesEqual(actual, expected_messages) + + def test_can_fd_remote_message(self): + expected_messages = [ + can.Message( + timestamp=30.300981, + arbitration_id=0x50005, + channel=2, + dlc=5, + is_rx=False, + is_fd=True, + is_remote_frame=True, + error_state_indicator=True, + ) + ] + actual = self._read_log_file("test_CanFdRemoteMessage.asc") + self.assertMessagesEqual(actual, expected_messages) + + def test_can_fd_message(self): + expected_messages = [ + can.Message( + timestamp=30.005021, + arbitration_id=0x300, + is_extended_id=False, + channel=0, + dlc=8, + data=[0x11, 0xC2, 3, 4, 5, 6, 7, 8], + is_fd=True, + bitrate_switch=True, + ), + can.Message( + timestamp=30.005041, + arbitration_id=0x1C4D80A7, + channel=1, + dlc=8, + is_rx=False, + data=[0x12, 0xC2, 3, 4, 5, 6, 7, 8], + is_fd=True, + error_state_indicator=True, + ), + can.Message( + timestamp=30.005071, + arbitration_id=0x30A, + is_extended_id=False, + channel=2, + dlc=8, + data=[1, 2, 3, 4, 5, 6, 7, 8], + is_fd=True, + bitrate_switch=True, + error_state_indicator=True, + ), + ] + actual = self._read_log_file("test_CanFdMessage.asc") + self.assertMessagesEqual(actual, expected_messages) + + def test_can_fd_message_64(self): + expected_messages = [ + can.Message( + timestamp=30.506898, + arbitration_id=0x4EE, + is_extended_id=False, + channel=3, + dlc=0xF, + data=[0xA1, 2, 3, 4] + 59 * [0] + [0x64], + is_fd=True, + error_state_indicator=True, + ), + can.Message( + timestamp=31.506898, + arbitration_id=0x1C4D80A7, + channel=3, + dlc=0xF, + data=[0xB1, 2, 3, 4] + 59 * [0] + [0x64], + is_fd=True, + bitrate_switch=True, + ), + ] + actual = self._read_log_file("test_CanFdMessage64.asc") + self.assertMessagesEqual(actual, expected_messages) + + def test_can_and_canfd_error_frames(self): + expected_messages = [ + can.Message(timestamp=2.501000, channel=0, is_error_frame=True), + can.Message(timestamp=3.501000, channel=0, is_error_frame=True), + can.Message(timestamp=4.501000, channel=1, is_error_frame=True), + can.Message( + timestamp=30.806898, + channel=4, + is_rx=False, + is_error_frame=True, + is_fd=True, + ), + ] + actual = self._read_log_file("test_CanErrorFrames.asc") + self.assertMessagesEqual(actual, expected_messages) + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From 3d914c61320ec4dfb77d3ec25bd3ee443f95f9f8 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 13 May 2020 10:12:13 +1200 Subject: [PATCH 0401/1235] Configurs automatic merging to the repo via mergify. (#835) Closes #821 --- .mergify.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .mergify.yml diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 000000000..db4f18f33 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,10 @@ +pull_request_rules: + - name: Automatic merge on up to date branch with dual approval + conditions: + - "#approved-reviews-by>=2" + - "status-success=continuous-integration/travis-ci/pr" + - "label!=work-in-progress" + actions: + merge: + method: merge + strict: true From e0bb2456c01744ff198e00e1d2c4315f895975ff Mon Sep 17 00:00:00 2001 From: karl ding Date: Tue, 12 May 2020 15:38:23 -0700 Subject: [PATCH 0402/1235] Fix SyntaxWarning in SimpleCyclicSendTaskTest (#830) Equality should be compared via == not the "is" operator. This is a SyntaxWarning on Python 3.8+ --- test/simplecyclic_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 927855867..83a79de8c 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -176,7 +176,7 @@ def test_thread_based_cyclic_send_task(self): ) task.start() sleep(1) - self.assertTrue(on_error_mock.call_count is 1) + self.assertEqual(on_error_mock.call_count, 1) task.stop() # bus is still shutted down, but on_error returns True From 8f0a7e5cb5de7f3b5315134802aded8be09ebd3a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 13 May 2020 10:44:41 +1200 Subject: [PATCH 0403/1235] Formatting with black --- can/interfaces/socketcan/utils.py | 5 ++++- can/io/blf.py | 14 +++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 912580422..1316d153c 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -61,7 +61,10 @@ def find_available_interfaces() -> Iterable[str]: # output contains some lines like "1: vcan42: ..." # extract the "vcan42" of each line interfaces = [line.split(": ", 3)[1] for line in output.splitlines()] - log.debug("find_available_interfaces(): detected these interfaces (before filtering): %s", interfaces) + log.debug( + "find_available_interfaces(): detected these interfaces (before filtering): %s", + interfaces, + ) return filter(_PATTERN_CAN_INTERFACE.match, interfaces) diff --git a/can/io/blf.py b/can/io/blf.py index 064a95f7d..eb6c69210 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -286,9 +286,17 @@ def _parse_data(self, data): ) elif obj_type == CAN_FD_MESSAGE: members = unpack_can_fd_msg(data, pos) - channel, flags, dlc, can_id, _, _, fd_flags, valid_bytes, can_data = ( - members - ) + ( + channel, + flags, + dlc, + can_id, + _, + _, + fd_flags, + valid_bytes, + can_data, + ) = members yield Message( timestamp=timestamp, arbitration_id=can_id & 0x1FFFFFFF, From a838cf2ff050bf4b9445ff455f7183468d216c54 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 14 May 2020 22:59:28 +0200 Subject: [PATCH 0404/1235] edit docstring and return values --- can/interfaces/vector/canlib.py | 9 +++++---- can/interfaces/vector/xldriver.py | 12 ------------ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index a566a4929..d544703cc 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,6 +10,7 @@ import logging import time import os +from typing import Tuple try: # Try builtin Python 3 Windows API @@ -589,7 +590,7 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: @staticmethod def get_application_config( app_name: str, app_channel: int, bus_type: xldefine.XL_BusTypes, - ): + ) -> Tuple[xldefine.XL_HardwareType, int, int]: """Retrieve information for an application in Vector Hardware Configuration. :param app_name: @@ -617,7 +618,7 @@ def get_application_config( hw_channel, bus_type.value, ) - return xldefine.XL_HardwareType(hw_type.value), hw_index, hw_channel + return xldefine.XL_HardwareType(hw_type.value), hw_index.value, hw_channel.value @staticmethod def set_application_config( @@ -633,6 +634,8 @@ def set_application_config( :param app_name: The name of the application. Creates a new application if it does not exist yet. + :param app_channel: + The channel of the application. :param hw_type: The hardware type of the interface. E.g XL_HardwareType.XL_HWTYPE_VIRTUAL @@ -644,9 +647,7 @@ def set_application_config( :param bus_type: The bus type of the interfaces, which should be XL_BusTypes.XL_BUS_TYPE_CAN for most cases. - :return: """ - xldriver.xlSetApplConfig( app_name.encode(), app_channel, diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index c33f835bb..96b79c884 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -246,15 +246,3 @@ def check_status(result, function, arguments): ] xlSetApplConfig.restype = xlclass.XLstatus xlSetApplConfig.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 From 2ccca25b5fc7b565c440f49370820647aa5b64df Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 14 May 2020 23:18:34 +0200 Subject: [PATCH 0405/1235] implement xlSetTimerRate --- can/interfaces/vector/canlib.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ff3e2b8e2..aec4da006 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -658,6 +658,16 @@ def set_application_config( ) def set_timer_rate(self, timer_rate_ms: int): + """Set the cyclic event rate of the port. + + Once set, the port will generate a cyclic event with the tag XL_EventTags.XL_TIMER. + This timer can be used to keep an application alive. See XL Driver Library Description + for more information + + :param timer_rate_ms: + The timer rate in ms. The minimal timer rate is 1ms, a value of 0 deactivates + the timer events. + """ timer_rate_10us = timer_rate_ms * 100 xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) From e307da511a28f15b3fd38e2498dfa78dca195de4 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 14 May 2020 23:28:09 +0200 Subject: [PATCH 0406/1235] fix typo --- can/interfaces/vector/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index aec4da006..f40320d24 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -600,7 +600,7 @@ def get_application_config( :param bus_type: The bus type Enum e.g. `XL_BusTypes.XL_BUS_TYPE_CAN` :return: - Retruns a tuple of the hardware type, the hardware index and the + Returns a tuple of the hardware type, the hardware index and the hardware channel. :raises VectorError: Raises a VectorError when the application name does not exist in From db52d396a19407bd65b95868affae3b5efe20888 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 17 May 2020 21:58:03 +0200 Subject: [PATCH 0407/1235] add lazy tests and use old black formatter --- can/interfaces/vector/canlib.py | 2 +- can/interfaces/vector/xldriver.py | 24 ++++++++++++------------ test/test_vector.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index f40320d24..494960e44 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -589,7 +589,7 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: @staticmethod def get_application_config( - app_name: str, app_channel: int, bus_type: xldefine.XL_BusTypes, + app_name: str, app_channel: int, bus_type: xldefine.XL_BusTypes ) -> Tuple[xldefine.XL_HardwareType, int, int]: """Retrieve information for an application in Vector Hardware Configuration. diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index da35d3202..9c8095f7b 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -63,6 +63,18 @@ def check_status(result, function, arguments): xlGetApplConfig.restype = xlclass.XLstatus xlGetApplConfig.errcheck = check_status +xlSetApplConfig = _xlapi_dll.xlSetApplConfig +xlSetApplConfig.argtypes = [ + ctypes.c_char_p, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, +] +xlSetApplConfig.restype = xlclass.XLstatus +xlSetApplConfig.errcheck = check_status + xlGetChannelIndex = _xlapi_dll.xlGetChannelIndex xlGetChannelIndex.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] xlGetChannelIndex.restype = ctypes.c_int @@ -235,18 +247,6 @@ def check_status(result, function, arguments): xlPopupHwConfig.restype = xlclass.XLstatus xlPopupHwConfig.errcheck = check_status -xlSetApplConfig = _xlapi_dll.xlSetApplConfig -xlSetApplConfig.argtypes = [ - ctypes.c_char_p, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_uint, -] -xlSetApplConfig.restype = xlclass.XLstatus -xlSetApplConfig.errcheck = check_status - xlSetTimerRate = _xlapi_dll.xlSetTimerRate xlSetTimerRate.argtypes = [xlclass.XLportHandle, ctypes.c_ulong] xlSetTimerRate.restype = xlclass.XLstatus diff --git a/test/test_vector.py b/test/test_vector.py index a2d47c74e..f72c75550 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -248,6 +248,35 @@ def test_popup_hw_cfg(self) -> None: assert isinstance(args[0], ctypes.c_char_p) assert isinstance(args[1], ctypes.c_uint) + def test_get_application_config(self) -> None: + canlib.xldriver.xlGetApplConfig = Mock() + canlib.VectorBus.get_application_config( + app_name="CANalyzer", + app_channel=0, + bus_type=xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + ) + assert canlib.xldriver.xlGetApplConfig.called + + def test_set_application_config(self) -> None: + canlib.xldriver.xlSetApplConfig = Mock() + canlib.VectorBus.set_application_config( + app_name="CANalyzer", + app_channel=0, + hw_type=xldefine.XL_HardwareType.XL_HWTYPE_VN1610, + hw_index=0, + hw_channel=0, + bus_type=xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + ) + assert canlib.xldriver.xlSetApplConfig.called + + def test_set_timer_rate(self) -> None: + canlib.xldriver.xlSetTimerRate = Mock() + bus: canlib.VectorBus = can.Bus( + channel=0, bustype="vector", fd=True, _testing=True + ) + bus.set_timer_rate(timer_rate_ms=1) + assert canlib.xldriver.xlSetTimerRate.called + def test_called_without_testing_argument(self) -> None: """This tests if an exception is thrown when we are not running on Windows.""" if os.name != "nt": From 05f500e6696562b3b3a49dc8e8fc91144dcceb9e Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 17 May 2020 12:46:53 -0700 Subject: [PATCH 0408/1235] Fix typo in can.interface.Bus docstring s/resloved/resolved --- can/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interface.py b/can/interface.py index 8d96f651d..fd76b60bb 100644 --- a/can/interface.py +++ b/can/interface.py @@ -68,7 +68,7 @@ def __new__(cls, channel=None, *args, **kwargs): Some might have a special meaning, see below. :param channel: - Set to ``None`` to let it be resloved automatically from the default + Set to ``None`` to let it be resolved automatically from the default configuration. That might fail, see below. Expected type is backend dependent. From 80f69523466343405c5e5b570a5db0932a4c68e9 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 23 May 2020 22:09:25 +0200 Subject: [PATCH 0409/1235] use tox instead of 'setup.py test' (#833) * use tox instead of setup.py test * fix error in .appveyor.yml * pass TEST_SOCKETCAN environment variable for travis --- .appveyor.yml | 9 +++---- .travis.yml | 13 +++------- doc/development.rst | 34 +++++++++++++++++++------- pyproject.toml | 6 +++++ setup.cfg | 23 ------------------ setup.py | 26 +------------------- tox.ini | 59 +++++++++++++++++++++++++++++++++++++++++---- 7 files changed, 93 insertions(+), 77 deletions(-) create mode 100644 pyproject.toml diff --git a/.appveyor.yml b/.appveyor.yml index 500c71320..e7e9c80f9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -14,14 +14,11 @@ install: # Prepend Python installation and scripts (e.g. pytest) to PATH - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% - # We need to install the python-can library itself including the dependencies - - "python -m pip install .[test,neovi]" + # Install tox + - "pip install tox" build: off test_script: # run tests - - "pytest" - - # uplad coverage reports - - "codecov -X gcov" + - "tox -e appveyor" diff --git a/.travis.yml b/.travis.yml index 76195ce51..3b674f371 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,19 +21,14 @@ env: install: - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - - travis_retry pip install .[test] + - python setup.py install script: - | + # install tox + pip install tox # Run the tests - python setup.py test - # preserve the error code - RETURN_CODE=$? - # Upload the coverage to codecov.io - codecov -X gcov - # set error code - (exit $RETURN_CODE); - + tox -e travis jobs: allow_failures: diff --git a/doc/development.rst b/doc/development.rst index 8bad5c58e..0e3cc5c2c 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -22,14 +22,30 @@ Building & Installing The following assumes that the commands are executed from the root of the repository: -- The project can be built and installed with ``python setup.py build`` and - ``python setup.py install``. -- The unit tests can be run with ``python setup.py test``. The tests can be run with ``python2``, - ``python3``, ``pypy`` or ``pypy3`` to test with other python versions, if they are installed. - Maybe, you need to execute ``pip3 install python-can[test]`` (or only ``pip`` for Python 2), - if some dependencies are missing. -- The docs can be built with ``sphinx-build doc/ doc/_build``. Appending ``-n`` to the command - makes Sphinx complain about more subtle problems. +The project can be built with:: + + pip install wheel + python setup.py sdist bdist_wheel + +The project can be installed in editable mode with:: + + pip install -e . + +The unit tests can be run with:: + + pip install tox + tox + +The documentation can be built with:: + + pip install -r doc/doc-requirements.txt + python -m sphinx -an doc build + +The linters can be run with:: + + pip install -r requirements-lint.txt + pylint --rcfile=.pylintrc-wip can/**.py + black --check --verbose can Creating a new interface/backend @@ -81,7 +97,7 @@ Creating a new Release - Update `CONTRIBUTORS.txt` with any new contributors. - For larger changes update ``doc/history.rst``. - Sanity check that documentation has stayed inline with code. -- Create a temporary virtual environment. Run ``python setup.py install`` and ``python setup.py test``. +- Create a temporary virtual environment. Run ``python setup.py install`` and ``tox``. - Create and upload the distribution: ``python setup.py sdist bdist_wheel``. - Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl``. - Upload with twine ``twine upload dist/python-can-X.Y.Z*``. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..17d87f033 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools >= 40.8", + "wheel", +] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg index a0e8d5b6a..498ec14ac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,25 +1,2 @@ -[aliases] -test=pytest - [metadata] license_file = LICENSE.txt - -[tool:pytest] -addopts = -v --timeout=300 --cov=can --cov-config=setup.cfg - -[coverage:run] -# we could also use branch coverage -branch = False -# already specified by call to pytest using --cov=can -#source = can - -[coverage:report] -# two digits after decimal point -precision = 3 -show_missing = True -exclude_lines = - # Have to re-enable the standard pragma, see https://coverage.readthedocs.io/en/coverage-4.5.1a/config.html#syntax - pragma: no cover - - # Don't complain if non-runnable code isn't run: - if __name__ == .__main__.: diff --git a/setup.py b/setup.py index 8327b1432..6499aad8b 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,6 @@ from os.path import isfile, join import re import logging -import sys from setuptools import setup, find_packages logging.basicConfig(level=logging.WARNING) @@ -29,30 +28,9 @@ extras_require = { "seeedstudio": ["pyserial>=3.0"], "serial": ["pyserial~=3.0"], - "neovi": ["python-ics>=2.12", "filelock"], + "neovi": ["python-ics>=2.12"], } -tests_require = [ - "pytest~=5.3", - "pytest-timeout~=1.3", - "pytest-cov~=2.8", - # coveragepy==5.0 fails with `Safety level may not be changed inside a transaction` - # on python 3.6 on MACOS - "coverage<5", - "codecov~=2.0", - "hypothesis~=4.56", -] + extras_require["serial"] - -extras_require["test"] = tests_require - -# Check for 'pytest-runner' only if setup.py was invoked with 'test'. -# This optimizes setup.py for cases when pytest-runner is not needed, -# using the approach that is suggested upstream. -# -# See https://pypi.org/project/pytest-runner/#conditional-requirement -needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) -pytest_runner = ["pytest-runner"] if needs_pytest else [] - setup( # Description name="python-can", @@ -110,7 +88,5 @@ "mypy_extensions >= 0.4.0, < 0.5.0", 'pywin32;platform_system=="Windows"', ], - setup_requires=pytest_runner, extras_require=extras_require, - tests_require=tests_require, ) diff --git a/tox.ini b/tox.ini index 7447ccaf2..e5df520fe 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,60 @@ [tox] -envlist = py36, py37 [testenv] +deps = + pytest~=5.3 + pytest-timeout~=1.3 + pytest-cov~=2.8 + coverage<5 + codecov~=2.0 + hypothesis~=4.56 + pyserial~=3.0 + commands = - pip install .[test] pytest -passenv = CI + recreate = True -usedevelop = True -sitepackages=False + +[testenv:travis] +passenv = + CI + TRAVIS + TRAVIS_* + TEST_SOCKETCAN + +commands_post = + codecov -X gcov + +[testenv:appveyor] +passenv = + CI + APPVEYOR + APPVEYOR_* + +extras = neovi + +commands_post = + codecov -X gcov + +[pytest] +testpaths = test +addopts = -v --timeout=300 --cov=can --cov-append --cov-report=term + + +[coverage:run] +# we could also use branch coverage +branch = False + +[coverage:report] +# two digits after decimal point +precision = 3 +show_missing = True +exclude_lines = + # Have to re-enable the standard pragma, see https://coverage.readthedocs.io/en/coverage-4.5.1a/config.html#syntax + pragma: no cover + + # Don't complain if non-runnable code isn't run: + if __name__ == .__main__.: + + # Don't complain if tests don't hit defensive assertion code: + raise NotImplementedError From 405f63ec266f08fa4b768f6c40daaa3d321902e4 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 23 May 2020 23:10:41 +0200 Subject: [PATCH 0410/1235] upload codecov artifacts (#825) --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 16168f521..b9b9b52b6 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,7 +1,7 @@ # Validate with curl --data-binary @.codecov.yml https://codecov.io/validate codecov: archive: - uploads: no + uploads: yes coverage: precision: 2 From e045043828ecc19ddef63dbaa24585b440ae2763 Mon Sep 17 00:00:00 2001 From: Karl Date: Fri, 8 May 2020 22:13:02 -0700 Subject: [PATCH 0411/1235] Add typing annotations for slcan interface This works towards PEP 561 compatibility. --- .travis.yml | 1 + can/interfaces/slcan.py | 77 ++++++++++++++++++++++------------------- can/typechecking.py | 4 ++- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3b674f371..78650ebd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -99,6 +99,7 @@ jobs: can/broadcastmanager.py can/bus.py can/interface.py + can/interfaces/slcan.py can/interfaces/socketcan/**.py can/interfaces/virtual.py can/listener.py diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 5d7450987..a952fb7c4 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -7,6 +7,9 @@ """ +from typing import Any, Optional, Tuple +from can import typechecking + import time import logging @@ -52,38 +55,39 @@ class slcanBus(BusABC): def __init__( self, - channel, - ttyBaudrate=115200, - bitrate=None, - btr=None, - sleep_after_open=_SLEEP_AFTER_SERIAL_OPEN, - rtscts=False, - **kwargs - ): + channel: typechecking.ChannelStr, + ttyBaudrate: int = 115200, + bitrate: Optional[int] = None, + btr: Optional[str] = None, + sleep_after_open: float = _SLEEP_AFTER_SERIAL_OPEN, + rtscts: bool = False, + **kwargs: Any + ) -> None: """ :raise ValueError: if both *bitrate* and *btr* are set - :param str channel: + :param channel: port of underlying serial or usb device (e.g. /dev/ttyUSB0, COM8, ...) Must not be empty. - :param int ttyBaudrate: + :param ttyBaudrate: baudrate of underlying serial or usb device - :param int bitrate: + :param bitrate: Bitrate in bit/s - :param str btr: + :param btr: BTR register value to set custom can speed - :param float poll_interval: + :param poll_interval: Poll interval in seconds when reading messages - :param float sleep_after_open: + :param sleep_after_open: Time to wait in seconds after opening serial connection - :param bool rtscts: + :param rtscts: turn hardware handshake (RTS/CTS) on and off """ if not channel: # if None or empty raise TypeError("Must specify a serial port.") if "@" in channel: - (channel, ttyBaudrate) = channel.split("@") + (channel, baudrate) = channel.split("@") + ttyBaudrate = int(baudrate) self.serialPortOrig = serial.serial_for_url( channel, baudrate=ttyBaudrate, rtscts=rtscts ) @@ -104,11 +108,11 @@ def __init__( channel, ttyBaudrate=115200, bitrate=None, rtscts=False, **kwargs ) - def set_bitrate(self, bitrate): + def set_bitrate(self, bitrate: int) -> None: """ :raise ValueError: if both *bitrate* is not among the possible values - :param int bitrate: + :param bitrate: Bitrate in bit/s """ self.close() @@ -116,24 +120,26 @@ def set_bitrate(self, bitrate): self._write(self._BITRATES[bitrate]) else: raise ValueError( - "Invalid bitrate, choose one of " + (", ".join(self._BITRATES)) + "." + "Invalid bitrate, choose one of " + + (", ".join(str(k) for k in self._BITRATES.keys())) + + "." ) self.open() - def set_bitrate_reg(self, btr): + def set_bitrate_reg(self, btr: str) -> None: """ - :param str btr: + :param btr: BTR register value to set custom can speed """ self.close() self._write("s" + btr) self.open() - def _write(self, string): + def _write(self, string: str) -> None: self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() - def _read(self, timeout): + def _read(self, timeout: Optional[float]) -> Optional[str]: # first read what is already in receive buffer while self.serialPortOrig.in_waiting: @@ -165,18 +171,20 @@ def _read(self, timeout): break return string - def flush(self): + def flush(self) -> None: del self._buffer[:] while self.serialPortOrig.in_waiting: self.serialPortOrig.read() - def open(self): + def open(self) -> None: self._write("O") - def close(self): + def close(self) -> None: self._write("C") - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: canId = None remote = False @@ -223,7 +231,7 @@ def _recv_internal(self, timeout): return msg, False return None, False - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: if timeout != self.serialPortOrig.write_timeout: self.serialPortOrig.write_timeout = timeout if msg.is_remote_frame: @@ -239,20 +247,21 @@ def send(self, msg, timeout=None): sendStr += "".join(["%02X" % b for b in msg.data]) self._write(sendStr) - def shutdown(self): + def shutdown(self) -> None: self.close() self.serialPortOrig.close() - def fileno(self): + def fileno(self) -> int: if hasattr(self.serialPortOrig, "fileno"): return self.serialPortOrig.fileno() # Return an invalid file descriptor on Windows return -1 - def get_version(self, timeout): + def get_version( + self, timeout: Optional[float] + ) -> Tuple[Optional[int], Optional[int]]: """Get HW and SW version of the slcan interface. - :type timeout: int or None :param timeout: seconds to wait for version or None to wait indefinitely @@ -288,14 +297,12 @@ def get_version(self, timeout): else: return None, None - def get_serial_number(self, timeout): + def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: """Get serial number of the slcan interface. - :type timeout: int or None :param timeout: seconds to wait for serial number or None to wait indefinitely - :rtype str or None :return: None on timeout or a str object. """ diff --git a/can/typechecking.py b/can/typechecking.py index 0327874f5..50070b01e 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -22,7 +22,9 @@ CanData = typing.Union[bytes, bytearray, int, typing.Iterable[int]] # Used for the Abstract Base Class -Channel = typing.Union[int, str] +ChannelStr = str +ChannelInt = int +Channel = typing.Union[ChannelInt, ChannelStr] # Used by the IO module FileLike = typing.IO[typing.Any] From 74192b33ac2b87040f654726534bb46a85d121e5 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 28 May 2020 09:17:04 +0200 Subject: [PATCH 0412/1235] specify tox environment in docs (#844) --- doc/development.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development.rst b/doc/development.rst index 0e3cc5c2c..6b9aa529e 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -34,7 +34,7 @@ The project can be installed in editable mode with:: The unit tests can be run with:: pip install tox - tox + tox -e py The documentation can be built with:: From 136d6b7caeb779cc4bfce076a10150fdfbc52d07 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Mon, 1 Jun 2020 15:53:12 +0200 Subject: [PATCH 0413/1235] PCAN: Fix timestamp timezone offset (#778) * PCAN: Fix timestamp timezone offset * PCAN: Add comment on timezone offset --- can/interfaces/pcan/pcan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 14d8dc774..01287fd90 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -16,8 +16,9 @@ import uptime import datetime + # boottime() and fromtimestamp() are timezone offset, so the difference is not. boottimeEpoch = ( - uptime.boottime() - datetime.datetime.utcfromtimestamp(0) + uptime.boottime() - datetime.datetime.fromtimestamp(0) ).total_seconds() except ImportError: boottimeEpoch = 0 From 5dbbba4b2668fed7c8705dcd9008f98967435b3d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 1 Jun 2020 22:41:50 +0200 Subject: [PATCH 0414/1235] make VectorError picklable --- can/interfaces/vector/exceptions.py | 6 ++++++ test/test_vector.py | 20 +++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 042c9d73a..95f8b579d 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -8,3 +8,9 @@ class VectorError(CanError): def __init__(self, error_code, error_string, function): self.error_code = error_code super().__init__(f"{function} failed ({error_string})") + + # keep reference to args for pickling + self._args = error_code, error_string, function + + def __reduce__(self): + return VectorError, self._args, {} diff --git a/test/test_vector.py b/test/test_vector.py index a2d47c74e..73b8a2a4a 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -6,6 +6,7 @@ """ import ctypes +import pickle import time import logging import os @@ -15,7 +16,7 @@ import pytest import can -from can.interfaces.vector import canlib, xldefine, xlclass +from can.interfaces.vector import canlib, xldefine, xlclass, VectorError class TestVectorBus(unittest.TestCase): @@ -255,6 +256,23 @@ def test_called_without_testing_argument(self) -> None: # do not set the _testing argument, since it supresses the exception can.Bus(channel=0, bustype="vector") + def test_vector_error_pickle(self) -> None: + error_code = 118 + error_string = "XL_ERROR" + function = "function_name" + + exc = VectorError(error_code, error_string, function) + + # pickle and unpickle + p = pickle.dumps(exc) + exc_unpickled: VectorError = pickle.loads(p) + + self.assertEqual(str(exc), str(exc_unpickled)) + self.assertEqual(error_code, exc_unpickled.error_code) + + with pytest.raises(VectorError): + raise exc_unpickled + def xlGetApplConfig( app_name_p: ctypes.c_char_p, From 555810023699357723acc5ac1e612d1163f9e27d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 2 Jun 2020 18:43:17 +0200 Subject: [PATCH 0415/1235] add missing error codes to xldefine.py --- can/interfaces/vector/canlib.py | 8 ++--- can/interfaces/vector/xldefine.py | 52 ++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 494960e44..d32dad320 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,7 +10,7 @@ import logging import time import os -from typing import Tuple +from typing import Tuple, List try: # Try builtin Python 3 Windows API @@ -657,7 +657,7 @@ def set_application_config( bus_type.value, ) - def set_timer_rate(self, timer_rate_ms: int): + def set_timer_rate(self, timer_rate_ms: int) -> None: """Set the cyclic event rate of the port. Once set, the port will generate a cyclic event with the tag XL_EventTags.XL_TIMER. @@ -672,7 +672,7 @@ def set_timer_rate(self, timer_rate_ms: int): xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) -def get_channel_configs(): +def get_channel_configs() -> List[xlclass.XLchannelConfig]: if xldriver is None: return [] driver_config = xlclass.XLdriverConfig() @@ -680,6 +680,6 @@ def get_channel_configs(): xldriver.xlOpenDriver() xldriver.xlGetDriverConfig(driver_config) xldriver.xlCloseDriver() - except Exception: + except VectorError: pass return [driver_config.channel[i] for i in range(driver_config.channelCount)] diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index d4134001b..28a35e7a2 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -84,7 +84,8 @@ class XL_CANFD_RX_MessageFlags(Enum): class XL_CANFD_TX_EventTags(Enum): - XL_CAN_EV_TAG_TX_MSG = 1088 + XL_CAN_EV_TAG_TX_MSG = 1088 # =0x0440 + XL_CAN_EV_TAG_TX_ERRFR = 1089 # =0x0441 class XL_CANFD_TX_MessageFlags(Enum): @@ -158,10 +159,51 @@ class XL_Sizes(Enum): class XL_Status(Enum): - XL_SUCCESS = 0 - XL_PENDING = 1 - XL_ERR_QUEUE_IS_EMPTY = 10 - XL_ERR_HW_NOT_PRESENT = 129 + XL_SUCCESS = 0 # =0x0000 + XL_PENDING = 1 # =0x0001 + XL_ERR_QUEUE_IS_EMPTY = 10 # =0x000A + XL_ERR_QUEUE_IS_FULL = 11 # =0x000B + XL_ERR_TX_NOT_POSSIBLE = 12 # =0x000C + XL_ERR_NO_LICENSE = 14 # =0x000E + XL_ERR_WRONG_PARAMETER = 101 # =0x0065 + XL_ERR_TWICE_REGISTER = 110 # =0x006E + XL_ERR_INVALID_CHAN_INDEX = 111 # =0x006F + XL_ERR_INVALID_ACCESS = 112 # =0x0070 + XL_ERR_PORT_IS_OFFLINE = 113 # =0x0071 + XL_ERR_CHAN_IS_ONLINE = 116 # =0x0074 + XL_ERR_NOT_IMPLEMENTED = 117 # =0x0075 + XL_ERR_INVALID_PORT = 118 # =0x0076 + XL_ERR_HW_NOT_READY = 120 # =0x0078 + XL_ERR_CMD_TIMEOUT = 121 # =0x0079 + XL_ERR_HW_NOT_PRESENT = 129 # =0x0081 + XL_ERR_NOTIFY_ALREADY_ACTIVE = 131 # =0x0083 + XL_ERR_NO_RESOURCES = 152 # =0x0098 + XL_ERR_WRONG_CHIP_TYPE = 153 # =0x0099 + XL_ERR_WRONG_COMMAND = 154 # =0x009A + XL_ERR_INVALID_HANDLE = 155 # =0x009B + XL_ERR_RESERVED_NOT_ZERO = 157 # =0x009D + XL_ERR_INIT_ACCESS_MISSING = 158 # =0x009E + XL_ERR_CANNOT_OPEN_DRIVER = 201 # =0x00C9 + XL_ERR_WRONG_BUS_TYPE = 202 # =0x00CA + XL_ERR_DLL_NOT_FOUND = 203 # =0x00CB + XL_ERR_INVALID_CHANNEL_MASK = 204 # =0x00CC + XL_ERR_NOT_SUPPORTED = 205 # =0x00CD + XL_ERR_CONNECTION_BROKEN = 210 # =0x00D2 + XL_ERR_CONNECTION_CLOSED = 211 # =0x00D3 + XL_ERR_INVALID_STREAM_NAME = 212 # =0x00D4 + XL_ERR_CONNECTION_FAILED = 213 # =0x00D5 + XL_ERR_STREAM_NOT_FOUND = 214 # =0x00D6 + XL_ERR_STREAM_NOT_CONNECTED = 215 # =0x00D7 + XL_ERR_QUEUE_OVERRUN = 216 # =0x00D8 + XL_ERROR = 255 # =0x00FF + + # CAN FD Error Codes + XL_ERR_INVALID_DLC = 513 # =0x0201 + XL_ERR_INVALID_CANID = 514 # =0x0202 + XL_ERR_INVALID_FDFLAG_MODE20 = 515 # =0x203 + XL_ERR_EDL_RTR = 516 # =0x204 + XL_ERR_EDL_NOT_SET = 517 # =0x205 + XL_ERR_UNKNOWN_FLAG = 518 # =0x206 class XL_TimeSyncNewValue(Enum): From 8345eece4429a982024460eeddfe3bc5297a48c1 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 2 Jun 2020 19:19:14 +0200 Subject: [PATCH 0416/1235] use get_application_config() method in __init__() --- can/interfaces/vector/canlib.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index d32dad320..77c62ed49 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -150,21 +150,11 @@ def __init__( for channel in self.channels: if app_name: # Get global channel index from application channel - hw_type = ctypes.c_uint(0) - hw_index = ctypes.c_uint(0) - hw_channel = ctypes.c_uint(0) - xldriver.xlGetApplConfig( - self._app_name, - channel, - hw_type, - hw_index, - hw_channel, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, + hw_type, hw_index, hw_channel = self.get_application_config( + app_name, channel, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN ) LOG.debug("Channel index %d found", channel) - idx = xldriver.xlGetChannelIndex( - hw_type.value, hw_index.value, hw_channel.value - ) + idx = xldriver.xlGetChannelIndex(hw_type.value, hw_index, hw_channel) if idx < 0: # Undocumented behavior! See issue #353. # If hardware is unavailable, this function returns -1. @@ -172,7 +162,7 @@ def __init__( # would have signalled XL_ERR_HW_NOT_PRESENT. raise VectorError( xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.value, - "XL_ERR_HW_NOT_PRESENT", + xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name, "xlGetChannelIndex", ) else: @@ -350,7 +340,6 @@ def _recv_internal(self, timeout): event = xlclass.XLcanRxEvent() else: event = xlclass.XLevent() - event_count = ctypes.c_uint() while True: if self.fd: @@ -407,7 +396,7 @@ def _recv_internal(self, timeout): self.handle_canfd_event(event) else: - event_count.value = 1 + event_count = ctypes.c_uint(1) try: xldriver.xlReceive(self.port_handle, event_count, event) except VectorError as exc: From dace52050295a8d045f9554432525bb1b5992691 Mon Sep 17 00:00:00 2001 From: Artur Drogunow Date: Wed, 3 Jun 2020 13:35:03 +0200 Subject: [PATCH 0417/1235] add missing api functions xlGetEventString and xlCanGetEventString --- can/interfaces/vector/xlclass.py | 2 ++ can/interfaces/vector/xldriver.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py index 8c55bf058..c0ef9fa48 100644 --- a/can/interfaces/vector/xlclass.py +++ b/can/interfaces/vector/xlclass.py @@ -18,6 +18,8 @@ XLstatus = ctypes.c_short XLportHandle = ctypes.c_long XLeventTag = ctypes.c_ubyte +XLstringType = ctypes.c_char_p + # structure for XL_RECEIVE_MSG, XL_TRANSMIT_MSG class s_xl_can_msg(ctypes.Structure): diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 9c8095f7b..5027cb9e6 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -27,7 +27,7 @@ # ctypes wrapping for API functions xlGetErrorString = _xlapi_dll.xlGetErrorString xlGetErrorString.argtypes = [xlclass.XLstatus] -xlGetErrorString.restype = ctypes.c_char_p +xlGetErrorString.restype = xlclass.XLstringType def check_status(result, function, arguments): @@ -251,3 +251,11 @@ def check_status(result, function, arguments): xlSetTimerRate.argtypes = [xlclass.XLportHandle, ctypes.c_ulong] xlSetTimerRate.restype = xlclass.XLstatus xlSetTimerRate.errcheck = check_status + +xlGetEventString = _xlapi_dll.xlGetEventString +xlGetEventString.argtypes = [ctypes.POINTER(xlclass.XLevent)] +xlGetEventString.restype = xlclass.XLstringType + +xlCanGetEventString = _xlapi_dll.xlCanGetEventString +xlCanGetEventString.argtypes = [ctypes.POINTER(xlclass.XLcanRxEvent)] +xlCanGetEventString.restype = xlclass.XLstringType From 14ca4f2f7fa531b1deef8df77fe660a5f6bcc3d3 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 3 Jun 2020 22:10:12 +0200 Subject: [PATCH 0418/1235] refactor VectorBus.send() method --- can/interfaces/vector/canlib.py | 125 +++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 43 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ac85ef4ce..5ac435776 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -11,6 +11,8 @@ import time import os +import typing + try: # Try builtin Python 3 Windows API from _winapi import WaitForSingleObject, INFINITE @@ -482,57 +484,94 @@ def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: """ pass - def send(self, msg, timeout=None): - msg_id = msg.arbitration_id + def send(self, msg: Message, timeout: typing.Optional[float] = None): + self._send_sequence([msg]) - if msg.is_extended_id: - msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + def _send_sequence(self, msgs: typing.Sequence[Message]) -> int: + """Send messages and return number of successful transmissions.""" + if self.fd: + return self._send_can_fd_msg_sequence(msgs) + else: + return self._send_can_msg_sequence(msgs) - flags = 0 + def _get_tx_channel_mask(self, msgs: typing.Sequence[Message]) -> int: + if len(msgs) == 1: + return self.channel_masks.get(msgs[0].channel, self.mask) + else: + return self.mask - # If channel has been specified, try to send only to that one. - # Otherwise send to all channels - mask = self.channel_masks.get(msg.channel, self.mask) + def _send_can_msg_sequence(self, msgs: typing.Sequence[Message]) -> int: + """Send CAN messages and return number of successful transmissions.""" + mask = self._get_tx_channel_mask(msgs) + message_count = ctypes.c_uint(len(msgs)) - if self.fd: - if msg.is_fd: - flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_EDL.value - if msg.bitrate_switch: - flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_BRS.value - if msg.is_remote_frame: - flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_RTR.value - - message_count = 1 - MsgCntSent = ctypes.c_uint(1) - - 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 - XLcanTxEvent.tagData.canMsg.msgFlags = flags - XLcanTxEvent.tagData.canMsg.dlc = len2dlc(msg.dlc) - for idx, value in enumerate(msg.data): - XLcanTxEvent.tagData.canMsg.data[idx] = value - xldriver.xlCanTransmitEx( - self.port_handle, mask, message_count, MsgCntSent, XLcanTxEvent - ) + xl_event_array = (xlclass.XLevent * message_count.value)() + for idx, msg in enumerate(msgs): + xl_event_array[idx] = self._build_xl_event(msg) - else: - if msg.is_remote_frame: - flags |= xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value + xldriver.xlCanTransmit(self.port_handle, mask, message_count, xl_event_array) + return message_count.value - message_count = ctypes.c_uint(1) + @staticmethod + def _build_xl_event(msg: Message) -> xlclass.XLevent: + msg_id = msg.arbitration_id + if msg.is_extended_id: + msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value - xl_event = xlclass.XLevent() - xl_event.tag = xldefine.XL_EventTags.XL_TRANSMIT_MSG.value + flags = 0 + if msg.is_remote_frame: + flags |= xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value + + 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 + + return xl_event + + def _send_can_fd_msg_sequence(self, msgs: typing.Sequence[Message]) -> int: + """Send CAN FD messages and return number of successful transmissions.""" + mask = self._get_tx_channel_mask(msgs) + message_count = len(msgs) + + xl_can_tx_event_array = (xlclass.XLcanTxEvent * message_count)() + for idx, msg in enumerate(msgs): + xl_can_tx_event_array[idx] = self._build_xl_can_tx_event(msg) + + msg_count_sent = ctypes.c_uint(0) + xldriver.xlCanTransmitEx( + self.port_handle, mask, message_count, msg_count_sent, xl_can_tx_event_array + ) + return msg_count_sent.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 - xldriver.xlCanTransmit(self.port_handle, mask, message_count, xl_event) + @staticmethod + def _build_xl_can_tx_event(msg: Message) -> xlclass.XLcanTxEvent: + msg_id = msg.arbitration_id + if msg.is_extended_id: + msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + + flags = 0 + if msg.is_fd: + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_EDL.value + if msg.bitrate_switch: + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_BRS.value + if msg.is_remote_frame: + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_RTR.value + + xl_can_tx_event = xlclass.XLcanTxEvent() + xl_can_tx_event.tag = xldefine.XL_CANFD_TX_EventTags.XL_CAN_EV_TAG_TX_MSG.value + xl_can_tx_event.transId = 0xFFFF + + xl_can_tx_event.tagData.canMsg.canId = msg_id + xl_can_tx_event.tagData.canMsg.msgFlags = flags + xl_can_tx_event.tagData.canMsg.dlc = len2dlc(msg.dlc) + for idx, value in enumerate(msg.data): + xl_can_tx_event.tagData.canMsg.data[idx] = value + + return xl_can_tx_event def flush_tx_buffer(self): xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask) From 2fa05008f6bf0ab1154a46ff199a66197ccaad67 Mon Sep 17 00:00:00 2001 From: Fabian Henze <32638720+henzef@users.noreply.github.com> Date: Fri, 5 Jun 2020 09:42:14 +0200 Subject: [PATCH 0419/1235] Support PCAN interface on cygwin (#840) * Support PCAN interface on cygwin * Mention cygwin support in pcan docs Co-authored-by: Felix Divo --- can/interfaces/pcan/basic.py | 4 ++++ doc/interfaces/pcan.rst | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 94a9ec950..92f05a9d3 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -545,6 +545,10 @@ def __init__(self): logger.error("Exception: The PEAK-driver couldn't be found!") finally: winreg.CloseKey(aReg) + elif "CYGWIN" in platform.system(): + self.__m_dllBasic = cdll.LoadLibrary("PCANBasic.dll") + # Unfortunately cygwin python has no winreg module, so we can't + # check for the registry key. elif platform.system() == "Darwin": self.__m_dllBasic = cdll.LoadLibrary("libPCBUSB.dylib") else: diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index 9bbaec9cb..39fc356c9 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -5,7 +5,7 @@ PCAN Basic API Interface to `Peak-System `__'s PCAN-Basic API. -Windows driver: https://www.peak-system.com/Downloads.76.0.html?&L=1 +Windows driver: https://www.peak-system.com/Downloads.76.0.html?&L=1 (also supported on cygwin) Linux driver: https://www.peak-system.com/fileadmin/media/linux/index.htm#download and https://www.peak-system.com/Downloads.76.0.html?&L=1 (PCAN-Basic API (Linux)) From 370d1c175c44428853cac801dfa3f0acc5c2cea8 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 5 Jun 2020 23:33:24 +0200 Subject: [PATCH 0420/1235] remove for-loops --- can/interfaces/vector/canlib.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 5ac435776..8999dede7 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -464,8 +464,7 @@ def handle_can_event(self, event: xlclass.XLevent) -> None: """Handle non-message CAN events. Method is called by :meth:`~can.interfaces.vector.VectorBus._recv_internal` - when `event.tag` is not `XL_CAN_EV_TAG_RX_OK` or `XL_CAN_EV_TAG_TX_OK`. - Subclasses can implement this method. + when `event.tag` is not `XL_RECEIVE_MSG`. Subclasses can implement this method. :param event: XLevent that could have a `XL_CHIP_STATE`, `XL_TIMER` or `XL_SYNC_PULSE` tag. :return: None @@ -476,10 +475,11 @@ def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: """Handle non-message CAN FD events. Method is called by :meth:`~can.interfaces.vector.VectorBus._recv_internal` - when `event.tag` is not `XL_RECEIVE_MSG`. Subclasses can implement this method. + when `event.tag` is not `XL_CAN_EV_TAG_RX_OK` or `XL_CAN_EV_TAG_TX_OK`. + Subclasses can implement this method. - :param event: `XLcanRxEvent` that could have a `XL_CAN_EV_TAG_RX_ERROR`, `XL_CAN_EV_TAG_TX_ERROR` - or `XL_CAN_EV_TAG_CHIP_STATE` tag. + :param event: `XLcanRxEvent` that could have a `XL_CAN_EV_TAG_RX_ERROR`, `XL_CAN_EV_TAG_TX_ERROR`, + `XL_TIMER` or `XL_CAN_EV_TAG_CHIP_STATE` tag. :return: None """ pass @@ -505,9 +505,9 @@ def _send_can_msg_sequence(self, msgs: typing.Sequence[Message]) -> int: mask = self._get_tx_channel_mask(msgs) message_count = ctypes.c_uint(len(msgs)) - xl_event_array = (xlclass.XLevent * message_count.value)() - for idx, msg in enumerate(msgs): - xl_event_array[idx] = self._build_xl_event(msg) + xl_event_array = (xlclass.XLevent * message_count.value)( + *map(self._build_xl_event, msgs) + ) xldriver.xlCanTransmit(self.port_handle, mask, message_count, xl_event_array) return message_count.value @@ -527,8 +527,7 @@ def _build_xl_event(msg: Message) -> xlclass.XLevent: 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 + xl_event.tagData.msg.data = tuple(msg.data) return xl_event @@ -537,9 +536,9 @@ def _send_can_fd_msg_sequence(self, msgs: typing.Sequence[Message]) -> int: mask = self._get_tx_channel_mask(msgs) message_count = len(msgs) - xl_can_tx_event_array = (xlclass.XLcanTxEvent * message_count)() - for idx, msg in enumerate(msgs): - xl_can_tx_event_array[idx] = self._build_xl_can_tx_event(msg) + xl_can_tx_event_array = (xlclass.XLcanTxEvent * message_count)( + *map(self._build_xl_can_tx_event, msgs) + ) msg_count_sent = ctypes.c_uint(0) xldriver.xlCanTransmitEx( @@ -568,8 +567,7 @@ def _build_xl_can_tx_event(msg: Message) -> xlclass.XLcanTxEvent: xl_can_tx_event.tagData.canMsg.canId = msg_id xl_can_tx_event.tagData.canMsg.msgFlags = flags xl_can_tx_event.tagData.canMsg.dlc = len2dlc(msg.dlc) - for idx, value in enumerate(msg.data): - xl_can_tx_event.tagData.canMsg.data[idx] = value + xl_can_tx_event.tagData.canMsg.data = tuple(msg.data) return xl_can_tx_event From 202dabdafacc945eb64df6cfcc7c64dce92decd9 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 6 Jun 2020 01:49:33 +0200 Subject: [PATCH 0421/1235] implement message direction for VectorBus --- can/interfaces/vector/canlib.py | 201 +++++++++++++++++--------------- 1 file changed, 106 insertions(+), 95 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ac85ef4ce..4866c5542 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,6 +10,7 @@ import logging import time import os +from typing import Optional, Tuple try: # Try builtin Python 3 Windows API @@ -342,113 +343,32 @@ def _apply_filters(self, filters): except VectorError as exc: LOG.warning("Could not reset filters: %s", exc) - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: end_time = time.time() + timeout if timeout is not None else None - if self.fd: - event = xlclass.XLcanRxEvent() - else: - event = xlclass.XLevent() - event_count = ctypes.c_uint() - while True: - if self.fd: - try: - xldriver.xlCanReceive(self.port_handle, event) - except VectorError as exc: - if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY.value: - raise + try: + if self.fd: + msg = self._recv_canfd() else: - if ( - 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) - flags = event.tagData.canRxOkMsg.msgFlags - timestamp = event.timeStamp * 1e-9 - channel = self.index_to_channel.get(event.chanIndex) - msg = Message( - timestamp=timestamp + self._time_offset, - arbitration_id=msg_id & 0x1FFFFFFF, - 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 - & 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 - ), - dlc=dlc, - data=event.tagData.canRxOkMsg.data[:dlc], - channel=channel, - ) - return msg, self._is_filtered - else: - self.handle_canfd_event(event) + msg = self._recv_can() + except VectorError as exc: + if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY.value: + raise else: - event_count.value = 1 - try: - xldriver.xlReceive(self.port_handle, event_count, event) - except VectorError as exc: - if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY.value: - raise - else: - 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 - timestamp = event.timeStamp * 1e-9 - channel = self.index_to_channel.get(event.chanIndex) - msg = Message( - timestamp=timestamp + self._time_offset, - arbitration_id=msg_id & 0x1FFFFFFF, - is_extended_id=bool( - msg_id - & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value - ), - is_remote_frame=bool( - flags - & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value - ), - is_error_frame=bool( - flags - & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_ERROR_FRAME.value - ), - is_fd=False, - dlc=dlc, - data=event.tagData.msg.data[:dlc], - channel=channel, - ) - return msg, self._is_filtered - else: - self.handle_can_event(event) + if msg: + return msg, self._is_filtered + # if no message was received, wait or return on timeout if end_time is not None and time.time() > end_time: return None, self._is_filtered if HAS_EVENTS: # Wait for receive event to occur - if timeout is None: + if end_time is None: time_left_ms = INFINITE else: time_left = end_time - time.time() @@ -458,6 +378,97 @@ def _recv_internal(self, timeout): # Wait a short time until we try again time.sleep(self.poll_interval) + def _recv_canfd(self) -> Optional[Message]: + xl_can_rx_event = xlclass.XLcanRxEvent() + xldriver.xlCanReceive(self.port_handle, xl_can_rx_event) + + if ( + xl_can_rx_event.tag + == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK.value + ): + is_rx = True + data_struct = xl_can_rx_event.tagData.canRxOkMsg + elif ( + xl_can_rx_event.tag + == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_TX_OK.value + ): + is_rx = False + data_struct = xl_can_rx_event.tagData.canTxOkMsg + else: + self.handle_canfd_event(xl_can_rx_event) + return + + msg_id = data_struct.canId + dlc = dlc2len(data_struct.dlc) + flags = data_struct.msgFlags + timestamp = xl_can_rx_event.timeStamp * 1e-9 + channel = self.index_to_channel.get(xl_can_rx_event.chanIndex) + + msg = Message( + timestamp=timestamp + self._time_offset, + arbitration_id=msg_id & 0x1FFFFFFF, + 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 + ), + bitrate_switch=bool( + flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_BRS.value + ), + error_state_indicator=bool( + flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_ESI.value + ), + is_rx=is_rx, + channel=channel, + dlc=dlc, + data=data_struct.data[:dlc], + ) + return msg + + def _recv_can(self) -> Optional[Message]: + xl_event = xlclass.XLevent() + event_count = ctypes.c_uint(1) + xldriver.xlReceive(self.port_handle, event_count, xl_event) + + if xl_event.tag != xldefine.XL_EventTags.XL_RECEIVE_MSG.value: + self.handle_can_event(xl_event) + return + + msg_id = xl_event.tagData.msg.id + dlc = xl_event.tagData.msg.dlc + flags = xl_event.tagData.msg.flags + timestamp = xl_event.timeStamp * 1e-9 + channel = self.index_to_channel.get(xl_event.chanIndex) + + msg = Message( + timestamp=timestamp + self._time_offset, + arbitration_id=msg_id & 0x1FFFFFFF, + is_extended_id=bool( + msg_id & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + ), + is_remote_frame=bool( + flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value + ), + is_error_frame=bool( + flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_ERROR_FRAME.value + ), + is_rx=not bool( + flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_TX_COMPLETED.value + ), + is_fd=False, + dlc=dlc, + data=xl_event.tagData.msg.data[:dlc], + channel=channel, + ) + return msg + def handle_can_event(self, event: xlclass.XLevent) -> None: """Handle non-message CAN events. From b73014a6297f1296bda2d21f2db24226239a1f3b Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Tue, 9 Jun 2020 12:47:32 -0400 Subject: [PATCH 0422/1235] Filter out Tx message with global error flag set --- can/interfaces/ics_neovi/neovi_bus.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 638460a4c..02e4f55b2 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -261,9 +261,15 @@ def _process_msg_queue(self, timeout=0.1): for ics_msg in messages: if ics_msg.NetworkID not in self.channels: continue + is_tx = bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG) - if not self._receive_own_messages and is_tx: - continue + + if is_tx: + if bool(ics_msg.StatusBitField & ics.SPY_STATUS_GLOBAL_ERR): + continue + if not self._receive_own_messages: + continue + self.rx_buffer.append(ics_msg) if errors: logger.warning("%d error(s) found", errors) From 035cedb181322ae8f5620414e08458f21b5b5139 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 15 Jun 2020 13:16:16 -0400 Subject: [PATCH 0423/1235] Adding support for transmit (send) timeout in neovi (#855) * Neovi tx error filter and synchronous send * Implementing transmit (send) timeout support for neovi --- can/interfaces/ics_neovi/neovi_bus.py | 36 +++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 02e4f55b2..9d41cec8e 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -11,7 +11,9 @@ import logging import os import tempfile -from collections import deque +from collections import deque, defaultdict +from itertools import cycle +from threading import Event from can import Message, CanError, BusABC @@ -55,6 +57,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Use inter-process mutex to prevent concurrent device open. # When neoVI server is enabled, there is an issue with concurrent device open. open_lock = FileLock(os.path.join(tempfile.gettempdir(), "neovi.lock")) +description_id = cycle(range(1, 0x8000)) class ICSApiError(CanError): @@ -176,6 +179,7 @@ def __init__(self, channel, can_filters=None, **kwargs): logger.info("Using device: {}".format(self.channel_info)) self.rx_buffer = deque() + self.message_receipts = defaultdict(Event) @staticmethod def channel_to_netid(channel_name_or_id): @@ -267,6 +271,9 @@ def _process_msg_queue(self, timeout=0.1): if is_tx: if bool(ics_msg.StatusBitField & ics.SPY_STATUS_GLOBAL_ERR): continue + if ics_msg.DescriptionID: + receipt_key = (ics_msg.ArbIDOrHeader, ics_msg.DescriptionID) + self.message_receipts[receipt_key].set() if not self._receive_own_messages: continue @@ -349,7 +356,19 @@ def _recv_internal(self, timeout=0.1): return None, False return msg, False - def send(self, msg, timeout=None): + def send(self, msg, timeout=0): + """Transmit a message to the CAN bus. + + :param Message msg: A message object. + + :param float timeout: + If > 0, wait up to this many seconds for message to be ACK'ed. + If timeout is exceeded, an exception will be raised. + None blocks indefinitely. + + :raises can.CanError: + if the message could not be sent + """ if not ics.validate_hobject(self.dev): raise CanError("bus not open") message = ics.SpyMessage() @@ -385,7 +404,20 @@ def send(self, msg, timeout=None): else: raise ValueError("msg.channel must be set when using multiple channels.") + msg_desc_id = next(description_id) + message.DescriptionID = msg_desc_id + receipt_key = (msg.arbitration_id, msg_desc_id) + + if timeout != 0: + self.message_receipts[receipt_key].clear() + try: ics.transmit_messages(self.dev, message) except ics.RuntimeError: raise ICSApiError(*ics.get_last_api_error(self.dev)) + + # If timeout is set, wait for ACK + # This requires a notifier for the bus or + # some other thread calling recv periodically + if timeout != 0 and not self.message_receipts[receipt_key].wait(timeout): + raise CanError("Transmit timeout") From 49bd764731ac572876fb75f37ca5db342a013145 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Tue, 16 Jun 2020 08:24:12 -0400 Subject: [PATCH 0424/1235] Converting vector interface argument to lowercase Variable name should be lowercase, with words separated by underscores as necessary to improve readability. (PEP8) Current CamelCase arguments cannot be set from the configuration file since the variables are read as lowercase. --- can/interfaces/vector/canlib.py | 24 ++++++++++++------------ test/test_vector.py | 16 +++++++--------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ac85ef4ce..f3910f730 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -63,12 +63,12 @@ def __init__( serial=None, fd=False, data_bitrate=None, - sjwAbr=2, - tseg1Abr=6, - tseg2Abr=3, - sjwDbr=2, - tseg1Dbr=6, - tseg2Dbr=3, + sjw_abr=2, + tseg1_abr=6, + tseg2_abr=3, + sjw_dbr=2, + tseg1_dbr=6, + tseg2_dbr=3, **kwargs, ): """ @@ -219,16 +219,16 @@ def __init__( self.canFdConf.arbitrationBitRate = ctypes.c_uint(bitrate) else: self.canFdConf.arbitrationBitRate = ctypes.c_uint(500000) - self.canFdConf.sjwAbr = ctypes.c_uint(sjwAbr) - self.canFdConf.tseg1Abr = ctypes.c_uint(tseg1Abr) - self.canFdConf.tseg2Abr = ctypes.c_uint(tseg2Abr) + self.canFdConf.sjwAbr = ctypes.c_uint(sjw_abr) + self.canFdConf.tseg1Abr = ctypes.c_uint(tseg1_abr) + self.canFdConf.tseg2Abr = ctypes.c_uint(tseg2_abr) if data_bitrate: self.canFdConf.dataBitRate = ctypes.c_uint(data_bitrate) else: self.canFdConf.dataBitRate = self.canFdConf.arbitrationBitRate - self.canFdConf.sjwDbr = ctypes.c_uint(sjwDbr) - self.canFdConf.tseg1Dbr = ctypes.c_uint(tseg1Dbr) - self.canFdConf.tseg2Dbr = ctypes.c_uint(tseg2Dbr) + self.canFdConf.sjwDbr = ctypes.c_uint(sjw_dbr) + self.canFdConf.tseg1Dbr = ctypes.c_uint(tseg1_dbr) + self.canFdConf.tseg2Dbr = ctypes.c_uint(tseg2_dbr) xldriver.xlCanFdSetConfiguration( self.port_handle, self.mask, self.canFdConf diff --git a/test/test_vector.py b/test/test_vector.py index 73b8a2a4a..c5d894e5a 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -6,10 +6,8 @@ """ import ctypes -import pickle -import time -import logging import os +import pickle import unittest from unittest.mock import Mock @@ -131,12 +129,12 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: fd=True, bitrate=500000, data_bitrate=2000000, - sjwAbr=10, - tseg1Abr=11, - tseg2Abr=12, - sjwDbr=13, - tseg1Dbr=14, - tseg2Dbr=15, + sjw_abr=10, + tseg1_abr=11, + tseg2_abr=12, + sjw_dbr=13, + tseg1_dbr=14, + tseg2_dbr=15, _testing=True, ) self.assertIsInstance(self.bus, canlib.VectorBus) From f2bd6c4ed8b734e3fcac21591d1e09de402e6c39 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Tue, 16 Jun 2020 08:55:22 -0400 Subject: [PATCH 0425/1235] Converting vector timing argument to int --- can/interfaces/vector/canlib.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index f3910f730..1b344d164 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -48,6 +48,10 @@ LOG.warning("Could not import vxlapi: %s", exc) +def arg_to_c_uint(value): + return ctypes.c_uint(int(value)) + + class VectorBus(BusABC): """The CAN Bus implemented for the Vector interface.""" @@ -216,19 +220,19 @@ def __init__( if fd: self.canFdConf = xlclass.XLcanFdConf() if bitrate: - self.canFdConf.arbitrationBitRate = ctypes.c_uint(bitrate) + self.canFdConf.arbitrationBitRate = arg_to_c_uint(bitrate) else: - self.canFdConf.arbitrationBitRate = ctypes.c_uint(500000) - self.canFdConf.sjwAbr = ctypes.c_uint(sjw_abr) - self.canFdConf.tseg1Abr = ctypes.c_uint(tseg1_abr) - self.canFdConf.tseg2Abr = ctypes.c_uint(tseg2_abr) + self.canFdConf.arbitrationBitRate = arg_to_c_uint(500000) + self.canFdConf.sjwAbr = arg_to_c_uint(sjw_abr) + self.canFdConf.tseg1Abr = arg_to_c_uint(tseg1_abr) + self.canFdConf.tseg2Abr = arg_to_c_uint(tseg2_abr) if data_bitrate: - self.canFdConf.dataBitRate = ctypes.c_uint(data_bitrate) + self.canFdConf.dataBitRate = arg_to_c_uint(data_bitrate) else: self.canFdConf.dataBitRate = self.canFdConf.arbitrationBitRate - self.canFdConf.sjwDbr = ctypes.c_uint(sjw_dbr) - self.canFdConf.tseg1Dbr = ctypes.c_uint(tseg1_dbr) - self.canFdConf.tseg2Dbr = ctypes.c_uint(tseg2_dbr) + self.canFdConf.sjwDbr = arg_to_c_uint(sjw_dbr) + self.canFdConf.tseg1Dbr = arg_to_c_uint(tseg1_dbr) + self.canFdConf.tseg2Dbr = arg_to_c_uint(tseg2_dbr) xldriver.xlCanFdSetConfiguration( self.port_handle, self.mask, self.canFdConf From 87a6840da01f74ca90f46dcc127f7dc7f3e70157 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Tue, 16 Jun 2020 09:04:00 -0400 Subject: [PATCH 0426/1235] Adding vector timing arguments docstring --- can/interfaces/vector/canlib.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 1b344d164..ab3e79437 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -99,6 +99,18 @@ def __init__( :param int data_bitrate: Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. + :param int sjw_abr: + Bus timing value sample jump width (arbitration). + :param int tseg1_abr: + Bus timing value tseg1 (arbitration) + :param int tseg2_abr: + Bus timing value tseg2 (arbitration) + :param int sjw_dbr: + Bus timing value sample jump width (data) + :param int tseg1_dbr: + Bus timing value tseg1 (data) + :param int tseg2_dbr: + Bus timing value tseg2 (data) """ if os.name != "nt" and not kwargs.get("_testing", False): raise OSError( From d856fc6c475622d147099afe19ec62083fa0689c Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Tue, 16 Jun 2020 11:51:34 -0400 Subject: [PATCH 0427/1235] Adding deprecation warning and args aliases --- can/interfaces/vector/canlib.py | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ab3e79437..e2d7ced2c 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -7,9 +7,11 @@ # Import Standard Python Modules # ============================== import ctypes +import functools import logging import time import os +import warnings try: # Try builtin Python 3 Windows API @@ -48,6 +50,33 @@ LOG.warning("Could not import vxlapi: %s", exc) +def deprecated_args_alias(**aliases): + def deco(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + rename_kwargs(f.__name__, kwargs, aliases) + return f(*args, **kwargs) + + return wrapper + + return deco + + +def rename_kwargs(func_name, kwargs, aliases): + for alias, new in aliases.items(): + if alias in kwargs: + warnings.warn( + "{} is deprecated; use {}".format(alias, new), DeprecationWarning + ) + if new in kwargs: + raise TypeError( + "{} received both {} (deprecated) and {}".format( + func_name, alias, new + ) + ) + kwargs[new] = kwargs.pop(alias) + + def arg_to_c_uint(value): return ctypes.c_uint(int(value)) @@ -55,6 +84,16 @@ def arg_to_c_uint(value): class VectorBus(BusABC): """The CAN Bus implemented for the Vector interface.""" + deprecated_args = dict( + sjwAbr="sjw_abr", + tseg1Abr="tseg1_abr", + tseg2Abr="tseg2_abr", + sjwDbr="sjw_dbr", + tseg1Dbr="tseg1_dbr", + tseg2Dbr="tseg2_dbr", + ) + + @deprecated_args_alias(**deprecated_args) def __init__( self, channel, From e05024c9db8d2acbce4128db7e650d85734789d1 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Thu, 18 Jun 2020 08:29:46 -0400 Subject: [PATCH 0428/1235] Move helper functions in proper utils modules --- can/ctypesutil.py | 5 +++++ can/interfaces/vector/canlib.py | 37 +++------------------------------ can/util.py | 35 ++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index cba8e797b..94808c516 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -101,3 +101,8 @@ class HANDLE(ctypes.c_void_p): PHANDLE = ctypes.POINTER(HANDLE) + + +def arg_to_c_uint(value): + """Convert a number or string to an C unsigned integer""" + return ctypes.c_uint(int(value)) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 996690baa..c12581bba 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -7,14 +7,13 @@ # Import Standard Python Modules # ============================== import ctypes -import functools import logging import time import os -import warnings from typing import Optional, Tuple + try: # Try builtin Python 3 Windows API from _winapi import WaitForSingleObject, INFINITE @@ -33,7 +32,8 @@ # Import Modules # ============== from can import BusABC, Message -from can.util import len2dlc, dlc2len +from can.ctypesutil import arg_to_c_uint +from can.util import len2dlc, dlc2len, deprecated_args_alias from .exceptions import VectorError # Define Module Logger @@ -52,37 +52,6 @@ LOG.warning("Could not import vxlapi: %s", exc) -def deprecated_args_alias(**aliases): - def deco(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - rename_kwargs(f.__name__, kwargs, aliases) - return f(*args, **kwargs) - - return wrapper - - return deco - - -def rename_kwargs(func_name, kwargs, aliases): - for alias, new in aliases.items(): - if alias in kwargs: - warnings.warn( - "{} is deprecated; use {}".format(alias, new), DeprecationWarning - ) - if new in kwargs: - raise TypeError( - "{} received both {} (deprecated) and {}".format( - func_name, alias, new - ) - ) - kwargs[new] = kwargs.pop(alias) - - -def arg_to_c_uint(value): - return ctypes.c_uint(int(value)) - - class VectorBus(BusABC): """The CAN Bus implemented for the Vector interface.""" diff --git a/can/util.py b/can/util.py index 968f1f7fd..b67092284 100644 --- a/can/util.py +++ b/can/util.py @@ -1,7 +1,8 @@ """ Utilities and configuration file parsing. """ - +import functools +import warnings from typing import Dict, Optional, Union from can import typechecking @@ -279,6 +280,38 @@ def channel2int(channel: Optional[Union[typechecking.Channel]]) -> Optional[int] return None +def deprecated_args_alias(**aliases): + """Allows to rename/deprecate a function kwarg(s) and + have the deprecated kwarg(s) set as alias(es) + """ + + def deco(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + rename_kwargs(f.__name__, kwargs, aliases) + return f(*args, **kwargs) + + return wrapper + + return deco + + +def rename_kwargs(func_name, kwargs, aliases): + """Helper function for `deprecated_args_alias`""" + for alias, new in aliases.items(): + if alias in kwargs: + warnings.warn( + "{} is deprecated; use {}".format(alias, new), DeprecationWarning + ) + if new in kwargs: + raise TypeError( + "{} received both {} (deprecated) and {}".format( + func_name, alias, new + ) + ) + kwargs[new] = kwargs.pop(alias) + + if __name__ == "__main__": print("Searching for configuration named:") print("\n".join(CONFIG_FILES)) From d36c408d61b657970b3abc8922e3f7dc2103c263 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Thu, 18 Jun 2020 11:55:20 -0400 Subject: [PATCH 0429/1235] Remove unneeded C type conversion --- can/ctypesutil.py | 5 ----- can/interfaces/vector/canlib.py | 19 +++++++++---------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 94808c516..cba8e797b 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -101,8 +101,3 @@ class HANDLE(ctypes.c_void_p): PHANDLE = ctypes.POINTER(HANDLE) - - -def arg_to_c_uint(value): - """Convert a number or string to an C unsigned integer""" - return ctypes.c_uint(int(value)) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index c12581bba..068b4acf8 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -32,7 +32,6 @@ # Import Modules # ============== from can import BusABC, Message -from can.ctypesutil import arg_to_c_uint from can.util import len2dlc, dlc2len, deprecated_args_alias from .exceptions import VectorError @@ -242,19 +241,19 @@ def __init__( if fd: self.canFdConf = xlclass.XLcanFdConf() if bitrate: - self.canFdConf.arbitrationBitRate = arg_to_c_uint(bitrate) + self.canFdConf.arbitrationBitRate = int(bitrate) else: - self.canFdConf.arbitrationBitRate = arg_to_c_uint(500000) - self.canFdConf.sjwAbr = arg_to_c_uint(sjw_abr) - self.canFdConf.tseg1Abr = arg_to_c_uint(tseg1_abr) - self.canFdConf.tseg2Abr = arg_to_c_uint(tseg2_abr) + self.canFdConf.arbitrationBitRate = 500000 + self.canFdConf.sjwAbr = int(sjw_abr) + self.canFdConf.tseg1Abr = int(tseg1_abr) + self.canFdConf.tseg2Abr = int(tseg2_abr) if data_bitrate: - self.canFdConf.dataBitRate = arg_to_c_uint(data_bitrate) + self.canFdConf.dataBitRate = int(data_bitrate) else: self.canFdConf.dataBitRate = self.canFdConf.arbitrationBitRate - self.canFdConf.sjwDbr = arg_to_c_uint(sjw_dbr) - self.canFdConf.tseg1Dbr = arg_to_c_uint(tseg1_dbr) - self.canFdConf.tseg2Dbr = arg_to_c_uint(tseg2_dbr) + self.canFdConf.sjwDbr = int(sjw_dbr) + self.canFdConf.tseg1Dbr = int(tseg1_dbr) + self.canFdConf.tseg2Dbr = int(tseg2_dbr) xldriver.xlCanFdSetConfiguration( self.port_handle, self.mask, self.canFdConf From 6af19d73a6ac044e80105692f8720f80b966bace Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 19 Jun 2020 07:42:27 -0400 Subject: [PATCH 0430/1235] Adding docstring examples Co-authored-by: Brian Thorne --- can/util.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/can/util.py b/can/util.py index b67092284..09dd05a6b 100644 --- a/can/util.py +++ b/can/util.py @@ -283,6 +283,12 @@ def channel2int(channel: Optional[Union[typechecking.Channel]]) -> Optional[int] def deprecated_args_alias(**aliases): """Allows to rename/deprecate a function kwarg(s) and have the deprecated kwarg(s) set as alias(es) + + Example: + + @deprecated_args_alias(oldArg="new_arg", anotherOldArg="another_new_arg") + def library_function(new_arg, another_new_arg): + pass """ def deco(f): From 2e37c9d00e51aaa9abffb6c44e54b04e84e5234b Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Fri, 19 Jun 2020 08:00:02 -0400 Subject: [PATCH 0431/1235] Allow to deprecate kwargs without aliases (obsolete) --- can/util.py | 32 +++++++++++++++++++------------ test/test_util.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 test/test_util.py diff --git a/can/util.py b/can/util.py index 09dd05a6b..c9c007b2b 100644 --- a/can/util.py +++ b/can/util.py @@ -281,14 +281,18 @@ def channel2int(channel: Optional[Union[typechecking.Channel]]) -> Optional[int] def deprecated_args_alias(**aliases): - """Allows to rename/deprecate a function kwarg(s) and + """Allows to rename/deprecate a function kwarg(s) and optionally have the deprecated kwarg(s) set as alias(es) - + Example: - + @deprecated_args_alias(oldArg="new_arg", anotherOldArg="another_new_arg") def library_function(new_arg, another_new_arg): pass + + @deprecated_args_alias(oldArg="new_arg", obsoleteOldArg=None) + def library_function(new_arg): + pass """ def deco(f): @@ -306,16 +310,20 @@ def rename_kwargs(func_name, kwargs, aliases): """Helper function for `deprecated_args_alias`""" for alias, new in aliases.items(): if alias in kwargs: - warnings.warn( - "{} is deprecated; use {}".format(alias, new), DeprecationWarning - ) - if new in kwargs: - raise TypeError( - "{} received both {} (deprecated) and {}".format( - func_name, alias, new - ) + value = kwargs.pop(alias) + if new is not None: + warnings.warn( + "{} is deprecated; use {}".format(alias, new), DeprecationWarning ) - kwargs[new] = kwargs.pop(alias) + if new in kwargs: + raise TypeError( + "{} received both {} (deprecated) and {}".format( + func_name, alias, new + ) + ) + kwargs[new] = value + else: + warnings.warn("{} is deprecated".format(alias), DeprecationWarning) if __name__ == "__main__": diff --git a/test/test_util.py b/test/test_util.py new file mode 100644 index 000000000..3dcde04fd --- /dev/null +++ b/test/test_util.py @@ -0,0 +1,49 @@ +import unittest +import warnings + +from can.util import rename_kwargs + + +class RenameKwargsTest(unittest.TestCase): + expected_kwargs = dict(a=1, b=2, c=3, d=4) + + def _test(self, kwargs, aliases): + + # Test that we do get the DeprecationWarning when called with deprecated kwargs + with self.assertWarnsRegex(DeprecationWarning, "is deprecated"): + rename_kwargs("unit_test", kwargs, aliases) + + # Test that the aliases contains the deprecated values and + # the obsolete kwargs have been removed + assert kwargs == self.expected_kwargs + + # Test that we do not get a DeprecationWarning when we call + # without deprecated kwargs + + # Cause all warnings to always be triggered. + warnings.simplefilter("error", DeprecationWarning) + try: + rename_kwargs("unit_test", kwargs, aliases) + finally: + warnings.resetwarnings() + + def test_rename(self): + kwargs = dict(old_a=1, old_b=2, c=3, d=4) + aliases = {"old_a": "a", "old_b": "b"} + self._test(kwargs, aliases) + + def test_obsolete(self): + kwargs = dict(a=1, b=2, c=3, d=4, z=10) + aliases = {"z": None} + self._test(kwargs, aliases) + + def test_rename_and_obsolete(self): + kwargs = dict(old_a=1, old_b=2, c=3, d=4, z=10) + aliases = {"old_a": "a", "old_b": "b", "z": None} + self._test(kwargs, aliases) + + def test_with_new_and_alias_present(self): + kwargs = dict(old_a=1, a=1, b=2, c=3, d=4, z=10) + aliases = {"old_a": "a", "old_b": "b", "z": None} + with self.assertRaises(TypeError): + self._test(kwargs, aliases) From 525c3fed81d6550b3c84de809bfb8af9990831ae Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 19 Jun 2020 22:18:40 +1200 Subject: [PATCH 0432/1235] Add missing 3.3.x release notes to changelog --- CHANGELOG.txt | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2f50dca8d..3027c1ff3 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,52 @@ +Version 3.3.4 +==== + +Last call for Python2 support. + +* #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. + + +Version 3.3.3 +==== + +Backported fixes from 4.x development branch which targets Python 3. + +* #798 Backport caching msg.data value in neovi interface. +* #796 Fix Vector CANlib treatment of empty app name. +* #771 Handle empty CSV file. +* #741 ASCII reader can now handle FD frames. +* #740 Exclude test packages from distribution. +* #713 RTR crash fix in canutils log reader parsing RTR frames. +* #701 Skip J1939 messages in ASC Reader. +* #690 Exposes a configuration option to allow the CAN message player to send error frames + (and sets the default to not send error frames). +* #638 Fixes the semantics provided by periodic tasks in SocketCAN interface. +* #628 Avoid padding CAN_FD_MESSAGE_64 objects to 4 bytes. +* #617 Fixes the broken CANalyst-II interface. +* #605 Socketcan BCM status fix. + + +Version 3.3.2 +==== + +Minor bug fix release addressing issue in PCAN RTR. + + +Version 3.3.1 +==== + +Minor fix to setup.py to only require pytest-runner when necessary. + + +Version 3.3.0 +==== + +* Adding CAN FD 64 frame support to blf reader +* Updates to installation instructions +* Clean up bits generator in PCAN interface #588 +* Minor fix to use latest tools when building wheels on travis. + + Version 3.2.0 ==== From 82599c01885f3603c64c184262ea7fe437f23aec Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 19 Jun 2020 22:25:47 +1200 Subject: [PATCH 0433/1235] Set version to 4.0.0-dev --- CHANGELOG.txt | 6 ++++++ can/__init__.py | 7 +++++-- setup.py | 12 ++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 3027c1ff3..f2cb69f93 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,9 @@ +Version 4.0.0 +==== + +(In development) + + Version 3.3.4 ==== diff --git a/can/__init__.py b/can/__init__.py index 457546307..39605b57e 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -1,12 +1,15 @@ """ -``can`` is an object-orient Controller Area Network (CAN) interface module. +The ``can`` package provides controller area network support for +Python developers; providing common abstractions to +different hardware devices, and a suite of utilities for sending and receiving +messages on a can bus. """ import logging from typing import Dict, Any -__version__ = "3.2.0" +__version__ = "4.0.0-dev" log = logging.getLogger("can") diff --git a/setup.py b/setup.py index 6499aad8b..542a8cc93 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,5 @@ #!/usr/bin/env python -""" -python-can requires the setuptools package to be installed. -""" - # pylint: disable=invalid-name from __future__ import absolute_import @@ -42,6 +38,7 @@ "Programming Language :: Python", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", @@ -62,16 +59,11 @@ "Topic :: System :: Hardware :: Hardware Drivers", "Topic :: Utilities", ], - # Code version=version, packages=find_packages(exclude=["test*", "doc", "scripts", "examples"]), scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), - # Author - author="Brian Thorne", - author_email="brian@thorne.link", - # License + author="Python CAN contributors", license="LGPL v3", - # Package data package_data={ "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], "doc": ["*.*"], From a213aeddf9c6597e598e456b63f1872d34421cd0 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 20 Jun 2020 08:14:02 +1200 Subject: [PATCH 0434/1235] Add missing changelog entry for 3.2.1 --- CHANGELOG.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f2cb69f93..cf2a8b027 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -11,7 +11,6 @@ Last call for Python2 support. * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. - Version 3.3.3 ==== @@ -37,13 +36,11 @@ Version 3.3.2 Minor bug fix release addressing issue in PCAN RTR. - Version 3.3.1 ==== Minor fix to setup.py to only require pytest-runner when necessary. - Version 3.3.0 ==== @@ -52,6 +49,12 @@ Version 3.3.0 * Clean up bits generator in PCAN interface #588 * Minor fix to use latest tools when building wheels on travis. +Version 3.2.1 +==== + +* CAN FD 64 frame support to blf reader +* Minor fix to use latest tools when building wheels on travis. +* Updates links in documentation. Version 3.2.0 ==== From 60db830f3cf7b8bdc414e741e7324f243d596946 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 22 Jun 2020 09:52:50 +1200 Subject: [PATCH 0435/1235] Use more inclusive language --- .pylintrc | 4 ++-- .pylintrc-wip | 4 ++-- can/interfaces/ixxat/canlib.py | 2 +- doc/interfaces/ixxat.rst | 18 +++++++++++------- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.pylintrc b/.pylintrc index 5a2613994..2389f7559 100644 --- a/.pylintrc +++ b/.pylintrc @@ -5,11 +5,11 @@ # run arbitrary code. extension-pkg-whitelist= -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to be ignored. They should be base names, not # paths. ignore=CVS -# Add files or directories matching the regex patterns to the blacklist. The +# Add files or directories matching the regex patterns to be ignored. The # regex matches against base names, not paths. ignore-patterns= diff --git a/.pylintrc-wip b/.pylintrc-wip index c028f9f3d..67ad614ab 100644 --- a/.pylintrc-wip +++ b/.pylintrc-wip @@ -5,11 +5,11 @@ # run arbitrary code. extension-pkg-whitelist= -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to be ignored. They should be base names, not # paths. ignore=CVS -# Add files or directories matching the regex patterns to the blacklist. The +# Add files or directories matching the regex patterns to to be ignored. The # regex matches against base names, not paths. ignore-patterns= diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index aa90ffafe..4108be752 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -531,7 +531,7 @@ def __init__(self, channel, can_filters=None, **kwargs): constants.CAN_ACC_MASK_NONE, ) for can_filter in can_filters: - # Whitelist + # Filters define what messages are accepted code = int(can_filter["can_id"]) mask = int(can_filter["can_mask"]) extended = can_filter.get("extended", False) diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 9ab79ffcf..845585dee 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -43,6 +43,17 @@ module, while the following parameters are optional and are interpreted by IXXAT * ``extended`` (default False) Allow usage of extended IDs +Filtering +--------- + +The CAN filters act as an allow list in IXXAT implementation, that is if you +supply a non-empty filter list you must explicitly state EVERY frame you want +to receive (including RTR field). +The can_id/mask must be specified according to IXXAT behaviour, that is +bit 0 of can_id/mask parameters represents the RTR field in CAN frame. See IXXAT +VCI documentation, section "Message filters" for more info. + + Internals --------- @@ -58,10 +69,3 @@ explicitly instantiated by the caller. RX and TX FIFO sizes are configurable with ``rxFifoSize`` and ``txFifoSize`` options, defaulting at 16 for both. - -The CAN filters act as a "whitelist" in IXXAT implementation, that is if you -supply a non-empty filter list you must explicitly state EVERY frame you want -to receive (including RTR field). -The can_id/mask must be specified according to IXXAT behaviour, that is -bit 0 of can_id/mask parameters represents the RTR field in CAN frame. See IXXAT -VCI documentation, section "Message filters" for more info. From 5b68375cfb6aacfbf028910f905f8209f42e0fd5 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 22 Jun 2020 10:51:18 +1200 Subject: [PATCH 0436/1235] Pylint takes modules not directories/paths --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3b674f371..dfdf70f08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,12 +83,12 @@ jobs: - pylint --rcfile=.pylintrc-wip can/**.py # check setup.py - pylint --rcfile=.pylintrc *.py - # check doc/conf.py and possible other scripts in there - - pylint --rcfile=.pylintrc doc/**.py + # check doc/conf.py + - pylint --rcfile=.pylintrc doc.conf # check the scripts folder - - pylint --rcfile=.pylintrc scripts/**.py + - find scripts -type f -name "*.py" | xargs pylint --rcfile=.pylintrc # check the examples - - pylint --rcfile=.pylintrc-wip examples/**.py + - find examples -type f -name "*.py" | xargs pylint --rcfile=.pylintrc-wip # ------------- # mypy checking: - mypy From 9fe97d01c0e92c38e10d5dfabb9fddf289692883 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 22 Jun 2020 13:06:46 +1200 Subject: [PATCH 0437/1235] setup.py doesn't really need a module docstring --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 542a8cc93..25705bb89 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # pylint: disable=invalid-name +# pylint: disable=missing-module-docstring from __future__ import absolute_import From 343a0d786ca5acfa9d84510927310f296c4958da Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 9 Jul 2020 21:52:49 +1200 Subject: [PATCH 0438/1235] Linting setup.py --- .travis.yml | 4 +--- setup.py | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index dfdf70f08..fe4253992 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,9 +81,7 @@ jobs: # re-introduced # check the entire main codebase - pylint --rcfile=.pylintrc-wip can/**.py - # check setup.py - - pylint --rcfile=.pylintrc *.py - # check doc/conf.py + - pylint --rcfile=.pylintrc setup.py - pylint --rcfile=.pylintrc doc.conf # check the scripts folder - find scripts -type f -name "*.py" | xargs pylint --rcfile=.pylintrc diff --git a/setup.py b/setup.py index 25705bb89..ab6194207 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,10 @@ #!/usr/bin/env python +""" +Setup script for the `can` package. +Learn more at https://github.com/hardbyte/python-can/ +""" # pylint: disable=invalid-name -# pylint: disable=missing-module-docstring from __future__ import absolute_import From 436e78f4356774d67ba00d575b01f943f871bd0e Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Thu, 9 Jul 2020 09:42:52 -0400 Subject: [PATCH 0439/1235] Raising more precise API error when set bitrate fails Also calling shutdown before raising exception from the init if the device is opened --- can/interfaces/ics_neovi/neovi_bus.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 9d41cec8e..eff3620dc 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -158,15 +158,25 @@ def __init__(self, channel, can_filters=None, **kwargs): with open_lock: ics.open_device(self.dev) - if "bitrate" in kwargs: - for channel in self.channels: - ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel) - - fd = kwargs.get("fd", False) - if fd: - if "data_bitrate" in kwargs: + try: + if "bitrate" in kwargs: for channel in self.channels: - ics.set_fd_bit_rate(self.dev, kwargs.get("data_bitrate"), channel) + ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel) + + fd = kwargs.get("fd", False) + if fd: + if "data_bitrate" in kwargs: + for channel in self.channels: + ics.set_fd_bit_rate( + self.dev, kwargs.get("data_bitrate"), channel + ) + except ics.RuntimeError as re: + logger.error(re) + err = ICSApiError(*ics.get_last_api_error(self.dev)) + try: + self.shutdown() + finally: + raise err self._use_system_timestamp = bool(kwargs.get("use_system_timestamp", False)) self._receive_own_messages = kwargs.get("receive_own_messages", True) From 8fca2a9d1af97204ce4d864323a552eba2ac5a60 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Thu, 9 Jul 2020 10:56:12 -0400 Subject: [PATCH 0440/1235] Simplified if fd condition in init --- can/interfaces/ics_neovi/neovi_bus.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index eff3620dc..aa332bfcf 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -163,8 +163,7 @@ def __init__(self, channel, can_filters=None, **kwargs): for channel in self.channels: ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel) - fd = kwargs.get("fd", False) - if fd: + if kwargs.get("fd", False): if "data_bitrate" in kwargs: for channel in self.channels: ics.set_fd_bit_rate( From fca9207ae0f98b9dc055ae6b8a0619ad29a4d29a Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Wed, 15 Jul 2020 09:31:06 -0400 Subject: [PATCH 0441/1235] Simplified the serial number decode conditions --- can/interfaces/ics_neovi/neovi_bus.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index aa332bfcf..defe9850d 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -212,10 +212,10 @@ def get_serial_number(device): :return: ics device serial string :rtype: str """ - a0000 = 604661760 - if device.SerialNumber >= a0000: + if int("AA0000", 36) < device.SerialNumber < int("ZZZZZZ", 36): return ics.base36enc(device.SerialNumber) - return str(device.SerialNumber) + else: + return str(device.SerialNumber) def shutdown(self): super().shutdown() From 63c60af045bdf23e2a7587e753ac4566a3f377c6 Mon Sep 17 00:00:00 2001 From: karl ding Date: Wed, 15 Jul 2020 20:13:28 -0700 Subject: [PATCH 0442/1235] Enable GitHub Actions runners to run tests (#827) GitHub announced Actions at GitHub Universe in 2018, however this was somewhat limited, and support for running CI/CD pipelines was only released in August 2019. This was in beta for a long time, and finally has been rolled out to the general public. This adds a minimal GitHub Actions workflow that runs all the current tests on a build matrix consisting of {Ubuntu,macOS,Windows} and Python 3.{6,7,8}. This also seems to run much faster than the current Travis CI pipelines, and actually lets us cover multiple versions on macOS without a series of hacks. Unfortunately, the kernel that GitHub Actions uses (5.0.0-1035-azure) doesn't include the vcan kernel module, so we still need Travis CI to run SocketCAN tests. --- .github/workflows/build.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..3bcb94f86 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: Tests + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[test] + - name: Test with pytest + run: | + pytest From 6eb7d5d0d2e13c11ba9aba832880701d451b773e Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 16 Jul 2020 21:25:17 +1200 Subject: [PATCH 0443/1235] Use tox for testing with github actions --- .github/workflows/build.yml | 10 +++++++--- tox.ini | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3bcb94f86..06e72125e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install .[test] - - name: Test with pytest + pip install tox + pip install -r requirements-lint.txt + - name: Test with pytest via tox run: | - pytest + tox -e gh + - name: Code Format Check with Black + run: | + black --check --verbose . diff --git a/tox.ini b/tox.ini index e5df520fe..5cf50bd78 100644 --- a/tox.ini +++ b/tox.ini @@ -15,6 +15,11 @@ commands = recreate = True +[testenv:gh] +passenv = + CI + PYTHONPATH + [testenv:travis] passenv = CI From 587325cb9df8c1784fb254293cd0e60cbef03026 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 24 Jul 2020 21:09:39 +1200 Subject: [PATCH 0444/1235] Add CAN ID filtering example to docs. Contributed by @aaknitt --- doc/bus.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/bus.rst b/doc/bus.rst index b524d4868..1fbe771cb 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -57,8 +57,21 @@ Filtering ''''''''' Message filtering can be set up for each bus. Where the interface supports it, this is carried -out in the hardware or kernel layer - not in Python. +out in the hardware or kernel layer - not in Python. All messages that match at least one filter +are returned. +Example defining two filters, one to pass 11-bit ID ``0x451``, the other to pass 29-bit ID ``0xA0000``: + +.. code-block:: python + + filters = [ + {"can_id": 0x451, "can_mask": 0x7FF, "extended": False}, + {"can_id": 0xA0000, "can_mask": 0x1FFFFFFF, "extended": True}, + ] + bus = can.interface.Bus(channel="can0", bustype="socketcan", can_filters=filters) + + +See :meth:`~can.BusABC.set_filters` for the implementation. Thread safe bus --------------- From 13625ea2cc18e3283b8111bec7e139ed9b6c7f48 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:49:06 +0200 Subject: [PATCH 0445/1235] use IntEnum instead of Enum, remove aenum dependency --- can/bus.py | 2 +- can/interfaces/vector/canlib.py | 97 ++++++++++++------------------- can/interfaces/vector/xldefine.py | 46 +++++++-------- setup.py | 1 - 4 files changed, 62 insertions(+), 84 deletions(-) diff --git a/can/bus.py b/can/bus.py index e315ee9d5..90a3cd906 100644 --- a/can/bus.py +++ b/can/bus.py @@ -11,7 +11,7 @@ import logging import threading from time import time -from aenum import Enum, auto +from enum import Enum, auto from can.broadcastmanager import ThreadBasedCyclicSendTask from can.message import Message diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 731c8250b..658f256f1 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -178,14 +178,14 @@ def __init__( app_name, channel, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN ) LOG.debug("Channel index %d found", channel) - idx = xldriver.xlGetChannelIndex(hw_type.value, hw_index, hw_channel) + idx = xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) if idx < 0: # Undocumented behavior! See issue #353. # If hardware is unavailable, this function returns -1. # Raise an exception as if the driver # would have signalled XL_ERR_HW_NOT_PRESENT. raise VectorError( - xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.value, + xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name, "xlGetChannelIndex", ) @@ -208,8 +208,8 @@ def __init__( self.mask, permission_mask, rx_queue_size, - xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, ) else: xldriver.xlOpenPort( @@ -218,8 +218,8 @@ def __init__( self.mask, permission_mask, rx_queue_size, - xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, ) LOG.debug( "Open Port: PortHandle: %d, PermissionMask: 0x%X", @@ -286,10 +286,7 @@ def __init__( try: xldriver.xlActivateChannel( - self.port_handle, - self.mask, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, - 0, + self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0 ) except VectorError: self.shutdown() @@ -323,9 +320,9 @@ def _apply_filters(self, filters): self.mask, can_filter["can_id"], can_filter["can_mask"], - xldefine.XL_AcceptanceFilter.XL_CAN_EXT.value + xldefine.XL_AcceptanceFilter.XL_CAN_EXT if can_filter.get("extended") - else xldefine.XL_AcceptanceFilter.XL_CAN_STD.value, + else xldefine.XL_AcceptanceFilter.XL_CAN_STD, ) except VectorError as exc: LOG.warning("Could not set filters: %s", exc) @@ -345,14 +342,14 @@ def _apply_filters(self, filters): self.mask, 0x0, 0x0, - xldefine.XL_AcceptanceFilter.XL_CAN_EXT.value, + xldefine.XL_AcceptanceFilter.XL_CAN_EXT, ) xldriver.xlCanSetChannelAcceptance( self.port_handle, self.mask, 0x0, 0x0, - xldefine.XL_AcceptanceFilter.XL_CAN_STD.value, + xldefine.XL_AcceptanceFilter.XL_CAN_STD, ) except VectorError as exc: LOG.warning("Could not reset filters: %s", exc) @@ -370,7 +367,7 @@ def _recv_internal( msg = self._recv_can() except VectorError as exc: - if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY.value: + if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY: raise else: if msg: @@ -396,16 +393,10 @@ def _recv_canfd(self) -> Optional[Message]: xl_can_rx_event = xlclass.XLcanRxEvent() xldriver.xlCanReceive(self.port_handle, xl_can_rx_event) - if ( - xl_can_rx_event.tag - == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK.value - ): + if xl_can_rx_event.tag == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK: is_rx = True data_struct = xl_can_rx_event.tagData.canRxOkMsg - elif ( - xl_can_rx_event.tag - == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_TX_OK.value - ): + elif xl_can_rx_event.tag == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_TX_OK: is_rx = False data_struct = xl_can_rx_event.tagData.canTxOkMsg else: @@ -422,22 +413,20 @@ def _recv_canfd(self) -> Optional[Message]: timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, is_extended_id=bool( - msg_id & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + msg_id & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID ), is_remote_frame=bool( - flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_RTR.value + flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_RTR ), 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 + flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_EF ), + is_fd=bool(flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_EDL), bitrate_switch=bool( - flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_BRS.value + flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_BRS ), error_state_indicator=bool( - flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_ESI.value + flags & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_ESI ), is_rx=is_rx, channel=channel, @@ -451,7 +440,7 @@ def _recv_can(self) -> Optional[Message]: event_count = ctypes.c_uint(1) xldriver.xlReceive(self.port_handle, event_count, xl_event) - if xl_event.tag != xldefine.XL_EventTags.XL_RECEIVE_MSG.value: + if xl_event.tag != xldefine.XL_EventTags.XL_RECEIVE_MSG: self.handle_can_event(xl_event) return @@ -465,16 +454,16 @@ def _recv_can(self) -> Optional[Message]: timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, is_extended_id=bool( - msg_id & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + msg_id & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID ), is_remote_frame=bool( - flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value + flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME ), is_error_frame=bool( - flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_ERROR_FRAME.value + flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_ERROR_FRAME ), is_rx=not bool( - flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_TX_COMPLETED.value + flags & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_TX_COMPLETED ), is_fd=False, dlc=dlc, @@ -539,14 +528,14 @@ def _send_can_msg_sequence(self, msgs: typing.Sequence[Message]) -> int: def _build_xl_event(msg: Message) -> xlclass.XLevent: msg_id = msg.arbitration_id if msg.is_extended_id: - msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID flags = 0 if msg.is_remote_frame: - flags |= xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value + flags |= xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME xl_event = xlclass.XLevent() - xl_event.tag = xldefine.XL_EventTags.XL_TRANSMIT_MSG.value + xl_event.tag = xldefine.XL_EventTags.XL_TRANSMIT_MSG xl_event.tagData.msg.id = msg_id xl_event.tagData.msg.dlc = msg.dlc xl_event.tagData.msg.flags = flags @@ -573,18 +562,18 @@ def _send_can_fd_msg_sequence(self, msgs: typing.Sequence[Message]) -> int: def _build_xl_can_tx_event(msg: Message) -> xlclass.XLcanTxEvent: msg_id = msg.arbitration_id if msg.is_extended_id: - msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID flags = 0 if msg.is_fd: - flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_EDL.value + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_EDL if msg.bitrate_switch: - flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_BRS.value + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_BRS if msg.is_remote_frame: - flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_RTR.value + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_RTR xl_can_tx_event = xlclass.XLcanTxEvent() - xl_can_tx_event.tag = xldefine.XL_CANFD_TX_EventTags.XL_CAN_EV_TAG_TX_MSG.value + xl_can_tx_event.tag = xldefine.XL_CANFD_TX_EventTags.XL_CAN_EV_TAG_TX_MSG xl_can_tx_event.transId = 0xFFFF xl_can_tx_event.tagData.canMsg.canId = msg_id @@ -605,7 +594,7 @@ def shutdown(self): def reset(self): xldriver.xlDeactivateChannel(self.port_handle, self.mask) xldriver.xlActivateChannel( - self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, 0 + self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0 ) @staticmethod @@ -616,7 +605,7 @@ def _detect_available_configs(): for channel_config in channel_configs: if ( not channel_config.channelBusCapabilities - & xldefine.XL_BusCapabilities.XL_BUS_ACTIVE_CAP_CAN.value + & xldefine.XL_BusCapabilities.XL_BUS_ACTIVE_CAP_CAN ): continue LOG.info( @@ -631,7 +620,7 @@ def _detect_available_configs(): "channel": channel_config.channelIndex, "supports_fd": bool( channel_config.channelBusCapabilities - & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT.value + & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT ), } ) @@ -670,12 +659,7 @@ def get_application_config( hw_channel = ctypes.c_uint() xldriver.xlGetApplConfig( - app_name.encode(), - app_channel, - hw_type, - hw_index, - hw_channel, - bus_type.value, + app_name.encode(), app_channel, hw_type, hw_index, hw_channel, bus_type ) return xldefine.XL_HardwareType(hw_type.value), hw_index.value, hw_channel.value @@ -708,12 +692,7 @@ def set_application_config( XL_BusTypes.XL_BUS_TYPE_CAN for most cases. """ xldriver.xlSetApplConfig( - app_name.encode(), - app_channel, - hw_type.value, - hw_index, - hw_channel, - bus_type.value, + app_name.encode(), app_channel, hw_type, hw_index, hw_channel, bus_type ) def set_timer_rate(self, timer_rate_ms: int) -> None: diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index 28a35e7a2..eb228af34 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -4,7 +4,7 @@ # Import Python Modules # ============================== -from enum import Enum +from enum import IntEnum MAX_MSG_LEN = 8 @@ -12,44 +12,44 @@ XL_INVALID_PORTHANDLE = -1 -class XL_AC_Flags(Enum): +class XL_AC_Flags(IntEnum): XL_ACTIVATE_NONE = 0 XL_ACTIVATE_RESET_CLOCK = 8 -class XL_AcceptanceFilter(Enum): +class XL_AcceptanceFilter(IntEnum): XL_CAN_STD = 1 XL_CAN_EXT = 2 -class XL_BusCapabilities(Enum): +class XL_BusCapabilities(IntEnum): XL_BUS_COMPATIBLE_CAN = 1 XL_BUS_ACTIVE_CAP_CAN = 65536 -class XL_BusStatus(Enum): +class XL_BusStatus(IntEnum): XL_CHIPSTAT_BUSOFF = 1 XL_CHIPSTAT_ERROR_PASSIVE = 2 XL_CHIPSTAT_ERROR_WARNING = 4 XL_CHIPSTAT_ERROR_ACTIVE = 8 -class XL_BusTypes(Enum): +class XL_BusTypes(IntEnum): XL_BUS_TYPE_NONE = 0 XL_BUS_TYPE_CAN = 1 -class XL_CANFD_BusParams_CanOpMode(Enum): +class XL_CANFD_BusParams_CanOpMode(IntEnum): XL_BUS_PARAMS_CANOPMODE_CAN20 = 1 XL_BUS_PARAMS_CANOPMODE_CANFD = 2 XL_BUS_PARAMS_CANOPMODE_CANFD_NO_ISO = 8 -class XL_CANFD_ConfigOptions(Enum): +class XL_CANFD_ConfigOptions(IntEnum): CANFD_CONFOPT_NO_ISO = 8 -class XL_CANFD_RX_EV_ERROR_errorCode(Enum): +class XL_CANFD_RX_EV_ERROR_errorCode(IntEnum): XL_CAN_ERRC_BIT_ERROR = 1 XL_CAN_ERRC_FORM_ERROR = 2 XL_CAN_ERRC_STUFF_ERROR = 3 @@ -61,7 +61,7 @@ class XL_CANFD_RX_EV_ERROR_errorCode(Enum): XL_CAN_ERRC_EXCPT_ERROR = 9 -class XL_CANFD_RX_EventTags(Enum): +class XL_CANFD_RX_EventTags(IntEnum): XL_SYNC_PULSE = 11 XL_CAN_EV_TAG_RX_OK = 1024 XL_CAN_EV_TAG_RX_ERROR = 1025 @@ -71,7 +71,7 @@ class XL_CANFD_RX_EventTags(Enum): XL_CAN_EV_TAG_CHIP_STATE = 1033 -class XL_CANFD_RX_MessageFlags(Enum): +class XL_CANFD_RX_MessageFlags(IntEnum): XL_CAN_RXMSG_FLAG_NONE = 0 XL_CAN_RXMSG_FLAG_EDL = 1 XL_CAN_RXMSG_FLAG_BRS = 2 @@ -83,12 +83,12 @@ class XL_CANFD_RX_MessageFlags(Enum): XL_CAN_RXMSG_FLAG_TE = 16384 -class XL_CANFD_TX_EventTags(Enum): +class XL_CANFD_TX_EventTags(IntEnum): XL_CAN_EV_TAG_TX_MSG = 1088 # =0x0440 XL_CAN_EV_TAG_TX_ERRFR = 1089 # =0x0441 -class XL_CANFD_TX_MessageFlags(Enum): +class XL_CANFD_TX_MessageFlags(IntEnum): XL_CAN_TXMSG_FLAG_NONE = 0 XL_CAN_TXMSG_FLAG_EDL = 1 XL_CAN_TXMSG_FLAG_BRS = 2 @@ -97,7 +97,7 @@ class XL_CANFD_TX_MessageFlags(Enum): XL_CAN_TXMSG_FLAG_WAKEUP = 512 -class XL_ChannelCapabilities(Enum): +class XL_ChannelCapabilities(IntEnum): XL_CHANNEL_FLAG_TIME_SYNC_RUNNING = 1 XL_CHANNEL_FLAG_NO_HWSYNC_SUPPORT = 1024 XL_CHANNEL_FLAG_SPDIF_CAPABLE = 16384 @@ -106,7 +106,7 @@ class XL_ChannelCapabilities(Enum): XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 2147483648 -class XL_EventTags(Enum): +class XL_EventTags(IntEnum): XL_NO_COMMAND = 0 XL_RECEIVE_MSG = 1 XL_CHIP_STATE = 4 @@ -117,14 +117,14 @@ class XL_EventTags(Enum): XL_APPLICATION_NOTIFICATION = 15 -class XL_InterfaceVersion(Enum): +class XL_InterfaceVersion(IntEnum): 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): +class XL_MessageFlags(IntEnum): XL_CAN_MSG_FLAG_NONE = 0 XL_CAN_MSG_FLAG_ERROR_FRAME = 1 XL_CAN_MSG_FLAG_OVERRUN = 2 @@ -138,18 +138,18 @@ class XL_MessageFlags(Enum): XL_EVENT_FLAG_OVERRUN = 1 -class XL_MessageFlagsExtended(Enum): +class XL_MessageFlagsExtended(IntEnum): XL_CAN_EXT_MSG_ID = 2147483648 -class XL_OutputMode(Enum): +class XL_OutputMode(IntEnum): 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): +class XL_Sizes(IntEnum): XL_MAX_LENGTH = 31 XL_MAX_APPNAME = 32 XL_MAX_NAME_LENGTH = 48 @@ -158,7 +158,7 @@ class XL_Sizes(Enum): XL_APPLCONFIG_MAX_CHANNELS = 256 -class XL_Status(Enum): +class XL_Status(IntEnum): XL_SUCCESS = 0 # =0x0000 XL_PENDING = 1 # =0x0001 XL_ERR_QUEUE_IS_EMPTY = 10 # =0x000A @@ -206,13 +206,13 @@ class XL_Status(Enum): XL_ERR_UNKNOWN_FLAG = 518 # =0x206 -class XL_TimeSyncNewValue(Enum): +class XL_TimeSyncNewValue(IntEnum): XL_SET_TIMESYNC_NO_CHANGE = 0 XL_SET_TIMESYNC_ON = 1 XL_SET_TIMESYNC_OFF = 2 -class XL_HardwareType(Enum): +class XL_HardwareType(IntEnum): XL_HWTYPE_NONE = 0 XL_HWTYPE_VIRTUAL = 1 XL_HWTYPE_CANCARDX = 2 diff --git a/setup.py b/setup.py index ab6194207..64892354d 100644 --- a/setup.py +++ b/setup.py @@ -78,7 +78,6 @@ python_requires=">=3.6", install_requires=[ "wrapt~=1.10", - "aenum", 'windows-curses;platform_system=="Windows"', "filelock", "mypy_extensions >= 0.4.0, < 0.5.0", From 763842045ebac365f8881ee5e569acc9286f02e3 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 1 Aug 2020 13:23:06 +0200 Subject: [PATCH 0446/1235] implement rotating file logger --- can/__init__.py | 2 +- can/io/__init__.py | 2 +- can/io/logger.py | 101 +++++++++++++++++++++++++++++++++++++++++++-- doc/listeners.rst | 3 ++ 4 files changed, 103 insertions(+), 5 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 39605b57e..55fca0c4a 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -24,7 +24,7 @@ class CanError(IOError): from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader -from .io import Logger, Printer, LogReader, MessageSync +from .io import Logger, RotatingFileLogger, Printer, LogReader, MessageSync from .io import ASCWriter, ASCReader from .io import BLFReader, BLFWriter from .io import CanutilsLogReader, CanutilsLogWriter diff --git a/can/io/__init__.py b/can/io/__init__.py index 53389e91b..4ecd7a3e5 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -4,7 +4,7 @@ """ # Generic -from .logger import Logger +from .logger import Logger, RotatingFileLogger from .player import LogReader, MessageSync # Format specific diff --git a/can/io/logger.py b/can/io/logger.py index a4544b87d..c8b48fb13 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -3,13 +3,14 @@ """ import pathlib -import typing +from typing import Optional, Callable from pkg_resources import iter_entry_points import can.typechecking +from ..message import Message from ..listener import Listener -from .generic import BaseIOHandler +from .generic import BaseIOHandler, MessageWriter from .asc import ASCWriter from .blf import BLFWriter from .canutils import CanutilsLogWriter @@ -51,7 +52,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method @staticmethod def __new__( - cls, filename: typing.Optional[can.typechecking.StringPathLike], *args, **kwargs + cls, filename: Optional[can.typechecking.StringPathLike], *args, **kwargs ): """ :param filename: the filename/path of the file to write to, @@ -78,3 +79,97 @@ def __new__( raise ValueError( f'No write support for this unknown log format "{suffix}"' ) from None + + +class RotatingFileLogger(Listener): + """Log CAN messages to a sequence of files with a given maximum size. + + The log file name and path must be returned by the function`filename_func` + as a path-like object (e.g. a string). The log file format is defined by + the suffix of the path-like object. + + The RotatingFileLogger currently supports + * .asc: :class:`can.ASCWriter` + * .blf :class:`can.BLFWriter` + * .csv: :class:`can.CSVWriter` + * .log :class:`can.CanutilsLogWriter` + * .txt :class:`can.Printer` + + The log files may be incomplete until `stop()` is called due to buffering. + + Example:: + + from can import Notifier + from can.interfaces.vector import VectorBus + from can import RotatingFileLogger + + bus = VectorBus(channel=[0], app_name="CANape", fd=True) + logger = RotatingFileLogger( + filename_func=lambda idx: f"CAN_Log_{idx:03}.txt", # filename with three digit counter + max_bytes=5 * 1024 ** 2, # =5MB + initial_file_number=23, # start with number 23 + ) + notifier = Notifier(bus=bus, listeners=[logger]) + + """ + + supported_writers = { + ".asc": ASCWriter, + ".blf": BLFWriter, + ".csv": CSVWriter, + ".log": CanutilsLogWriter, + ".txt": Printer, + } + + def __init__( + self, + filename_func: Callable[[int], can.typechecking.StringPathLike], + max_bytes: int, + initial_file_number: int = 0, + *args, + **kwargs, + ): + """ + :param filename_func: + A function or lambda expression that returns the path of the new + log file e.g. `lambda file_number: f"C:\\my_can_logfile_{file_number:03}.asc"`. + :param max_bytes: + The file size threshold in bytes at which a new file is created. + :param initial_file_number: + The first log file will start with number `initial_file_number`. + :raises ValueError: + The filename's suffix is not supported. + """ + self.filename_func = filename_func + self.max_bytes = max_bytes + self.file_number = initial_file_number + + self.writer_args = args + self.writer_kwargs = kwargs + + self.writer: MessageWriter = self._get_new_writer() + + def on_message_received(self, msg: Message): + if self.writer.file is not None and self.writer.file.tell() >= self.max_bytes: + self.writer.stop() + self.file_number += 1 + self.writer = self._get_new_writer() + + self.writer.on_message_received(msg) + + def on_error(self, exc: Exception): + self.writer.on_error(exc) + + def stop(self): + self.writer.stop() + + def _get_new_writer(self) -> MessageWriter: + filename = self.filename_func(self.file_number) + suffix = pathlib.Path(filename).suffix.lower() + try: + writer_class = self.supported_writers[suffix] + except KeyError: + raise ValueError( + f'Log format "{suffix} is not supported by RotatingFileLogger."' + ) + return writer_class(filename, *self.writer_args, **self.writer_kwargs) diff --git a/doc/listeners.rst b/doc/listeners.rst index 8e2a79a8b..7243e7bba 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -75,6 +75,9 @@ create log files with different file types of the messages received. .. autoclass:: can.Logger :members: +.. autoclass:: can.RotatingFileLogger + :members: + Printer ------- From 1a6ecfc4dad68bde41e9f32afc3fb1c544b7a4f3 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Tue, 4 Aug 2020 03:37:37 -0400 Subject: [PATCH 0447/1235] Adds CANtact interface (#853) --- can/interfaces/__init__.py | 1 + can/interfaces/cantact.py | 152 +++++++++++++++++++++++++++++++++++++ doc/installation.rst | 14 ++++ setup.py | 1 + test/simplecyclic_test.py | 1 + test/test_cantact.py | 64 ++++++++++++++++ 6 files changed, 233 insertions(+) create mode 100644 can/interfaces/cantact.py create mode 100644 test/test_cantact.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 2f00d0309..1fa3f5b4b 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -24,6 +24,7 @@ "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), "systec": ("can.interfaces.systec", "UcanBus"), "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), + "cantact": ("can.interfaces.cantact", "CantactBus"), } BACKENDS.update( diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py new file mode 100644 index 000000000..5d71cf3ad --- /dev/null +++ b/can/interfaces/cantact.py @@ -0,0 +1,152 @@ +""" +Interface for CANtact devices from Linklayer Labs +""" + +import time +import logging +from unittest.mock import Mock + +from can import BusABC, Message + +logger = logging.getLogger(__name__) + +try: + import cantact +except ImportError: + logger.warning( + "The CANtact module is not installed. Install it using `python3 -m pip install cantact`" + ) + + +class CantactBus(BusABC): + """CANtact interface""" + + @staticmethod + def _detect_available_configs(): + try: + interface = cantact.Interface() + except NameError: + # couldn't import cantact, so no configurations are available + return [] + + channels = [] + for i in range(0, interface.channel_count()): + channels.append({"interface": "cantact", "channel": "ch:%d" % i}) + return channels + + def __init__( + self, + channel, + bitrate=500000, + poll_interval=0.01, + monitor=False, + bit_timing=None, + _testing=False, + **kwargs + ): + """ + :param int channel: + Channel number (zero indexed, labeled on multi-channel devices) + :param int bitrate: + Bitrate in bits/s + :param bool monitor: + If true, operate in listen-only monitoring mode + :param BitTiming bit_timing + Optional BitTiming to use for custom bit timing setting. Overrides bitrate if not None. + """ + + if _testing: + self.interface = MockInterface() + else: + self.interface = cantact.Interface() + + self.channel = int(channel) + self.channel_info = "CANtact: ch:%s" % channel + + # configure the interface + if bit_timing is None: + # use bitrate + self.interface.set_bitrate(int(channel), int(bitrate)) + else: + # use custom bit timing + self.interface.set_bit_timing( + int(channel), + int(bit_timing.brp), + int(bit_timing.tseg1), + int(bit_timing.tseg2), + int(bit_timing.sjw), + ) + self.interface.set_enabled(int(channel), True) + self.interface.set_monitor(int(channel), monitor) + self.interface.start() + + super().__init__( + channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs + ) + + def _recv_internal(self, timeout): + frame = self.interface.recv(int(timeout * 1000)) + if frame is None: + # timeout occured + return None, False + + msg = Message( + arbitration_id=frame["id"], + is_extended_id=frame["extended"], + timestamp=frame["timestamp"], + is_remote_frame=frame["rtr"], + dlc=frame["dlc"], + data=frame["data"][: frame["dlc"]], + channel=frame["channel"], + is_rx=(not frame["loopback"]), # received if not loopback frame + ) + return msg, False + + def send(self, msg, timeout=None): + self.interface.send( + self.channel, + msg.arbitration_id, + bool(msg.is_extended_id), + bool(msg.is_remote_frame), + msg.dlc, + msg.data, + ) + + def shutdown(self): + self.interface.stop() + + +def mock_recv(timeout): + if timeout > 0: + frame = {} + frame["id"] = 0x123 + frame["extended"] = False + frame["timestamp"] = time.time() + frame["loopback"] = False + frame["rtr"] = False + frame["dlc"] = 8 + frame["data"] = [1, 2, 3, 4, 5, 6, 7, 8] + frame["channel"] = 0 + return frame + else: + # simulate timeout when timeout = 0 + return None + + +class MockInterface: + """ + Mock interface to replace real interface when testing. + This allows for tests to run without actual hardware. + """ + + start = Mock() + set_bitrate = Mock() + set_bit_timing = Mock() + set_enabled = Mock() + set_monitor = Mock() + start = Mock() + stop = Mock() + send = Mock() + channel_count = Mock(return_value=1) + + recv = Mock(side_effect=mock_recv) diff --git a/doc/installation.rst b/doc/installation.rst index a70f7d5ea..8533b6289 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -90,6 +90,20 @@ To install ``python-can`` using the XL Driver Library as the backend: 3. Use Vector Hardware Configuration to assign a channel to your application. +CANtact +~~~~~~~ + +CANtact is supported on Linux, Windows, and macOS. +To install ``python-can`` using the CANtact driver backend: + +``python3 -m pip install "python-can[cantact]"`` + +If ``python-can`` is already installed, the CANtact backend can be installed seperately: + +``python3 -m pip install cantact`` + +Additional CANtact documentation is available at https://cantact.io. + Installing python-can in development mode ----------------------------------------- diff --git a/setup.py b/setup.py index ab6194207..15ff3458a 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ "seeedstudio": ["pyserial>=3.0"], "serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12"], + "cantact": ["cantact>=0.0.7"], } setup( diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 83a79de8c..82f0850a7 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -152,6 +152,7 @@ def test_stopping_perodic_tasks(self): bus.shutdown() + @unittest.skipIf(IS_CI, "fails randomly when run on CI server") def test_thread_based_cyclic_send_task(self): bus = can.ThreadSafeBus(bustype="virtual") msg = can.Message( diff --git a/test/test_cantact.py b/test/test_cantact.py new file mode 100644 index 000000000..dc3b6a385 --- /dev/null +++ b/test/test_cantact.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +Tests for CANtact interfaces +""" + +import time +import logging +import unittest +from unittest.mock import Mock, patch + +import pytest + +import can +from can.interfaces import cantact + + +class CantactTest(unittest.TestCase): + def test_bus_creation(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + self.assertIsInstance(bus, cantact.CantactBus) + cantact.MockInterface.set_bitrate.assert_called() + cantact.MockInterface.set_bit_timing.assert_not_called() + cantact.MockInterface.set_enabled.assert_called() + cantact.MockInterface.set_monitor.assert_called() + cantact.MockInterface.start.assert_called() + + def test_bus_creation_bittiming(self): + cantact.MockInterface.set_bitrate.reset_mock() + + bt = can.BitTiming(tseg1=13, tseg2=2, brp=6, sjw=1) + bus = can.Bus(channel=0, bustype="cantact", bit_timing=bt, _testing=True) + self.assertIsInstance(bus, cantact.CantactBus) + cantact.MockInterface.set_bitrate.assert_not_called() + cantact.MockInterface.set_bit_timing.assert_called() + cantact.MockInterface.set_enabled.assert_called() + cantact.MockInterface.set_monitor.assert_called() + cantact.MockInterface.start.assert_called() + + def test_transmit(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + bus.send(msg) + cantact.MockInterface.send.assert_called() + + def test_recv(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + frame = bus.recv(timeout=0.5) + cantact.MockInterface.recv.assert_called() + self.assertIsInstance(frame, can.Message) + + def test_recv_timeout(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + frame = bus.recv(timeout=0.0) + cantact.MockInterface.recv.assert_called() + self.assertIsNone(frame) + + def test_shutdown(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + bus.shutdown() + cantact.MockInterface.stop.assert_called() From 98fda97a37829b424170b8815d7f8de21741f195 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 4 Aug 2020 19:57:15 +1200 Subject: [PATCH 0448/1235] Update installation docs for IXXAT Updating after @johanbrus successful test with the V4 drivers on Win10 --- doc/installation.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/installation.rst b/doc/installation.rst index 8533b6289..e97427cf2 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -57,10 +57,10 @@ the CPU intensive polling that will otherwise have be used. IXXAT ~~~~~ -To install ``python-can`` using the IXXAT VCI V3 SDK as the backend: +To install ``python-can`` using the IXXAT VCI V3 or V4 SDK as the backend: -1. Install `IXXAT's latest Windows VCI V3 SDK - drivers `__. +1. Install `IXXAT's latest Windows VCI V3 SDK or VCI V4 SDK (Win10) + drivers `__. 2. Test that IXXAT's own tools (i.e. MiniMon) work to ensure the driver is properly installed and that the hardware is working. From 2fa99c398cab575c93d7f2e0726724180288eb7d Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 4 Aug 2020 20:14:37 +1200 Subject: [PATCH 0449/1235] Update developer docs --- doc/development.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/development.rst b/doc/development.rst index 6b9aa529e..3bad8fc9b 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -8,6 +8,9 @@ Contributing Contribute to source code, documentation, examples and report issues: https://github.com/hardbyte/python-can +Note that the latest released version on PyPi may be significantly behind the +``develop`` branch. Please open any feature requests against the ``develop`` branch + There is also a `python-can `__ mailing list for development discussion. @@ -16,6 +19,14 @@ in the chapter :ref:`internalapi`. There is also additional information on extending the ``can.io`` module. +Pre-releases +------------ + +The latest pre-release can be installed with:: + + pip install --upgrade --pre python-can + + Building & Installing --------------------- @@ -90,7 +101,7 @@ The modules in ``python-can`` are: Creating a new Release ---------------------- -- Release from the ``master`` branch. +- Release from the ``master`` branch (except for pre-releases). - Update the library version in ``__init__.py`` using `semantic versioning `__. - Check if any deprecations are pending. - Run all tests and examples against available hardware. From ac6e3ef8c6722fa19b48fc35b6ba0cca8c8b5303 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 4 Aug 2020 21:47:06 +1200 Subject: [PATCH 0450/1235] Add a default fileno function to the BusABC class. (#877) Modify Notifier to handle the exception raised if the bus doesn't override it. --- can/bus.py | 5 ++++- can/notifier.py | 25 ++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/can/bus.py b/can/bus.py index 90a3cd906..362eefd72 100644 --- a/can/bus.py +++ b/can/bus.py @@ -199,7 +199,7 @@ def send_periodic( Disable to instead manage tasks manually. :return: A started task instance. Note the task can be stopped (and depending on - the backend modified) by calling the :meth:`stop` method. + the backend modified) by calling the task's :meth:`stop` method. .. note:: @@ -430,3 +430,6 @@ def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: for usage in the interface's bus constructor. """ raise NotImplementedError() + + def fileno(self) -> int: + raise NotImplementedError("fileno is not implemented using current CAN bus") diff --git a/can/notifier.py b/can/notifier.py index de2894f64..eae7363ab 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -61,23 +61,26 @@ def add_bus(self, bus: BusABC): :param bus: CAN bus instance. """ - 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() # type: ignore + reader: int = -1 + try: + reader = bus.fileno() + except NotImplementedError: + # Bus doesn't support fileno, we fall back to thread based reader + pass + + if self._loop is not None and reader >= 0: + # Use bus file descriptor to watch for messages self._loop.add_reader(reader, self._on_message_available, bus) + self._readers.append(reader) else: - reader = threading.Thread( + reader_thread = threading.Thread( target=self._rx_thread, args=(bus,), name='can.notifier for bus "{}"'.format(bus.channel_info), ) - reader.daemon = True - reader.start() - self._readers.append(reader) + reader_thread.daemon = True + reader_thread.start() + self._readers.append(reader_thread) def stop(self, timeout: float = 5): """Stop notifying Listeners when new :class:`~can.Message` objects arrive From 511dcfd9df9721ae6ddbbb1c072f98168baf21c5 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Fri, 14 Aug 2020 07:53:25 -0400 Subject: [PATCH 0451/1235] Find the correct Reader/Writer independently of the file extension case --- can/io/logger.py | 2 +- can/io/player.py | 2 +- test/logformats_test.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index a4544b87d..9a9b851d8 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -71,7 +71,7 @@ def __new__( ) Logger.fetched_plugins = True - suffix = pathlib.PurePath(filename).suffix + suffix = pathlib.PurePath(filename).suffix.lower() try: return Logger.message_writers[suffix](filename, *args, **kwargs) except KeyError: diff --git a/can/io/player.py b/can/io/player.py index 88d3497fa..21e716250 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -70,7 +70,7 @@ def __new__(cls, filename: "can.typechecking.StringPathLike", *args, **kwargs): ) LogReader.fetched_plugins = True - suffix = pathlib.PurePath(filename).suffix + suffix = pathlib.PurePath(filename).suffix.lower() try: return LogReader.message_readers[suffix](filename, *args, **kwargs) except KeyError: diff --git a/test/logformats_test.py b/test/logformats_test.py index 08a9e929d..fbca43306 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -36,6 +36,36 @@ logging.basicConfig(level=logging.DEBUG) +class ReaderWriterExtensionTest(unittest.TestCase): + message_writers_and_readers = {} + for suffix, writer in can.Logger.message_writers.items(): + message_writers_and_readers[suffix] = ( + writer, + can.LogReader.message_readers.get(suffix), + ) + + def test_extension_matching(self): + for suffix, (writer, reader) in self.message_writers_and_readers.items(): + suffix_variants = [ + suffix.upper(), + suffix.lower(), + "".join([c.upper() if i % 2 else c for i, c in enumerate(suffix)]), + ] + for suffix_variant in suffix_variants: + tmp_file = tempfile.NamedTemporaryFile( + suffix=suffix_variant, delete=False + ) + tmp_file.close() + try: + with can.Logger(tmp_file.name) as logger: + assert type(logger) == writer + if reader is not None: + with can.LogReader(tmp_file.name) as player: + assert type(player) == reader + finally: + os.remove(tmp_file.name) + + class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase, metaclass=ABCMeta): """Tests a pair of writer and reader by writing all data first and then reading all data and checking if they could be reconstructed From 5cfe5051f781b7fd3eaf2ef3589d53915d9ded16 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 18 Aug 2020 21:29:05 +0200 Subject: [PATCH 0452/1235] implement BaseRotatingCanLogger and SizedRotatingCanLogger --- can/__init__.py | 2 +- can/io/__init__.py | 2 +- can/io/generic.py | 8 +- can/io/logger.py | 266 +++++++++++++++++++++++++++++++++------------ doc/listeners.rst | 5 +- 5 files changed, 210 insertions(+), 73 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 55fca0c4a..c48931fc2 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -24,7 +24,7 @@ class CanError(IOError): from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader -from .io import Logger, RotatingFileLogger, Printer, LogReader, MessageSync +from .io import Logger, SizedRotatingCanLogger, Printer, LogReader, MessageSync from .io import ASCWriter, ASCReader from .io import BLFReader, BLFWriter from .io import CanutilsLogReader, CanutilsLogWriter diff --git a/can/io/__init__.py b/can/io/__init__.py index 4ecd7a3e5..a4b4ea231 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -4,7 +4,7 @@ """ # Generic -from .logger import Logger, RotatingFileLogger +from .logger import Logger, BaseRotatingCanLogger, SizedRotatingCanLogger from .player import LogReader, MessageSync # Format specific diff --git a/can/io/generic.py b/can/io/generic.py index eefa3d028..9b15068a4 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -3,7 +3,7 @@ """ from abc import ABCMeta -from typing import Optional, cast +from typing import Optional, cast, Union, TextIO, BinaryIO import can import can.typechecking @@ -54,6 +54,12 @@ class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): """The base class for all writers.""" +# pylint: disable=abstract-method,too-few-public-methods +class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): + """The base class for all writers.""" + file: Union[TextIO, BinaryIO] + + # 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 c8b48fb13..65677c49f 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -1,16 +1,18 @@ """ See the :class:`Logger` class. """ - +import os import pathlib +from abc import ABC, abstractmethod +from datetime import datetime from typing import Optional, Callable from pkg_resources import iter_entry_points -import can.typechecking +from can.typechecking import StringPathLike from ..message import Message from ..listener import Listener -from .generic import BaseIOHandler, MessageWriter +from .generic import BaseIOHandler, FileIOMessageWriter from .asc import ASCWriter from .blf import BLFWriter from .canutils import CanutilsLogWriter @@ -51,9 +53,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method } @staticmethod - def __new__( - cls, filename: Optional[can.typechecking.StringPathLike], *args, **kwargs - ): + def __new__(cls, filename: Optional[StringPathLike], *args, **kwargs): """ :param filename: the filename/path of the file to write to, may be a path-like object or None to @@ -81,36 +81,32 @@ def __new__( ) from None -class RotatingFileLogger(Listener): - """Log CAN messages to a sequence of files with a given maximum size. - - The log file name and path must be returned by the function`filename_func` - as a path-like object (e.g. a string). The log file format is defined by - the suffix of the path-like object. - - The RotatingFileLogger currently supports - * .asc: :class:`can.ASCWriter` - * .blf :class:`can.BLFWriter` - * .csv: :class:`can.CSVWriter` - * .log :class:`can.CanutilsLogWriter` - * .txt :class:`can.Printer` - - The log files may be incomplete until `stop()` is called due to buffering. - - Example:: +class BaseRotatingCanLogger(Listener, ABC): + """ + Base class for rotating CAN loggers. This class is not meant to be + instantiated directly. Subclasses must implement the `should_rollover` + and `do_rollover` methods according to their rotation strategy. - from can import Notifier - from can.interfaces.vector import VectorBus - from can import RotatingFileLogger + The rotation behavior can be further customized by the user by setting + the `namer` and `rotator´ attributes after instantiating the subclass. - bus = VectorBus(channel=[0], app_name="CANape", fd=True) - logger = RotatingFileLogger( - filename_func=lambda idx: f"CAN_Log_{idx:03}.txt", # filename with three digit counter - max_bytes=5 * 1024 ** 2, # =5MB - initial_file_number=23, # start with number 23 - ) - notifier = Notifier(bus=bus, listeners=[logger]) + These attributes as well as the methods `rotation_filename` and `rotate` + and the corresponding docstrings are carried over from the python builtin + `BaseRotatingHandler`. + :attr Optional[Callable] namer: + If this attribute is set to a callable, the rotation_filename() method + delegates to this callable. The parameters passed to the callable are + those passed to rotation_filename(). + :attr Optional[Callable] rotator: + If this attribute is set to a callable, the rotate() method delegates + to this callable. The parameters passed to the callable are those + passed to rotate(). + :attr int rollover_count: + An integer counter to track the number of rollovers. + :attr FileIOMessageWriter writer: + This attribute holds an instance of a writer class which manages the + actual file IO. """ supported_writers = { @@ -120,56 +116,188 @@ class RotatingFileLogger(Listener): ".log": CanutilsLogWriter, ".txt": Printer, } + namer: Optional[Callable] = None + rotator: Optional[Callable] = None + rollover_count: int = 0 + writer: FileIOMessageWriter - def __init__( - self, - filename_func: Callable[[int], can.typechecking.StringPathLike], - max_bytes: int, - initial_file_number: int = 0, - *args, - **kwargs, - ): - """ - :param filename_func: - A function or lambda expression that returns the path of the new - log file e.g. `lambda file_number: f"C:\\my_can_logfile_{file_number:03}.asc"`. - :param max_bytes: - The file size threshold in bytes at which a new file is created. - :param initial_file_number: - The first log file will start with number `initial_file_number`. - :raises ValueError: - The filename's suffix is not supported. - """ - self.filename_func = filename_func - self.max_bytes = max_bytes - self.file_number = initial_file_number - + def __init__(self, *args, **kwargs): self.writer_args = args self.writer_kwargs = kwargs - self.writer: MessageWriter = self._get_new_writer() + def rotation_filename(self, default_name: StringPathLike): + """Modify the filename of a log file when rotating. + + This is provided so that a custom filename can be provided. + The default implementation calls the 'namer' attribute of the + handler, if it's callable, passing the default name to + it. If the attribute isn't callable (the default is None), the name + is returned unchanged. + + :param default_name: + The default name for the log file. + """ + if not callable(self.namer): + result = default_name + else: + result = self.namer(default_name) + return result + + def rotate(self, source: StringPathLike, dest: StringPathLike): + """When rotating, rotate the current log. + + The default implementation calls the 'rotator' attribute of the + handler, if it's callable, passing the source and dest arguments to + it. If the attribute isn't callable (the default is None), the source + is simply renamed to the destination. + + :param source: + The source filename. This is normally the base + filename, e.g. 'test.log' + :param dest: + The destination filename. This is normally + what the source is rotated to, e.g. 'test_#001.log'. + """ + if not callable(self.rotator): + if os.path.exists(source): + os.rename(source, dest) + else: + self.rotator(source, dest) def on_message_received(self, msg: Message): - if self.writer.file is not None and self.writer.file.tell() >= self.max_bytes: - self.writer.stop() - self.file_number += 1 - self.writer = self._get_new_writer() + """This method is called to handle the given message. - self.writer.on_message_received(msg) + :param msg: + the delivered message + """ + if self.should_rollover(msg): + self.do_rollover() + self.rollover_count += 1 - def on_error(self, exc: Exception): - self.writer.on_error(exc) + self.writer.on_message_received(msg) - def stop(self): - self.writer.stop() + def get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: + """Instantiate a new writer. - def _get_new_writer(self) -> MessageWriter: - filename = self.filename_func(self.file_number) + :param filename: + Path-like object that specifies the location and name of the log file. + The log file format is defined by the suffix of `filename`. + :return: + An instance of a writer class. + """ suffix = pathlib.Path(filename).suffix.lower() try: writer_class = self.supported_writers[suffix] except KeyError: raise ValueError( - f'Log format "{suffix} is not supported by RotatingFileLogger."' + f'Log format with suffix"{suffix}" is ' + f"not supported by {self.__class__.__name__}." ) return writer_class(filename, *self.writer_args, **self.writer_kwargs) + + def stop(self): + """Stop handling new messages. + + Carry out any final tasks to ensure + data is persisted and cleanup any open resources. + """ + self.writer.stop() + + @abstractmethod + def should_rollover(self, msg: Message) -> bool: + """Determine if the rollover conditions are met.""" + ... + + @abstractmethod + def do_rollover(self): + """Perform rollover.""" + ... + + +class SizedRotatingCanLogger(BaseRotatingCanLogger): + """Log CAN messages to a sequence of files with a given maximum size. + + The logger creates a log file with the given `base_filename`. When the + size threshold is reached the current log file is closed and renamed + by adding a timestamp and the rollover count. A new log file is then + created and written to. + + This behavior can be customized by setting the ´namer´ and `rotator` + attribute. + + Example:: + + from can import Notifier, RotatingFileLogger + from can.interfaces.vector import VectorBus + + bus = VectorBus(channel=[0], app_name="CANape", fd=True) + + logger = SizedRotatingCanLogger( + base_filename="my_logfile.asc", + max_bytes=5 * 1024 ** 2, # =5MB + ) + logger.rollover_count = 23 # start counter at 23 + + notifier = Notifier(bus=bus, listeners=[logger]) + + The SizedRotatingCanLogger currently supports the formats + * .asc: :class:`can.ASCWriter` + * .blf :class:`can.BLFWriter` + * .csv: :class:`can.CSVWriter` + * .log :class:`can.CanutilsLogWriter` + * .txt :class:`can.Printer` + + The log files may be incomplete until `stop()` is called due to buffering. + + """ + + def __init__( + self, base_filename: StringPathLike, max_bytes: int = 0, *args, **kwargs + ): + """ + :param base_filename: + A path-like object for the base filename. The log file format is defined by + the suffix of `base_filename`. + :param max_bytes: + The size threshold at which a new log file shall be created. If set to 0, no + rollover will be performed. + + """ + super(SizedRotatingCanLogger, self).__init__(*args, **kwargs) + + self.base_filename = os.path.abspath(base_filename) + self.max_bytes = max_bytes + + self.writer = self.get_new_writer(self.base_filename) + + def should_rollover(self, msg: Message) -> bool: + if self.max_bytes == 0: + return False + + if self.writer.file.tell() >= self.max_bytes: + return True + + return False + + def do_rollover(self): + if self.writer: + self.writer.stop() + + sfn = self.base_filename + dfn = self.rotation_filename(self._default_name()) + self.rotate(sfn, dfn) + + self.writer = self.get_new_writer(self.base_filename) + + def _default_name(self) -> StringPathLike: + """Generate the default rotation filename.""" + path = pathlib.Path(self.base_filename) + new_name = ( + path.stem + + "_" + + datetime.now().strftime("%Y-%m-%dT%H%M%S") + + "_" + + f"#{self.rollover_count:03}" + + path.suffix + ) + return str(path.parent / new_name) diff --git a/doc/listeners.rst b/doc/listeners.rst index 7243e7bba..cf9080355 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -75,7 +75,10 @@ create log files with different file types of the messages received. .. autoclass:: can.Logger :members: -.. autoclass:: can.RotatingFileLogger +.. autoclass:: can.io.BaseRotatingCanLogger + :members: + +.. autoclass:: can.SizedRotatingCanLogger :members: From 88a65b5e62d6dd28f506904ddb9a8bfa645886a2 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 24 Aug 2020 21:16:44 +0200 Subject: [PATCH 0453/1235] add --file_size argument to can logger script --- can/io/generic.py | 1 + can/logger.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/can/io/generic.py b/can/io/generic.py index 9b15068a4..2a45e0983 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -57,6 +57,7 @@ class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): # pylint: disable=abstract-method,too-few-public-methods class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): """The base class for all writers.""" + file: Union[TextIO, BinaryIO] diff --git a/can/logger.py b/can/logger.py index 8a3a214e6..8ca44d81f 100644 --- a/can/logger.py +++ b/can/logger.py @@ -20,7 +20,7 @@ from datetime import datetime import can -from can import Bus, BusState, Logger +from can import Bus, BusState, Logger, SizedRotatingCanLogger def main(): @@ -37,6 +37,15 @@ def main(): default=None, ) + parser.add_argument( + "-s", + "--file_size", + dest="file_size", + type=int, + help="""Maximum file size in bytes. Rotate log file when size threshold is reached.""", + default=None, + ) + parser.add_argument( "-v", action="count", @@ -142,7 +151,11 @@ def main(): print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") print(f"Can Logger (Started on {datetime.now()})") - logger = Logger(results.log_file) + + if results.file_size: + logger = SizedRotatingCanLogger(base_filename=results.log_file, max_bytes=results.file_size) + else: + logger = Logger(filename=results.log_file) try: while True: From 5ba2a93159723656c76674ec0ae9f634e036f688 Mon Sep 17 00:00:00 2001 From: karl ding Date: Fri, 28 Aug 2020 03:06:59 -0700 Subject: [PATCH 0454/1235] Add Message pickle serialization test (#904) Add a test that exercises pickling/unpickling of a Message. Addresses #804 --- test/test_message_class.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/test_message_class.py b/test/test_message_class.py index 760a848bd..7156268a5 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -5,12 +5,15 @@ import sys from math import isinf, isnan from copy import copy, deepcopy +import pickle from hypothesis import given, settings, reproduce_failure import hypothesis.strategies as st from can import Message +from .message_helper import ComparingMessagesTestCase + class TestMessageClass(unittest.TestCase): """ @@ -85,5 +88,31 @@ def test_methods(self, **kwargs): self.assertTrue(message.equals(other, timestamp_delta=0)) +class MessageSerialization(unittest.TestCase, ComparingMessagesTestCase): + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=0.016, preserves_channel=True + ) + + def test_serialization(self): + message = Message( + timestamp=1.0, + arbitration_id=0x401, + is_extended_id=False, + is_remote_frame=False, + is_error_frame=False, + channel=1, + dlc=6, + data=bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]), + is_fd=False, + ) + + serialized = pickle.dumps(message, -1) + deserialized = pickle.loads(serialized) + + self.assertMessageEqual(message, deserialized) + + if __name__ == "__main__": unittest.main() From 9a3a41eadf46679d33693467651bfec0b293f010 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 31 Aug 2020 01:10:09 +0200 Subject: [PATCH 0455/1235] implement tests for rotating loggers --- can/io/logger.py | 26 ++-- can/logger.py | 4 +- test/test_rotating_loggers.py | 268 ++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+), 10 deletions(-) create mode 100644 test/test_rotating_loggers.py diff --git a/can/io/logger.py b/can/io/logger.py index 65677c49f..bc8a702db 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -119,12 +119,19 @@ class BaseRotatingCanLogger(Listener, ABC): namer: Optional[Callable] = None rotator: Optional[Callable] = None rollover_count: int = 0 - writer: FileIOMessageWriter + _writer: Optional[FileIOMessageWriter] = None def __init__(self, *args, **kwargs): self.writer_args = args self.writer_kwargs = kwargs + @property + def writer(self) -> FileIOMessageWriter: + if not self._writer: + raise ValueError("Attempt to access writer failed.") + + return self._writer + def rotation_filename(self, default_name: StringPathLike): """Modify the filename of a log file when rotating. @@ -176,7 +183,7 @@ def on_message_received(self, msg: Message): self.writer.on_message_received(msg) - def get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: + def get_new_writer(self, filename: StringPathLike): """Instantiate a new writer. :param filename: @@ -193,7 +200,10 @@ def get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: f'Log format with suffix"{suffix}" is ' f"not supported by {self.__class__.__name__}." ) - return writer_class(filename, *self.writer_args, **self.writer_kwargs) + else: + self._writer = writer_class( + filename, *self.writer_args, **self.writer_kwargs + ) def stop(self): """Stop handling new messages. @@ -227,7 +237,7 @@ class SizedRotatingCanLogger(BaseRotatingCanLogger): Example:: - from can import Notifier, RotatingFileLogger + from can import Notifier, SizedRotatingCanLogger from can.interfaces.vector import VectorBus bus = VectorBus(channel=[0], app_name="CANape", fd=True) @@ -248,7 +258,6 @@ class SizedRotatingCanLogger(BaseRotatingCanLogger): * .txt :class:`can.Printer` The log files may be incomplete until `stop()` is called due to buffering. - """ def __init__( @@ -261,17 +270,16 @@ def __init__( :param max_bytes: The size threshold at which a new log file shall be created. If set to 0, no rollover will be performed. - """ super(SizedRotatingCanLogger, self).__init__(*args, **kwargs) self.base_filename = os.path.abspath(base_filename) self.max_bytes = max_bytes - self.writer = self.get_new_writer(self.base_filename) + self.get_new_writer(self.base_filename) def should_rollover(self, msg: Message) -> bool: - if self.max_bytes == 0: + if self.max_bytes <= 0: return False if self.writer.file.tell() >= self.max_bytes: @@ -287,7 +295,7 @@ def do_rollover(self): dfn = self.rotation_filename(self._default_name()) self.rotate(sfn, dfn) - self.writer = self.get_new_writer(self.base_filename) + self.get_new_writer(self.base_filename) def _default_name(self) -> StringPathLike: """Generate the default rotation filename.""" diff --git a/can/logger.py b/can/logger.py index 8ca44d81f..04f65244d 100644 --- a/can/logger.py +++ b/can/logger.py @@ -153,7 +153,9 @@ def main(): print(f"Can Logger (Started on {datetime.now()})") if results.file_size: - logger = SizedRotatingCanLogger(base_filename=results.log_file, max_bytes=results.file_size) + logger = SizedRotatingCanLogger( + base_filename=results.log_file, max_bytes=results.file_size + ) else: logger = Logger(filename=results.log_file) diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py new file mode 100644 index 000000000..0cda47cdd --- /dev/null +++ b/test/test_rotating_loggers.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +Test rotating loggers +""" +import os +from pathlib import Path +import tempfile +from unittest.mock import Mock + +import pytest + +import can +from .data.example_data import generate_message + + +class TestBaseRotatingCanLogger: + @staticmethod + def _get_instance(*args, **kwargs): + class SubClass(can.io.BaseRotatingCanLogger): + """Subclass that implements abstract methods for testing.""" + + def should_rollover(self, msg): + ... + + def do_rollover(self): + ... + + return SubClass(*args, **kwargs) + + def test_import(self): + assert hasattr(can.io, "BaseRotatingCanLogger") + + def test_attributes(self): + assert issubclass(can.io.BaseRotatingCanLogger, can.Listener) + assert hasattr(can.io.BaseRotatingCanLogger, "supported_writers") + assert hasattr(can.io.BaseRotatingCanLogger, "namer") + assert hasattr(can.io.BaseRotatingCanLogger, "rotator") + assert hasattr(can.io.BaseRotatingCanLogger, "rollover_count") + assert hasattr(can.io.BaseRotatingCanLogger, "writer") + assert hasattr(can.io.BaseRotatingCanLogger, "rotation_filename") + assert hasattr(can.io.BaseRotatingCanLogger, "rotate") + assert hasattr(can.io.BaseRotatingCanLogger, "on_message_received") + assert hasattr(can.io.BaseRotatingCanLogger, "get_new_writer") + assert hasattr(can.io.BaseRotatingCanLogger, "stop") + assert hasattr(can.io.BaseRotatingCanLogger, "should_rollover") + assert hasattr(can.io.BaseRotatingCanLogger, "do_rollover") + + def test_supported_writers(self): + supported_writers = can.io.BaseRotatingCanLogger.supported_writers + assert supported_writers[".asc"] == can.ASCWriter + assert supported_writers[".blf"] == can.BLFWriter + assert supported_writers[".csv"] == can.CSVWriter + assert supported_writers[".log"] == can.CanutilsLogWriter + assert supported_writers[".txt"] == can.Printer + + def test_get_new_writer(self): + logger_instance = self._get_instance() + + # access to non existing writer shall raise ValueError + with pytest.raises(ValueError): + _ = logger_instance.writer + + with tempfile.TemporaryDirectory() as temp_dir: + logger_instance.get_new_writer(os.path.join(temp_dir, "file.ASC")) + assert isinstance(logger_instance.writer, can.ASCWriter) + logger_instance.stop() + + logger_instance.get_new_writer(os.path.join(temp_dir, "file.BLF")) + assert isinstance(logger_instance.writer, can.BLFWriter) + logger_instance.stop() + + logger_instance.get_new_writer(os.path.join(temp_dir, "file.CSV")) + assert isinstance(logger_instance.writer, can.CSVWriter) + logger_instance.stop() + + logger_instance.get_new_writer(os.path.join(temp_dir, "file.LOG")) + assert isinstance(logger_instance.writer, can.CanutilsLogWriter) + logger_instance.stop() + + logger_instance.get_new_writer(os.path.join(temp_dir, "file.TXT")) + assert isinstance(logger_instance.writer, can.Printer) + logger_instance.stop() + + def test_rotation_filename(self): + logger_instance = self._get_instance() + + default_name = "default" + assert logger_instance.rotation_filename(default_name) == "default" + + logger_instance.namer = lambda x: x + "_by_namer" + assert logger_instance.rotation_filename(default_name) == "default_by_namer" + + def test_rotate(self): + logger_instance = self._get_instance() + + # test without rotator + with tempfile.TemporaryDirectory() as temp_dir: + source = os.path.join(temp_dir, "source.txt") + dest = os.path.join(temp_dir, "dest.txt") + + assert os.path.exists(source) is False + assert os.path.exists(dest) is False + + logger_instance.get_new_writer(source) + logger_instance.stop() + + assert os.path.exists(source) is True + assert os.path.exists(dest) is False + + logger_instance.rotate(source, dest) + + assert os.path.exists(source) is False + assert os.path.exists(dest) is True + + # test with rotator + rotator_func = Mock() + logger_instance.rotator = rotator_func + with tempfile.TemporaryDirectory() as temp_dir: + source = os.path.join(temp_dir, "source.txt") + dest = os.path.join(temp_dir, "dest.txt") + + assert os.path.exists(source) is False + assert os.path.exists(dest) is False + + logger_instance.get_new_writer(source) + logger_instance.stop() + + assert os.path.exists(source) is True + assert os.path.exists(dest) is False + + logger_instance.rotate(source, dest) + rotator_func.assert_called_with(source, dest) + + # assert that no rotation was performed since rotator_func + # does not do anything + assert os.path.exists(source) is True + assert os.path.exists(dest) is False + + def test_stop(self): + """Test if stop() method of writer is called.""" + logger_instance = self._get_instance() + + with tempfile.TemporaryDirectory() as temp_dir: + logger_instance.get_new_writer(os.path.join(temp_dir, "file.ASC")) + + # replace stop method of writer with Mock + org_stop = logger_instance.writer.stop + mock_stop = Mock() + logger_instance.writer.stop = mock_stop + + logger_instance.stop() + mock_stop.assert_called() + + # close file.ASC to enable cleanup of temp_dir + org_stop() + + def test_on_message_received(self): + logger_instance = self._get_instance() + + with tempfile.TemporaryDirectory() as temp_dir: + logger_instance.get_new_writer(os.path.join(temp_dir, "file.ASC")) + + """Test without rollover""" + should_rollover = Mock(return_value=False) + do_rollover = Mock() + writers_on_message_received = Mock() + + logger_instance.should_rollover = should_rollover + logger_instance.do_rollover = do_rollover + logger_instance.writer.on_message_received = writers_on_message_received + + msg = generate_message(0x123) + logger_instance.on_message_received(msg) + + should_rollover.assert_called_with(msg) + do_rollover.assert_not_called() + writers_on_message_received.assert_called_with(msg) + + """Test with rollover""" + should_rollover = Mock(return_value=True) + do_rollover = Mock() + writers_on_message_received = Mock() + + logger_instance.should_rollover = should_rollover + logger_instance.do_rollover = do_rollover + logger_instance.writer.on_message_received = writers_on_message_received + + msg = generate_message(0x123) + logger_instance.on_message_received(msg) + + should_rollover.assert_called_with(msg) + do_rollover.assert_called() + writers_on_message_received.assert_called_with(msg) + + # stop writer to enable cleanup of temp_dir + logger_instance.stop() + + +class TestSizedRotatingCanLogger: + def test_import(self): + assert hasattr(can.io, "SizedRotatingCanLogger") + assert hasattr(can, "SizedRotatingCanLogger") + + def test_attributes(self): + assert issubclass(can.SizedRotatingCanLogger, can.io.BaseRotatingCanLogger) + assert hasattr(can.SizedRotatingCanLogger, "supported_writers") + assert hasattr(can.SizedRotatingCanLogger, "namer") + assert hasattr(can.SizedRotatingCanLogger, "rotator") + assert hasattr(can.SizedRotatingCanLogger, "should_rollover") + assert hasattr(can.SizedRotatingCanLogger, "do_rollover") + + def test_create_instance(self): + base_filename = "mylogfile.ASC" + max_bytes = 512 + + with tempfile.TemporaryDirectory() as temp_dir: + logger_instance = can.SizedRotatingCanLogger( + base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes + ) + assert Path(logger_instance.base_filename).name == base_filename + assert logger_instance.max_bytes == max_bytes + assert logger_instance.rollover_count == 0 + assert isinstance(logger_instance.writer, can.ASCWriter) + + logger_instance.stop() + + def test_should_rollover(self): + base_filename = "mylogfile.ASC" + max_bytes = 512 + + with tempfile.TemporaryDirectory() as temp_dir: + logger_instance = can.SizedRotatingCanLogger( + base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes + ) + msg = generate_message(0x123) + do_rollover = Mock() + logger_instance.do_rollover = do_rollover + + logger_instance.writer.file.tell = Mock(return_value=511) + assert logger_instance.should_rollover(msg) is False + logger_instance.on_message_received(msg) + do_rollover.assert_not_called() + + logger_instance.writer.file.tell = Mock(return_value=512) + assert logger_instance.should_rollover(msg) is True + logger_instance.on_message_received(msg) + do_rollover.assert_called() + + logger_instance.stop() + + def test_logfile_size(self): + base_filename = "mylogfile.ASC" + max_bytes = 1024 + msg = generate_message(0x123) + + with tempfile.TemporaryDirectory() as temp_dir: + logger_instance = can.SizedRotatingCanLogger( + base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes + ) + for _ in range(128): + logger_instance.on_message_received(msg) + + for file_path in os.listdir(temp_dir): + assert os.path.getsize(os.path.join(temp_dir, file_path)) <= 1100 + + logger_instance.stop() From 77c1737715ff8b0202479e7d8e4747e973af9eb7 Mon Sep 17 00:00:00 2001 From: Angus Peart Date: Thu, 27 Aug 2020 17:39:30 -0700 Subject: [PATCH 0456/1235] seeed interface: fix fileno issue with windows --- can/interfaces/seeedstudio/seeedstudio.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index aecc3c15f..25d848e9d 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -8,6 +8,7 @@ import logging import struct +import io from time import time from can import BusABC, Message @@ -262,7 +263,7 @@ def _recv_internal(self, timeout): return None, None def fileno(self): - if hasattr(self.ser, "fileno"): + try: return self.ser.fileno() - # Return an invalid file descriptor on Windows - return -1 + except io.UnsupportedOperation: + raise NotImplementedError("fileno is not implemented using current CAN bus") From 67690163866d53b34ad7922cc085cde8c8c4f43e Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 17 Sep 2020 21:01:50 +0200 Subject: [PATCH 0457/1235] update rotating logger class names --- can/__init__.py | 2 +- can/io/__init__.py | 2 +- can/io/logger.py | 12 ++++---- can/logger.py | 4 +-- doc/listeners.rst | 4 +-- test/test_rotating_loggers.py | 58 +++++++++++++++++------------------ 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index c48931fc2..30a8590ac 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -24,7 +24,7 @@ class CanError(IOError): from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader -from .io import Logger, SizedRotatingCanLogger, Printer, LogReader, MessageSync +from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync from .io import ASCWriter, ASCReader from .io import BLFReader, BLFWriter from .io import CanutilsLogReader, CanutilsLogWriter diff --git a/can/io/__init__.py b/can/io/__init__.py index a4b4ea231..0d3741b05 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -4,7 +4,7 @@ """ # Generic -from .logger import Logger, BaseRotatingCanLogger, SizedRotatingCanLogger +from .logger import Logger, BaseRotatingLogger, SizedRotatingLogger from .player import LogReader, MessageSync # Format specific diff --git a/can/io/logger.py b/can/io/logger.py index bc8a702db..177a2980e 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -81,7 +81,7 @@ def __new__(cls, filename: Optional[StringPathLike], *args, **kwargs): ) from None -class BaseRotatingCanLogger(Listener, ABC): +class BaseRotatingLogger(Listener, ABC): """ Base class for rotating CAN loggers. This class is not meant to be instantiated directly. Subclasses must implement the `should_rollover` @@ -224,7 +224,7 @@ def do_rollover(self): ... -class SizedRotatingCanLogger(BaseRotatingCanLogger): +class SizedRotatingLogger(BaseRotatingLogger): """Log CAN messages to a sequence of files with a given maximum size. The logger creates a log file with the given `base_filename`. When the @@ -237,12 +237,12 @@ class SizedRotatingCanLogger(BaseRotatingCanLogger): Example:: - from can import Notifier, SizedRotatingCanLogger + from can import Notifier, SizedRotatingLogger from can.interfaces.vector import VectorBus bus = VectorBus(channel=[0], app_name="CANape", fd=True) - logger = SizedRotatingCanLogger( + logger = SizedRotatingLogger( base_filename="my_logfile.asc", max_bytes=5 * 1024 ** 2, # =5MB ) @@ -250,7 +250,7 @@ class SizedRotatingCanLogger(BaseRotatingCanLogger): notifier = Notifier(bus=bus, listeners=[logger]) - The SizedRotatingCanLogger currently supports the formats + The SizedRotatingLogger currently supports the formats * .asc: :class:`can.ASCWriter` * .blf :class:`can.BLFWriter` * .csv: :class:`can.CSVWriter` @@ -271,7 +271,7 @@ def __init__( The size threshold at which a new log file shall be created. If set to 0, no rollover will be performed. """ - super(SizedRotatingCanLogger, self).__init__(*args, **kwargs) + super(SizedRotatingLogger, self).__init__(*args, **kwargs) self.base_filename = os.path.abspath(base_filename) self.max_bytes = max_bytes diff --git a/can/logger.py b/can/logger.py index 04f65244d..bf83ef10f 100644 --- a/can/logger.py +++ b/can/logger.py @@ -20,7 +20,7 @@ from datetime import datetime import can -from can import Bus, BusState, Logger, SizedRotatingCanLogger +from can import Bus, BusState, Logger, SizedRotatingLogger def main(): @@ -153,7 +153,7 @@ def main(): print(f"Can Logger (Started on {datetime.now()})") if results.file_size: - logger = SizedRotatingCanLogger( + logger = SizedRotatingLogger( base_filename=results.log_file, max_bytes=results.file_size ) else: diff --git a/doc/listeners.rst b/doc/listeners.rst index cf9080355..ad18aff24 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -75,10 +75,10 @@ create log files with different file types of the messages received. .. autoclass:: can.Logger :members: -.. autoclass:: can.io.BaseRotatingCanLogger +.. autoclass:: can.io.BaseRotatingLogger :members: -.. autoclass:: can.SizedRotatingCanLogger +.. autoclass:: can.SizedRotatingLogger :members: diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index 0cda47cdd..6f9121584 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -15,10 +15,10 @@ from .data.example_data import generate_message -class TestBaseRotatingCanLogger: +class TestBaseRotatingLogger: @staticmethod def _get_instance(*args, **kwargs): - class SubClass(can.io.BaseRotatingCanLogger): + class SubClass(can.io.BaseRotatingLogger): """Subclass that implements abstract methods for testing.""" def should_rollover(self, msg): @@ -30,25 +30,25 @@ def do_rollover(self): return SubClass(*args, **kwargs) def test_import(self): - assert hasattr(can.io, "BaseRotatingCanLogger") + assert hasattr(can.io, "BaseRotatingLogger") def test_attributes(self): - assert issubclass(can.io.BaseRotatingCanLogger, can.Listener) - assert hasattr(can.io.BaseRotatingCanLogger, "supported_writers") - assert hasattr(can.io.BaseRotatingCanLogger, "namer") - assert hasattr(can.io.BaseRotatingCanLogger, "rotator") - assert hasattr(can.io.BaseRotatingCanLogger, "rollover_count") - assert hasattr(can.io.BaseRotatingCanLogger, "writer") - assert hasattr(can.io.BaseRotatingCanLogger, "rotation_filename") - assert hasattr(can.io.BaseRotatingCanLogger, "rotate") - assert hasattr(can.io.BaseRotatingCanLogger, "on_message_received") - assert hasattr(can.io.BaseRotatingCanLogger, "get_new_writer") - assert hasattr(can.io.BaseRotatingCanLogger, "stop") - assert hasattr(can.io.BaseRotatingCanLogger, "should_rollover") - assert hasattr(can.io.BaseRotatingCanLogger, "do_rollover") + assert issubclass(can.io.BaseRotatingLogger, can.Listener) + assert hasattr(can.io.BaseRotatingLogger, "supported_writers") + assert hasattr(can.io.BaseRotatingLogger, "namer") + assert hasattr(can.io.BaseRotatingLogger, "rotator") + assert hasattr(can.io.BaseRotatingLogger, "rollover_count") + assert hasattr(can.io.BaseRotatingLogger, "writer") + assert hasattr(can.io.BaseRotatingLogger, "rotation_filename") + assert hasattr(can.io.BaseRotatingLogger, "rotate") + assert hasattr(can.io.BaseRotatingLogger, "on_message_received") + assert hasattr(can.io.BaseRotatingLogger, "get_new_writer") + assert hasattr(can.io.BaseRotatingLogger, "stop") + assert hasattr(can.io.BaseRotatingLogger, "should_rollover") + assert hasattr(can.io.BaseRotatingLogger, "do_rollover") def test_supported_writers(self): - supported_writers = can.io.BaseRotatingCanLogger.supported_writers + supported_writers = can.io.BaseRotatingLogger.supported_writers assert supported_writers[".asc"] == can.ASCWriter assert supported_writers[".blf"] == can.BLFWriter assert supported_writers[".csv"] == can.CSVWriter @@ -198,25 +198,25 @@ def test_on_message_received(self): logger_instance.stop() -class TestSizedRotatingCanLogger: +class TestSizedRotatingLogger: def test_import(self): - assert hasattr(can.io, "SizedRotatingCanLogger") - assert hasattr(can, "SizedRotatingCanLogger") + assert hasattr(can.io, "SizedRotatingLogger") + assert hasattr(can, "SizedRotatingLogger") def test_attributes(self): - assert issubclass(can.SizedRotatingCanLogger, can.io.BaseRotatingCanLogger) - assert hasattr(can.SizedRotatingCanLogger, "supported_writers") - assert hasattr(can.SizedRotatingCanLogger, "namer") - assert hasattr(can.SizedRotatingCanLogger, "rotator") - assert hasattr(can.SizedRotatingCanLogger, "should_rollover") - assert hasattr(can.SizedRotatingCanLogger, "do_rollover") + assert issubclass(can.SizedRotatingLogger, can.io.BaseRotatingLogger) + assert hasattr(can.SizedRotatingLogger, "supported_writers") + assert hasattr(can.SizedRotatingLogger, "namer") + assert hasattr(can.SizedRotatingLogger, "rotator") + assert hasattr(can.SizedRotatingLogger, "should_rollover") + assert hasattr(can.SizedRotatingLogger, "do_rollover") def test_create_instance(self): base_filename = "mylogfile.ASC" max_bytes = 512 with tempfile.TemporaryDirectory() as temp_dir: - logger_instance = can.SizedRotatingCanLogger( + logger_instance = can.SizedRotatingLogger( base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes ) assert Path(logger_instance.base_filename).name == base_filename @@ -231,7 +231,7 @@ def test_should_rollover(self): max_bytes = 512 with tempfile.TemporaryDirectory() as temp_dir: - logger_instance = can.SizedRotatingCanLogger( + logger_instance = can.SizedRotatingLogger( base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes ) msg = generate_message(0x123) @@ -256,7 +256,7 @@ def test_logfile_size(self): msg = generate_message(0x123) with tempfile.TemporaryDirectory() as temp_dir: - logger_instance = can.SizedRotatingCanLogger( + logger_instance = can.SizedRotatingLogger( base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes ) for _ in range(128): From b55c47e7299bad1369e5ff72a68ac332a82c6bb8 Mon Sep 17 00:00:00 2001 From: xygonakis Date: Fri, 18 Sep 2020 11:24:46 +0300 Subject: [PATCH 0458/1235] fix typo in asyncio_demo --- examples/asyncio_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 examples/asyncio_demo.py diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py old mode 100644 new mode 100755 index b2c84b9d1..d501d1aaf --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -48,7 +48,7 @@ async def main(): bus.shutdown() -if __name__ == "__main": +if __name__ == "__main__": try: # Get the default event loop LOOP = asyncio.get_event_loop() From 337be7bb992bcaad69ed575ae0157711dd69c0bf Mon Sep 17 00:00:00 2001 From: Fabian Henze Date: Fri, 25 Sep 2020 10:07:55 +0100 Subject: [PATCH 0459/1235] IXXAT: Add support for 666kbit/s bitrate --- can/interfaces/ixxat/canlib.py | 8 ++++++++ can/interfaces/ixxat/constants.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 4108be752..c44c2d5f8 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -378,6 +378,10 @@ class IXXATBus(BusABC): 125000: constants.CAN_BT0_125KB, 250000: constants.CAN_BT0_250KB, 500000: constants.CAN_BT0_500KB, + 666000: constants.CAN_BT0_667KB, + 666666: constants.CAN_BT0_667KB, + 666667: constants.CAN_BT0_667KB, + 667000: constants.CAN_BT0_667KB, 800000: constants.CAN_BT0_800KB, 1000000: constants.CAN_BT0_1000KB, }, @@ -389,6 +393,10 @@ class IXXATBus(BusABC): 125000: constants.CAN_BT1_125KB, 250000: constants.CAN_BT1_250KB, 500000: constants.CAN_BT1_500KB, + 666000: constants.CAN_BT1_667KB, + 666666: constants.CAN_BT1_667KB, + 666667: constants.CAN_BT1_667KB, + 667000: constants.CAN_BT1_667KB, 800000: constants.CAN_BT1_800KB, 1000000: constants.CAN_BT1_1000KB, }, diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index a24a3f291..e911a580a 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -26,6 +26,8 @@ CAN_BT1_250KB = 0x1C CAN_BT0_500KB = 0x00 CAN_BT1_500KB = 0x1C +CAN_BT0_667KB = 0x00 +CAN_BT1_667KB = 0x18 CAN_BT0_800KB = 0x00 CAN_BT1_800KB = 0x16 CAN_BT0_1000KB = 0x00 From 9fcca8e12c8decb0f9dc719c2c46bf777f250cfa Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 11 Oct 2020 14:54:08 +0200 Subject: [PATCH 0460/1235] Add more interface information to vector channel config --- can/interfaces/vector/canlib.py | 53 +++++++++++++++++++++++-------- can/interfaces/vector/xldefine.py | 22 ++++++++++--- test/test_vector.py | 7 +--- 3 files changed, 58 insertions(+), 24 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 658f256f1..de38aa44b 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -102,7 +102,7 @@ def __init__( :param int serial: Serial number of the hardware to be used. If set, the channel parameter refers to the channels ONLY on the specified hardware. - If set, the app_name is unused. + If set, the app_name does not have to be previously defined in Vector Hardware Config. :param bool fd: If CAN-FD frames should be supported. :param int data_bitrate: @@ -175,7 +175,7 @@ def __init__( if app_name: # Get global channel index from application channel hw_type, hw_index, hw_channel = self.get_application_config( - app_name, channel, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN + app_name, channel ) LOG.debug("Channel index %d found", channel) idx = xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) @@ -615,13 +615,24 @@ def _detect_available_configs(): ) configs.append( { + # data for use in VectorBus.__init__(): "interface": "vector", - "app_name": None, - "channel": channel_config.channelIndex, + "channel": channel_config.hwChannel, + "serial": channel_config.serialNumber, + # data for use in VectorBus.set_application_config(): + "hw_type": xldefine.XL_HardwareType(channel_config.hwType), + "hw_index": channel_config.hwIndex, + "hw_channel": channel_config.hwChannel, + # additional information: "supports_fd": bool( - channel_config.channelBusCapabilities + channel_config.channelCapabilities & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT ), + "isOnBus": bool(channel_config.isOnBus), + "name": channel_config.name.decode(), + "channelIndex": channel_config.channelIndex, + "channelMask": channel_config.channelMask, + "transceiverName": channel_config.transceiverName.decode(), } ) return configs @@ -637,7 +648,7 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: @staticmethod def get_application_config( - app_name: str, app_channel: int, bus_type: xldefine.XL_BusTypes + app_name: str, app_channel: int ) -> Tuple[xldefine.XL_HardwareType, int, int]: """Retrieve information for an application in Vector Hardware Configuration. @@ -645,8 +656,6 @@ def get_application_config( The name of the application. :param app_channel: The channel of the application. - :param bus_type: - The bus type Enum e.g. `XL_BusTypes.XL_BUS_TYPE_CAN` :return: Returns a tuple of the hardware type, the hardware index and the hardware channel. @@ -659,7 +668,12 @@ def get_application_config( hw_channel = ctypes.c_uint() xldriver.xlGetApplConfig( - app_name.encode(), app_channel, hw_type, hw_index, hw_channel, bus_type + app_name.encode(), + app_channel, + hw_type, + hw_index, + hw_channel, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, ) return xldefine.XL_HardwareType(hw_type.value), hw_index.value, hw_channel.value @@ -670,10 +684,19 @@ def set_application_config( hw_type: xldefine.XL_HardwareType, hw_index: int, hw_channel: int, - bus_type: xldefine.XL_BusTypes, + **kwargs, ) -> None: """Modify the application settings in Vector Hardware Configuration. + This method can also be used with a channel config dictionary:: + + import can + from can.interfaces.vector import VectorBus + + configs = can.detect_available_configs(interfaces=['vector']) + cfg = configs[0] + VectorBus.set_application_config(app_name="MyApplication", app_channel=0, **cfg) + :param app_name: The name of the application. Creates a new application if it does not exist yet. @@ -687,12 +710,14 @@ def set_application_config( hardware type are present. :param hw_channel: The channel index of the interface. - :param bus_type: - The bus type of the interfaces, which should be - XL_BusTypes.XL_BUS_TYPE_CAN for most cases. """ xldriver.xlSetApplConfig( - app_name.encode(), app_channel, hw_type, hw_index, hw_channel, bus_type + app_name.encode(), + app_channel, + hw_type, + hw_index, + hw_channel, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, ) def set_timer_rate(self, timer_rate_ms: int) -> None: diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index eb228af34..d2d69efd1 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -35,8 +35,17 @@ class XL_BusStatus(IntEnum): class XL_BusTypes(IntEnum): - XL_BUS_TYPE_NONE = 0 - XL_BUS_TYPE_CAN = 1 + XL_BUS_TYPE_NONE = 0 # =0x00000000 + XL_BUS_TYPE_CAN = 1 # =0x00000001 + XL_BUS_TYPE_LIN = 2 # =0x00000002 + XL_BUS_TYPE_FLEXRAY = 4 # =0x00000004 + XL_BUS_TYPE_AFDX = 8 # =0x00000008 + XL_BUS_TYPE_MOST = 16 # =0x00000010 + XL_BUS_TYPE_DAIO = 64 # =0x00000040 + XL_BUS_TYPE_J1708 = 256 # =0x00000100 + XL_BUS_TYPE_KLINE = 2048 # =0x00000800 + XL_BUS_TYPE_ETHERNET = 4096 # =0x00001000 + XL_BUS_TYPE_A429 = 8192 # =0x00002000 class XL_CANFD_BusParams_CanOpMode(IntEnum): @@ -106,6 +115,10 @@ class XL_ChannelCapabilities(IntEnum): XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 2147483648 +class XL_EventFlags(IntEnum): + XL_EVENT_FLAG_OVERRUN = 1 + + class XL_EventTags(IntEnum): XL_NO_COMMAND = 0 XL_RECEIVE_MSG = 1 @@ -135,7 +148,6 @@ class XL_MessageFlags(IntEnum): 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(IntEnum): @@ -266,4 +278,6 @@ class XL_HardwareType(IntEnum): XL_HWTYPE_VN5430 = 109 XL_HWTYPE_VN1530 = 112 XL_HWTYPE_VN1531 = 113 - XL_MAX_HWTYPE = 113 + XL_HWTYPE_VX1161A = 114 + XL_HWTYPE_VX1161B = 115 + XL_MAX_HWTYPE = 119 diff --git a/test/test_vector.py b/test/test_vector.py index 2863b5ff1..152d04024 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -249,11 +249,7 @@ def test_popup_hw_cfg(self) -> None: def test_get_application_config(self) -> None: canlib.xldriver.xlGetApplConfig = Mock() - canlib.VectorBus.get_application_config( - app_name="CANalyzer", - app_channel=0, - bus_type=xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - ) + canlib.VectorBus.get_application_config(app_name="CANalyzer", app_channel=0) assert canlib.xldriver.xlGetApplConfig.called def test_set_application_config(self) -> None: @@ -264,7 +260,6 @@ def test_set_application_config(self) -> None: hw_type=xldefine.XL_HardwareType.XL_HWTYPE_VN1610, hw_index=0, hw_channel=0, - bus_type=xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, ) assert canlib.xldriver.xlSetApplConfig.called From f481b27fc1f41e72865eb2a8346ca9b00582c80c Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Mon, 12 Oct 2020 12:25:02 -0400 Subject: [PATCH 0461/1235] trows -> throws in Notifier.remove_listener() docs --- can/notifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/notifier.py b/can/notifier.py index eae7363ab..2842cc130 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -158,7 +158,7 @@ def add_listener(self, listener: 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 + throws an exception if the given listener is not part of the stored listeners. :param listener: Listener to be removed from the list to be notified From e98b25c0274d06b9f7374202c94583dab537329a Mon Sep 17 00:00:00 2001 From: Philipp Schrader Date: Mon, 12 Oct 2020 18:41:04 +0000 Subject: [PATCH 0462/1235] Add missing "setuptools" dependency in setup.py A few of the source files import `pkg_resources` which is provided by `setuptools`. In an environment where you only provide the declared dependencies, `python-can` fails to import. This patch aims to fix that. $ git grep pkg_resources can/interfaces/__init__.py:from pkg_resources import iter_entry_points can/io/logger.py:from pkg_resources import iter_entry_points can/io/player.py:from pkg_resources import iter_entry_points --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e5582e0a5..799799539 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=3.6", install_requires=[ + "setuptools", "wrapt~=1.10", 'windows-curses;platform_system=="Windows"', "filelock", From 2d10303e5159d428b30de1e4da0fb3fb0828b0c4 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 14 Oct 2020 14:44:54 +1300 Subject: [PATCH 0463/1235] Add comment to setup.py Documenting why setuptools is a dependency --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 799799539..16cb4b599 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=3.6", install_requires=[ + # Setuptools provides pkg_resources which python-can makes use of. "setuptools", "wrapt~=1.10", 'windows-curses;platform_system=="Windows"', From 6da964c19348acc4a427ede0659b475f2554e8a7 Mon Sep 17 00:00:00 2001 From: Benny Meisels Date: Tue, 4 Aug 2020 18:17:26 +0300 Subject: [PATCH 0464/1235] Slcan: fix fileno issue with windows --- can/interfaces/slcan.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index a952fb7c4..93b6e8d8d 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -10,6 +10,7 @@ from typing import Any, Optional, Tuple from can import typechecking +import io import time import logging @@ -252,10 +253,10 @@ def shutdown(self) -> None: self.serialPortOrig.close() def fileno(self) -> int: - if hasattr(self.serialPortOrig, "fileno"): + try: return self.serialPortOrig.fileno() - # Return an invalid file descriptor on Windows - return -1 + except io.UnsupportedOperation: + raise NotImplementedError("fileno is not implemented using current CAN bus on this platform") def get_version( self, timeout: Optional[float] From b27a61d7046dceabedaa73203ed04532bc1cacf5 Mon Sep 17 00:00:00 2001 From: Fabian Henze <32638720+henzef@users.noreply.github.com> Date: Mon, 19 Oct 2020 23:50:51 +0200 Subject: [PATCH 0465/1235] ixxat: Raise exception on busoff in recv() (#856) --- can/interfaces/ixxat/canlib.py | 39 ++++++++++++++++++++++++++++++ can/interfaces/ixxat/exceptions.py | 13 +++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index c44c2d5f8..f59289d3e 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -29,6 +29,7 @@ __all__ = [ "VCITimeout", "VCIError", + "VCIBusOffError", "VCIDeviceNotFoundError", "IXXATBus", "vciFormatError", @@ -354,6 +355,15 @@ def __check_status(result, function, arguments): constants.CAN_ERROR_CRC: "CAN CRC error", constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", } + +CAN_STATUS_FLAGS = { + constants.CAN_STATUS_TXPEND: "transmission pending", + constants.CAN_STATUS_OVRRUN: "data overrun occurred", + constants.CAN_STATUS_ERRLIM: "error warning limit exceeded", + constants.CAN_STATUS_BUSOFF: "bus off", + constants.CAN_STATUS_ININIT: "init mode active", + constants.CAN_STATUS_BUSCERR: "bus coupling error", +} # ---------------------------------------------------------------------------- @@ -644,6 +654,13 @@ def _recv_internal(self, timeout): ) ) + elif ( + self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS + ): + log.info(_format_can_status(self._message.abData[0])) + if self._message.abData[0] & constants.CAN_STATUS_BUSOFF: + raise VCIBusOffError() + elif ( self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR @@ -761,3 +778,25 @@ def stop(self): # the list with permanently stopped messages _canlib.canSchedulerRemMessage(self._scheduler, self._index) self._index = None + + +def _format_can_status(status_flags: int): + """ + Format a status bitfield found in CAN_MSGTYPE_STATUS messages or in dwStatus + field in CANLINESTATUS. + + Valid states are defined in the CAN_STATUS_* constants in cantype.h + """ + states = [] + for flag, description in CAN_STATUS_FLAGS.items(): + if status_flags & flag: + states.append(description) + status_flags &= ~flag + + if status_flags: + states.append("unknown state 0x{:02x}".format(status_flags)) + + if states: + return "CAN status message: {}".format(", ".join(states)) + else: + return "Empty CAN status message" diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index d548510b1..78884c2a6 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -6,7 +6,13 @@ from can import CanError -__all__ = ["VCITimeout", "VCIError", "VCIRxQueueEmptyError", "VCIDeviceNotFoundError"] +__all__ = [ + "VCITimeout", + "VCIError", + "VCIRxQueueEmptyError", + "VCIBusOffError", + "VCIDeviceNotFoundError", +] class VCITimeout(CanError): @@ -24,5 +30,10 @@ def __init__(self): super().__init__("Receive queue is empty") +class VCIBusOffError(VCIError): + def __init__(self): + super().__init__("Controller is in BUSOFF state") + + class VCIDeviceNotFoundError(CanError): pass From c31b5bade36f4076f75fad4561968c348e245644 Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:05:23 +0800 Subject: [PATCH 0466/1235] Add gs_usb support --- can/interfaces/gs_usb.py | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 can/interfaces/gs_usb.py diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py new file mode 100644 index 000000000..e424a8643 --- /dev/null +++ b/can/interfaces/gs_usb.py @@ -0,0 +1,107 @@ +from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union + +from gs_usb.gs_usb import GsUsb +from gs_usb.gs_usb_frame import GsUsbFrame +from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC +import can +import usb + + +class GsUsbBus(can.BusABC): + def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs): + """ + :param channel: usb device name + :param bus: number of the bus that the device is connected to + :param address: address of the device on the bus it is connected to + :param can_filters: not supported + :param bitrate: CAN network bandwidth (bits/s) + """ + gs_usb = GsUsb.find(bus=bus, address=address) + if not gs_usb: + raise can.CanError("Can not find device {}".format(channel)) + self.gs_usb = gs_usb + self.channel_info = channel + + self.gs_usb.set_bitrate(bitrate) + self.gs_usb.start() + + super().__init__(channel=channel, can_filters=can_filters, **kwargs) + + def send(self, msg: can.Message, timeout: Optional[float] = None): + """Transmit a message to the CAN bus. + + :param Message msg: A message object. + :param timeout: timeout is not used here + + :raises can.CanError: + if the message could not be sent + """ + can_id = msg.arbitration_id + + if msg.is_extended_id: + can_id = can_id | CAN_EFF_FLAG + + if msg.is_remote_frame: + can_id = can_id | CAN_RTR_FLAG + + if msg.is_error_frame: + can_id = can_id | CAN_ERR_FLAG + + frame = GsUsbFrame() + frame.can_id = can_id + frame.can_dlc = msg.dlc + frame.timestamp_us = int(msg.timestamp * 1000000) + msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data))) + frame.data = list(msg.data) + + self.gs_usb.send(frame) + + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[can.Message], bool]: + """ + Read a message from the bus and tell whether it was filtered. + This methods may be called by :meth:`~can.BusABC.recv` + to read a message multiple times if the filters set by + :meth:`~can.BusABC.set_filters` do not match and the call has + not yet timed out. + + :param float timeout: seconds to wait for a message, + see :meth:`~can.BusABC.send` + 0 and None will be converted to minimum value 1ms. + float will be finally converted to integer. + + :return: + 1. a message that was read or None on timeout + 2. a bool that is True if message filtering has already + been done and else False. In this interface it is always False + since filtering is not available + + :raises can.CanError: + if an error occurred while reading + """ + frame = GsUsbFrame() + + # Do not set timeout as None or zero here to avoid blocking + timeout_ms = round(timeout * 1000) if timeout else 1 + if not self.gs_usb.read(frame=frame, timeout_ms=timeout_ms): + return None, False + + msg = can.Message() + msg.arbitration_id = frame.arbitration_id + msg.dlc = frame.can_dlc + msg.data = bytearray(frame.data)[0:msg.dlc] + msg.timestamp = frame.timestamp + msg.channel = self.channel_info + msg.is_extended_id = frame.is_extended_id + msg.is_remote_frame = frame.is_remote_frame + msg.is_error_frame = frame.is_error_frame + msg.is_rx = True + return msg, False + + def shutdown(self): + """ + Called to carry out any interface specific cleanup required + in shutting down a bus. + """ + self.gs_usb.stop() From 6972ab873e32022c010cc9e774469c541441a6b8 Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:05:41 +0800 Subject: [PATCH 0467/1235] Enable gs_usb in interface list --- can/interfaces/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 1fa3f5b4b..2fb1afce9 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -25,6 +25,7 @@ "systec": ("can.interfaces.systec", "UcanBus"), "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), "cantact": ("can.interfaces.cantact", "CantactBus"), + "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), } BACKENDS.update( From ab9d4a3cdce59ae6dadb517cbe444071cd7b60a0 Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:05:58 +0800 Subject: [PATCH 0468/1235] Add install requires for gs_usb interface --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e5582e0a5..65ddaae97 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ "serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12"], "cantact": ["cantact>=0.0.7"], + "gs_usb": ["gs_usb>=0.2.1"], } setup( From 9f7b60a72fb89b19cbb93e125b46ca8cdc45917d Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:06:15 +0800 Subject: [PATCH 0469/1235] Add documentation for gs_usb interface --- doc/interfaces.rst | 1 + doc/interfaces/gs_usb.rst | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100755 doc/interfaces/gs_usb.rst diff --git a/doc/interfaces.rst b/doc/interfaces.rst index bd7a0d1df..2541f4610 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -27,6 +27,7 @@ The available interfaces are: interfaces/canalystii interfaces/systec interfaces/seeedstudio + interfaces/gs_usb 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/gs_usb.rst b/doc/interfaces/gs_usb.rst new file mode 100755 index 000000000..0d6fe9ac5 --- /dev/null +++ b/doc/interfaces/gs_usb.rst @@ -0,0 +1,29 @@ +.. _gs_usb: + +CAN driver based on WCID for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces +======================== + +Windows/Linux/Mac CAN driver based on WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces. + +Install: ``pip install "python-can[gs_usb]"`` +Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` + + +Supported devices +----------------- + +Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces such as candleLight, canable, cantact, etc. + + +Supported platform +------------------ + +Windows, Linux and Mac. + +Note: Since ``pyusb``` is used, ``libusb-win32`` usb driver is required to install in Windows + +Bus +--- + +.. autoclass:: can.interfaces.gs_usb.GsUsbBus + :members: From 470c50e84e6b02be095223da45f2efb0acd5c771 Mon Sep 17 00:00:00 2001 From: jxltom Date: Sat, 12 Sep 2020 17:36:02 +0800 Subject: [PATCH 0470/1235] Reformat code by black --- can/interfaces/gs_usb.py | 2 +- doc/interfaces/gs_usb.rst | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index e424a8643..0214eff99 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -90,7 +90,7 @@ def _recv_internal( msg = can.Message() msg.arbitration_id = frame.arbitration_id msg.dlc = frame.can_dlc - msg.data = bytearray(frame.data)[0:msg.dlc] + msg.data = bytearray(frame.data)[0 : msg.dlc] msg.timestamp = frame.timestamp msg.channel = self.channel_info msg.is_extended_id = frame.is_extended_id diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index 0d6fe9ac5..c733e00e8 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -1,11 +1,12 @@ .. _gs_usb: CAN driver based on WCID for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces -======================== +================================================================================================================== Windows/Linux/Mac CAN driver based on WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces. Install: ``pip install "python-can[gs_usb]"`` + Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` @@ -20,7 +21,7 @@ Supported platform Windows, Linux and Mac. -Note: Since ``pyusb``` is used, ``libusb-win32`` usb driver is required to install in Windows +Note: Since ``pyusb`` is used, ``libusb-win32`` usb driver is required to be installed in Windows Bus --- From 80f8a9fa12035f906f26765d33cf903f3effc569 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 10:01:55 +0800 Subject: [PATCH 0471/1235] Remove additional comment since float is actually accepted as timeout --- can/interfaces/gs_usb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 0214eff99..8460a88f4 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -69,7 +69,6 @@ def _recv_internal( :param float timeout: seconds to wait for a message, see :meth:`~can.BusABC.send` 0 and None will be converted to minimum value 1ms. - float will be finally converted to integer. :return: 1. a message that was read or None on timeout From c4ccdd03143a04c00b3f6346bc40bc4a5cb085f2 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 10:23:57 +0800 Subject: [PATCH 0472/1235] Pass can message fields as named arguments --- can/interfaces/gs_usb.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 8460a88f4..872c75056 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -86,16 +86,18 @@ def _recv_internal( if not self.gs_usb.read(frame=frame, timeout_ms=timeout_ms): return None, False - msg = can.Message() - msg.arbitration_id = frame.arbitration_id - msg.dlc = frame.can_dlc - msg.data = bytearray(frame.data)[0 : msg.dlc] - msg.timestamp = frame.timestamp - msg.channel = self.channel_info - msg.is_extended_id = frame.is_extended_id - msg.is_remote_frame = frame.is_remote_frame - msg.is_error_frame = frame.is_error_frame - msg.is_rx = True + msg = can.Message( + timestamp=frame.timestamp, + arbitration_id=frame.arbitration_id, + is_extended_id=frame.can_dlc, + is_remote_frame=frame.is_remote_frame, + is_error_frame=frame.is_error_frame, + channel=self.channel_info, + dlc=frame.can_dlc, + data=bytearray(frame.data)[0 : msg.dlc], + is_rx=True, + ) + return msg, False def shutdown(self): From 74e3272d0fc2361677a62fdc1dd7f614fc2fb9f1 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 11:36:24 +0800 Subject: [PATCH 0473/1235] Update docs for complete setup for a first time user --- doc/interfaces/gs_usb.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index c733e00e8..5053f1d9b 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -7,7 +7,15 @@ Windows/Linux/Mac CAN driver based on WCID for Geschwister Schneider USB/CAN dev Install: ``pip install "python-can[gs_usb]"`` -Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` +Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` as shown below: + +:: + + import usb + import can + + dev = usb.core.find(idVendor=0x1D50, idProduct=0x606F) + bus = can.Bus(bustype="gs_usb", channel=dev.product, bus=dev.bus, address=dev.address, bitrate=250000) Supported devices From ad49fca3767ce5efab668ef9ddc7af81d015418e Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 11:40:35 +0800 Subject: [PATCH 0474/1235] Update can/interfaces/gs_usb.py Co-authored-by: Brian Thorne --- can/interfaces/gs_usb.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 872c75056..da30e8ddf 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -47,11 +47,13 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): if msg.is_error_frame: can_id = can_id | CAN_ERR_FLAG + # Pad message data + msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data))) + frame = GsUsbFrame() frame.can_id = can_id frame.can_dlc = msg.dlc frame.timestamp_us = int(msg.timestamp * 1000000) - msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data))) frame.data = list(msg.data) self.gs_usb.send(frame) From 52b53b21111ae989db07a522dfe6ad03138f8a3b Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 12:57:04 +0800 Subject: [PATCH 0475/1235] Catch usb error and re-raise as CanError --- can/interfaces/gs_usb.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index da30e8ddf..23c7e7f89 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -56,7 +56,10 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): frame.timestamp_us = int(msg.timestamp * 1000000) frame.data = list(msg.data) - self.gs_usb.send(frame) + try: + self.gs_usb.send(frame) + except usb.core.USBError: + raise can.CanError("The message can not be sent") def _recv_internal( self, timeout: Optional[float] From 5565e2bf7f89ebaf67090ece5dbc8b10f9d36d10 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 13:01:14 +0800 Subject: [PATCH 0476/1235] Add logging for future use --- can/interfaces/gs_usb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 23c7e7f89..fa6e7d148 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -5,8 +5,11 @@ from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC import can import usb +import logging +logger = logging.getLogger(__name__) + class GsUsbBus(can.BusABC): def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs): """ From e90535ae906c8e7131ddc203edaba01b95f523a9 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 13:07:14 +0800 Subject: [PATCH 0477/1235] Update docs for expected behavior for send method --- can/interfaces/gs_usb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index fa6e7d148..cea33d089 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -34,7 +34,8 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): """Transmit a message to the CAN bus. :param Message msg: A message object. - :param timeout: timeout is not used here + :param timeout: timeout is not supported. + The function won't return until message is sent or exception is raised. :raises can.CanError: if the message could not be sent From 676dd7c9613a7272d1d138c3791bc2f5933a5ac6 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 14:24:05 +0800 Subject: [PATCH 0478/1235] Add supplementary info on gs_usb --- doc/interfaces/gs_usb.rst | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index 5053f1d9b..88646547a 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -1,9 +1,9 @@ .. _gs_usb: -CAN driver based on WCID for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces +CAN driver for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces ================================================================================================================== -Windows/Linux/Mac CAN driver based on WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces. +Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces. Install: ``pip install "python-can[gs_usb]"`` @@ -29,7 +29,21 @@ Supported platform Windows, Linux and Mac. -Note: Since ``pyusb`` is used, ``libusb-win32`` usb driver is required to be installed in Windows +Note: Since ``pyusb`` with ```libusb0``` as backend is used, ``libusb-win32`` usb driver is required to be installed in Windows. + + +Supplementary Info on ``gs_usb`` +----------------------------------- + +The firmware implementation for Geschwister Schneider USB/CAN devices and candleLight USB CAN can be found in `candle-usb/candleLight_fw `_. +The Linux kernel driver can be found in `linux/drivers/net/can/usb/gs_usb.c `_. + +The ``gs_usb`` interface in ``PythonCan`` relys on upstream ``gs_usb`` package, which can be found in `https://pypi.org/project/gs-usb/ `_ or `https://github.com/jxltom/gs_usb `_. +The ``gs_usb`` package is using ``pyusb`` as backend, which brings better crossplatform compatibility. + +Note: The bitrate ``10K``, ``20K``, ``50K``, ``83.333K``, ``100K``, ``125K``, ``250K``, ``500K``, ``800K`` and ``1M`` are supported in this interface, as implemented in the upstream ``gs_usb`` package's ``set_bitrate`` method. + +Note: Message filtering is not supported in Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces. Bus --- From ec5740ab4d701e23c8f34c624747c50efa5bf7e0 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 14:41:04 +0800 Subject: [PATCH 0479/1235] Reformat code by black --- can/interfaces/gs_usb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index cea33d089..dadddc267 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -10,6 +10,7 @@ logger = logging.getLogger(__name__) + class GsUsbBus(can.BusABC): def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs): """ @@ -53,7 +54,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): # Pad message data msg.data.extend([0x00] * (CAN_MAX_DLC - len(msg.data))) - + frame = GsUsbFrame() frame.can_id = can_id frame.can_dlc = msg.dlc From 751dfa40c0da29c55c828285ed8227b4f1e5e902 Mon Sep 17 00:00:00 2001 From: jxltom Date: Thu, 22 Oct 2020 15:56:28 +0800 Subject: [PATCH 0480/1235] Fix bug where msg is referenced befor assignment --- can/interfaces/gs_usb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index dadddc267..ff6ba634e 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -104,7 +104,7 @@ def _recv_internal( is_error_frame=frame.is_error_frame, channel=self.channel_info, dlc=frame.can_dlc, - data=bytearray(frame.data)[0 : msg.dlc], + data=bytearray(frame.data)[0 : frame.can_dlc], is_rx=True, ) From d17939b2a5370aeef07b2e351933c09959e09f2d Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 30 Oct 2020 15:24:11 -0400 Subject: [PATCH 0481/1235] Update PCAN basic Python file to February 7, 2020 --- can/interfaces/pcan/basic.py | 56 +++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 92f05a9d3..e3031390b 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -2,11 +2,11 @@ PCAN-Basic API Author: Keneth Wagner -Last change: 13.11.2017 Wagner +Last change: 02.07.2020 Wagner Language: Python 2.7, 3.5 -Copyright (C) 1999-2017 PEAK-System Technik GmbH, Darmstadt, Germany +Copyright (C) 1999-2020 PEAK-System Technik GmbH, Darmstadt, Germany http://www.peak-system.com """ @@ -157,6 +157,9 @@ PCAN_ERROR_ILLPARAMVAL = TPCANStatus(0x08000) # Invalid parameter value PCAN_ERROR_UNKNOWN = TPCANStatus(0x10000) # Unknown error PCAN_ERROR_ILLDATA = TPCANStatus(0x20000) # Invalid data, function, or action +PCAN_ERROR_ILLMODE = TPCANStatus( + 0x80000 +) # Driver object state is wrong for the attempted operation PCAN_ERROR_CAUTION = TPCANStatus( 0x2000000 ) # An operation was successfully carried out, however, irregularities were registered @@ -183,7 +186,7 @@ PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices # PCAN parameters -PCAN_DEVICE_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter +PCAN_DEVICE_ID = TPCANParameter(0x01) # PCAN-USB device identifier parameter PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 5-Volt power parameter PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter PCAN_MESSAGE_FILTER = TPCANParameter(0x04) # PCAN message filter parameter @@ -263,6 +266,18 @@ ) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip - Multiple digital I/O pins to 1 = High PCAN_IO_DIGITAL_CLEAR = TPCANParameter(0x27) # Clear multiple digital I/O pins to 0 PCAN_IO_ANALOG_VALUE = TPCANParameter(0x28) # Get value of a single analog input pin +PCAN_FIRMWARE_VERSION = TPCANParameter( + 0x29 +) # Get the version of the firmware used by the device associated with a PCAN-Channel +PCAN_ATTACHED_CHANNELS_COUNT = TPCANParameter( + 0x2A +) # Get the amount of PCAN channels attached to a system +PCAN_ATTACHED_CHANNELS = TPCANParameter( + 0x2B +) # Get information about PCAN channels attached to a system + +# DEPRECATED parameters +PCAN_DEVICE_NUMBER = PCAN_DEVICE_ID # DEPRECATED. Use PCAN_DEVICE_ID instead # PCAN parameter values PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) @@ -324,6 +339,14 @@ SERVICE_STATUS_STOPPED = int(0x01) # The service is not running SERVICE_STATUS_RUNNING = int(0x04) # The service is running +# Other constants +MAX_LENGTH_HARDWARE_NAME = int( + 33 +) # Maximum length of the name of a device: 32 characters + terminator +MAX_LENGTH_VERSION_STRING = int( + 18 +) # Maximum length of a version string: 17 characters + terminator + # PCAN message types PCAN_MESSAGE_STANDARD = TPCANMessageType( 0x00 @@ -524,6 +547,22 @@ class TPCANMsgFDMac(Structure): ] # Data of the message (DATA[0]..DATA[63]) +class TPCANChannelInformation(Structure): + """ + Describes an available PCAN channel + """ + + _fields_ = [ + ("channel_handle", TPCANHandle), # PCAN channel handle + ("device_type", TPCANDevice), # Kind of PCAN device + ("controller_number", c_ubyte), # CAN-Controller number + ("device_features", c_uint), # Device capabilities flag (see FEATURE_*) + ("device_name", c_char * MAX_LENGTH_HARDWARE_NAME), # Device name + ("device_id", c_uint), # Device number + ("channel_condition", c_uint), + ] # Availability status of a PCAN-Channel + + # /////////////////////////////////////////////////////////// # PCAN-Basic API function declarations # /////////////////////////////////////////////////////////// @@ -834,15 +873,24 @@ def GetValue(self, Channel, Parameter): PCAN_TRACE_LOCATION, PCAN_BITRATE_INFO_FD, PCAN_IP_ADDRESS, + PCAN_FIRMWARE_VERSION, ): mybuffer = create_string_buffer(256) + elif Parameter == PCAN_ATTACHED_CHANNELS: + res = self.GetValue(Channel, PCAN_ATTACHED_CHANNELS_COUNT) + if TPCANStatus(res[0]) != PCAN_ERROR_OK: + return (TPCANStatus(res[0]),) + mybuffer = (TPCANChannelInformation * res[1])() else: mybuffer = c_int(0) res = self.__m_dllBasic.CAN_GetValue( Channel, Parameter, byref(mybuffer), sizeof(mybuffer) ) - return TPCANStatus(res), mybuffer.value + if Parameter == PCAN_ATTACHED_CHANNELS: + return TPCANStatus(res), mybuffer + else: + return TPCANStatus(res), mybuffer.value except: logger.error("Exception on PCANBasic.GetValue") raise From e90ea80a741cfc076ab435ccc924dc903fa65936 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 31 Oct 2020 20:05:14 +0100 Subject: [PATCH 0482/1235] group additional information of vector channel config --- can/interfaces/vector/canlib.py | 56 +++++++++++++++++++++++++------ can/interfaces/vector/xldefine.py | 6 ++-- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index de38aa44b..6c522d968 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -609,9 +609,7 @@ def _detect_available_configs(): ): continue LOG.info( - "Channel index %d: %s", - channel_config.channelIndex, - channel_config.name.decode("ascii"), + "Channel index %d: %s", channel_config.channelIndex, channel_config.name ) configs.append( { @@ -620,7 +618,7 @@ def _detect_available_configs(): "channel": channel_config.hwChannel, "serial": channel_config.serialNumber, # data for use in VectorBus.set_application_config(): - "hw_type": xldefine.XL_HardwareType(channel_config.hwType), + "hw_type": channel_config.hwType, "hw_index": channel_config.hwIndex, "hw_channel": channel_config.hwChannel, # additional information: @@ -628,11 +626,7 @@ def _detect_available_configs(): channel_config.channelCapabilities & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT ), - "isOnBus": bool(channel_config.isOnBus), - "name": channel_config.name.decode(), - "channelIndex": channel_config.channelIndex, - "channelMask": channel_config.channelMask, - "transceiverName": channel_config.transceiverName.decode(), + "vector_channel_config": channel_config, } ) return configs @@ -735,7 +729,23 @@ def set_timer_rate(self, timer_rate_ms: int) -> None: xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) -def get_channel_configs() -> List[xlclass.XLchannelConfig]: +class VectorChannelConfig(typing.NamedTuple): + name: str + hwType: xldefine.XL_HardwareType + hwIndex: int + hwChannel: int + channelIndex: int + channelMask: int + channelCapabilities: xldefine.XL_ChannelCapabilities + channelBusCapabilities: xldefine.XL_BusCapabilities + isOnBus: bool + connectedBusType: xldefine.XL_BusTypes + serialNumber: int + articleNumber: int + transceiverName: str + + +def get_channel_configs() -> List[VectorChannelConfig]: if xldriver is None: return [] driver_config = xlclass.XLdriverConfig() @@ -745,4 +755,28 @@ def get_channel_configs() -> List[xlclass.XLchannelConfig]: xldriver.xlCloseDriver() except VectorError: pass - return [driver_config.channel[i] for i in range(driver_config.channelCount)] + + channel_list: List[VectorChannelConfig] = [] + for i in range(driver_config.channelCount): + xlcc: xlclass.XLchannelConfig = driver_config.channel[i] + vcc = VectorChannelConfig( + name=xlcc.name.decode(), + hwType=xldefine.XL_HardwareType(xlcc.hwType), + hwIndex=xlcc.hwIndex, + hwChannel=xlcc.hwChannel, + channelIndex=xlcc.channelIndex, + channelMask=xlcc.channelMask, + channelCapabilities=xldefine.XL_ChannelCapabilities( + xlcc.channelCapabilities + ), + channelBusCapabilities=xldefine.XL_BusCapabilities( + xlcc.channelBusCapabilities + ), + isOnBus=bool(xlcc.isOnBus), + connectedBusType=xldefine.XL_BusTypes(xlcc.connectedBusType), + serialNumber=xlcc.serialNumber, + articleNumber=xlcc.articleNumber, + transceiverName=xlcc.transceiverName.decode(), + ) + channel_list.append(vcc) + return channel_list diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index d2d69efd1..d31c978ab 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -4,7 +4,7 @@ # Import Python Modules # ============================== -from enum import IntEnum +from enum import IntEnum, IntFlag MAX_MSG_LEN = 8 @@ -22,7 +22,7 @@ class XL_AcceptanceFilter(IntEnum): XL_CAN_EXT = 2 -class XL_BusCapabilities(IntEnum): +class XL_BusCapabilities(IntFlag): XL_BUS_COMPATIBLE_CAN = 1 XL_BUS_ACTIVE_CAP_CAN = 65536 @@ -106,7 +106,7 @@ class XL_CANFD_TX_MessageFlags(IntEnum): XL_CAN_TXMSG_FLAG_WAKEUP = 512 -class XL_ChannelCapabilities(IntEnum): +class XL_ChannelCapabilities(IntFlag): XL_CHANNEL_FLAG_TIME_SYNC_RUNNING = 1 XL_CHANNEL_FLAG_NO_HWSYNC_SUPPORT = 1024 XL_CHANNEL_FLAG_SPDIF_CAPABLE = 16384 From 1085ecd1bfd18c5d539fb7e2dba57f99887671c4 Mon Sep 17 00:00:00 2001 From: rliebscher <37689288+rliebscher@users.noreply.github.com> Date: Sun, 15 Nov 2020 22:52:29 +0100 Subject: [PATCH 0483/1235] Add function to list hwids of available IXXAT devices (#926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add function to list hwids of available IXXAT devices * Document usage of get_ixxat_hwids() To use multiple IXXAT devices at same time you have to know their hwids beforehand. If it does not matter which device is used, a list of available devices is needed. A potential use case is simulating both ends of a CAN bus with another device in between, so it does not matter which device is sender or receiver. Usage is as easy as: from can.interfaces.ixxat import get_ixxat_hwids for hwid in get_ixxat_hwids(): print("Found IXXAT with hardware id '%s'." % hwid) Co-authored-by: René Liebscher Co-authored-by: Brian Thorne --- can/interfaces/ixxat/__init__.py | 2 +- can/interfaces/ixxat/canlib.py | 19 +++++++++++++++++++ doc/interfaces/ixxat.rst | 10 ++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index a4613880b..fc2cae0f3 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -4,4 +4,4 @@ Copyright (C) 2016 Giuseppe Corbelli """ -from can.interfaces.ixxat.canlib import IXXATBus +from can.interfaces.ixxat.canlib import IXXATBus, get_ixxat_hwids diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index f59289d3e..ce8eb8777 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -800,3 +800,22 @@ def _format_can_status(status_flags: int): return "CAN status message: {}".format(", ".join(states)) else: return "Empty CAN status message" + + +def get_ixxat_hwids(): + """Get a list of hardware ids of all available IXXAT devices.""" + hwids = [] + device_handle = HANDLE() + device_info = structures.VCIDEVICEINFO() + + _canlib.vciEnumDeviceOpen(ctypes.byref(device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info)) + except StopIteration: + break + else: + hwids.append(device_info.UniqueHardwareId.AsChar.decode("ascii")) + _canlib.vciEnumDeviceClose(device_handle) + + return hwids diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 845585dee..9ff463a0d 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -53,6 +53,16 @@ The can_id/mask must be specified according to IXXAT behaviour, that is bit 0 of can_id/mask parameters represents the RTR field in CAN frame. See IXXAT VCI documentation, section "Message filters" for more info. +List available devices +----------------- +In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id. +To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids()`` as demonstrated below: + + >>> from can.interfaces.ixxat import get_ixxat_hwids + >>> for hwid in get_ixxat_hwids(): print("Found IXXAT with hardware id '%s'." % hwid) + Found IXXAT with hardware id 'HW441489'. + Found IXXAT with hardware id 'HW107422'. + Internals --------- From 72c4ac9c4444c85062d5a82d6d82ed2e28a6304f Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 16 Nov 2020 13:18:14 +1300 Subject: [PATCH 0484/1235] Update mergify config (#937) * Update mergify config - Allow automatic merging of PRs that have passed the CI and have at least one passing review and no outstanding reviews. * Update mergify config Enable smarter auto merging --- .mergify.yml | 24 +++++++++++++++++++++--- README.rst | 4 ++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index db4f18f33..e837cced6 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,10 +1,28 @@ pull_request_rules: - - name: Automatic merge on up to date branch with dual approval + - name: Automatic merge passing PR on up to date branch with approving CR conditions: - - "#approved-reviews-by>=2" + - "#approved-reviews-by>=1" + - "#review-requested=0" + - "#changes-requested-reviews-by=0" + - "#commented-reviews-by=0" - "status-success=continuous-integration/travis-ci/pr" - "label!=work-in-progress" actions: merge: method: merge - strict: true + strict: smart+fasttrack + - name: Request Brian to review changes on core api. + conditions: + - "-files~=^can/interfaces/$" + - "-closed" + - "author!=hardbyte" + actions: + request_reviews: + users: + - hardbyte + + - name: delete head branch after merge + conditions: + - merged + actions: + delete_head_branch: {} diff --git a/README.rst b/README.rst index 9fce86c02..6ce0a8232 100644 --- a/README.rst +++ b/README.rst @@ -37,6 +37,10 @@ python-can :target: https://codecov.io/gh/hardbyte/python-can/branch/develop :alt: Test coverage reports on Codecov.io +.. image:: https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/hardbyte/python-can&style=flat + :target: https://mergify.io + :alt: Mergify Status + 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 From e1a4a89b1b7c0d56bff52a3e597451ddaa0c2071 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 19 Nov 2020 14:37:00 +0100 Subject: [PATCH 0485/1235] Update PCAN docs Mainly better formatting. --- doc/interfaces/pcan.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index 39fc356c9..378d70e9b 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -5,11 +5,11 @@ PCAN Basic API Interface to `Peak-System `__'s PCAN-Basic API. -Windows driver: https://www.peak-system.com/Downloads.76.0.html?&L=1 (also supported on cygwin) +The required drivers can be downloaded here: -Linux driver: https://www.peak-system.com/fileadmin/media/linux/index.htm#download and https://www.peak-system.com/Downloads.76.0.html?&L=1 (PCAN-Basic API (Linux)) - -Mac driver: http://www.mac-can.com +- `Windows `__ (also supported on *Cygwin*) +- `Linux `__ (`also works without `__, see also :ref:`pcandoc linux installation`) +- `macOS `__ Configuration ------------- @@ -43,6 +43,8 @@ Valid ``channel`` values: Where ``x`` should be replaced with the desired channel number starting at 1. +.. _pcandoc linux installation: + Linux installation ------------------ From 54d0fc1df2c9a3209822923a691b71b84958184d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 19 Nov 2020 16:27:20 +0100 Subject: [PATCH 0486/1235] Version bump CI tools The automated static analysis tools are updated to their most recent version --- requirements-lint.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index d0b374c20..257c048e5 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ -pylint==2.3.1 -black==19.3b0 -mypy==0.740 -mypy-extensions==0.4.1 +pylint==2.6.0 +black==20.8b1 +mypy==0.790 +mypy-extensions==0.4.3 From 4a4f4a7d4d850b4ed957355029181cb0162dadb1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 19 Nov 2020 16:28:20 +0100 Subject: [PATCH 0487/1235] run black on newer version --- can/__init__.py | 4 +-- can/broadcastmanager.py | 6 ++-- can/bus.py | 3 +- can/interfaces/ixxat/canlib.py | 40 +++++++++++++-------------- can/interfaces/kvaser/canlib.py | 5 ++-- can/interfaces/pcan/basic.py | 37 ++++++++++++------------- can/interfaces/socketcan/socketcan.py | 9 ++---- can/io/sqlite.py | 3 +- can/listener.py | 3 +- test/logformats_test.py | 2 +- test/test_scripts.py | 3 +- test/test_socketcan.py | 24 ++++++++-------- test/test_vector.py | 12 ++++---- 13 files changed, 69 insertions(+), 82 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 30a8590ac..a7280250d 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -17,9 +17,7 @@ class CanError(IOError): - """Indicates an error with the CAN network. - - """ + """Indicates an error with the CAN network.""" from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 173727789..62fe34988 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -123,8 +123,7 @@ class RestartableCyclicTaskABC(CyclicSendTaskABC): @abc.abstractmethod def start(self): - """Restart a stopped periodic task. - """ + """Restart a stopped periodic task.""" class ModifiableCyclicTaskABC(CyclicSendTaskABC): @@ -170,8 +169,7 @@ def modify_data(self, messages: Union[Sequence[Message], Message]): class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): - """A Cyclic send task that supports switches send frequency after a set time. - """ + """A Cyclic send task that supports switches send frequency after a set time.""" def __init__( self, diff --git a/can/bus.py b/can/bus.py index 362eefd72..ecad55f1d 100644 --- a/can/bus.py +++ b/can/bus.py @@ -388,8 +388,7 @@ def _matches_filters(self, msg: Message) -> bool: return False def flush_tx_buffer(self): - """Discard every message that may be queued in the output buffer(s). - """ + """Discard every message that may be queued in the output buffer(s).""" def shutdown(self): """ diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index ce8eb8777..e6654dffe 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -61,17 +61,17 @@ def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): - """ Format a VCI error and attach failed function, decoded HRESULT and arguments - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :param arguments: - Arbitrary arguments tuple - :return: - Formatted string + """Format a VCI error and attach failed function, decoded HRESULT and arguments + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :param arguments: + Arbitrary arguments tuple + :return: + Formatted string """ # TODO: make sure we don't generate another exception return "{} - arguments were {}".format( @@ -80,15 +80,15 @@ def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): def __vciFormatError(library_instance, function, HRESULT): - """ Format a VCI error and attach failed function and decoded HRESULT - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :return: - Formatted string + """Format a VCI error and attach failed function and decoded HRESULT + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT HRESULT: + HRESULT returned by vcinpl call + :return: + Formatted string """ buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index d0610019b..972ceaf4c 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -528,8 +528,7 @@ def _apply_filters(self, filters): pass def flush_tx_buffer(self): - """ Wipeout the transmit buffer on the Kvaser. - """ + """Wipeout the transmit buffer on the Kvaser.""" canIoCtl(self._write_handle, canstat.canIOCTL_FLUSH_TX_BUFFER, 0, 0) def _recv_internal(self, timeout=None): @@ -649,7 +648,7 @@ def get_stats(self): :returns: bus statistics. :rtype: can.interfaces.kvaser.structures.BusStatistics - """ + """ canRequestBusStatistics(self._write_handle) stats = structures.BusStatistics() canGetBusStatistics( diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 92f05a9d3..33d1e2fcd 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -530,8 +530,7 @@ class TPCANMsgFDMac(Structure): class PCANBasic: - """PCAN-Basic API class implementation - """ + """PCAN-Basic API class implementation""" def __init__(self): # Loads the PCANBasic.dll and checks if driver is available @@ -590,25 +589,25 @@ def Initialize( def InitializeFD(self, Channel, BitrateFD): """ - Initializes a FD capable PCAN Channel - - Parameters: - Channel : The handle of a FD capable PCAN Channel - BitrateFD : The speed for the communication (FD bit rate string) + Initializes a FD capable PCAN Channel - Remarks: - See PCAN_BR_* values. - * parameter and values must be separated by '=' - * Couples of Parameter/value must be separated by ',' - * Following Parameter must be filled out: f_clock, data_brp, data_sjw, data_tseg1, data_tseg2, - nom_brp, nom_sjw, nom_tseg1, nom_tseg2. - * Following Parameters are optional (not used yet): data_ssp_offset, nom_samp + Parameters: + Channel : The handle of a FD capable PCAN Channel + BitrateFD : The speed for the communication (FD bit rate string) - Example: - f_clock=80000000,nom_brp=10,nom_tseg1=5,nom_tseg2=2,nom_sjw=1,data_brp=4,data_tseg1=7,data_tseg2=2,data_sjw=1 - - Returns: - A TPCANStatus error code + Remarks: + See PCAN_BR_* values. + * parameter and values must be separated by '=' + * Couples of Parameter/value must be separated by ',' + * Following Parameter must be filled out: f_clock, data_brp, data_sjw, data_tseg1, data_tseg2, + nom_brp, nom_sjw, nom_tseg1, nom_tseg2. + * Following Parameters are optional (not used yet): data_ssp_offset, nom_samp + + Example: + f_clock=80000000,nom_brp=10,nom_tseg1=5,nom_tseg2=2,nom_sjw=1,data_brp=4,data_tseg1=7,data_tseg2=2,data_sjw=1 + + Returns: + A TPCANStatus error code """ try: res = self.__m_dllBasic.CAN_InitializeFD(Channel, BitrateFD) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 28c16482a..aab647ecc 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -133,7 +133,7 @@ def bcm_header_factory( def build_can_frame(msg: Message) -> bytes: - """ CAN frame packing/unpacking (see 'struct can_frame' in ) + """CAN frame packing/unpacking (see 'struct can_frame' in ) /** * struct can_frame - basic CAN frame structure * @can_id: the CAN ID of the frame and CAN_*_FLAG flags, see above. @@ -446,10 +446,7 @@ def start(self) -> None: class MultiRateCyclicSendTask(CyclicSendTask): - """Exposes more of the full power of the TX_SETUP opcode. - - - """ + """Exposes more of the full power of the TX_SETUP opcode.""" def __init__( self, @@ -583,7 +580,7 @@ def capture_message( class SocketcanBus(BusABC): - """ A SocketCAN interface to CAN. + """A SocketCAN interface to CAN. It implements :meth:`can.BusABC._detect_available_configs` to search for available interfaces. diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 104ceb77d..c947ab3e3 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -80,8 +80,7 @@ def read_all(self): return (SqliteReader._assemble_message(frame) for frame in result) def stop(self): - """Closes the connection to the database. - """ + """Closes the connection to the database.""" super().stop() self._conn.close() diff --git a/can/listener.py b/can/listener.py index b9f6b5b8c..05762cad0 100644 --- a/can/listener.py +++ b/can/listener.py @@ -120,8 +120,7 @@ def get_message(self, timeout: float = 0.5) -> Optional[Message]: return None def stop(self): - """Prohibits any more additions to this reader. - """ + """Prohibits any more additions to this reader.""" self.is_stopped = True diff --git a/test/logformats_test.py b/test/logformats_test.py index fbca43306..dfd6fa860 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -528,7 +528,7 @@ def test_can_and_canfd_error_frames(self): class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. - + Uses log files created by Toby Lorenz: https://bitbucket.org/tobylorenz/vector_blf/src/master/src/Vector/BLF/tests/unittests/events_from_binlog/ """ diff --git a/test/test_scripts.py b/test/test_scripts.py index 9504cf707..59325dcd6 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -62,8 +62,7 @@ def _commands(self): @abstractmethod def _import(self): - """Returns the modue of the script that has a main() function. - """ + """Returns the modue of the script that has a main() function.""" pass diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 32389ff9f..d4077e36f 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -38,10 +38,10 @@ def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_4( ): """This tests a 32-bit platform (ex. Debian Stretch on i386), where: - * sizeof(long) == 4 - * sizeof(long long) == 8 - * alignof(long) == 4 - * alignof(long long) == 4 + * sizeof(long) == 4 + * sizeof(long long) == 8 + * alignof(long) == 4 + * alignof(long long) == 4 """ def side_effect_ctypes_sizeof(value): @@ -107,10 +107,10 @@ def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_long_8( ): """This tests a 32-bit platform (ex. Raspbian Stretch on armv7l), where: - * sizeof(long) == 4 - * sizeof(long long) == 8 - * alignof(long) == 4 - * alignof(long long) == 8 + * sizeof(long) == 4 + * sizeof(long long) == 8 + * alignof(long) == 4 + * alignof(long long) == 8 """ def side_effect_ctypes_sizeof(value): @@ -176,10 +176,10 @@ def test_bcm_header_factory_64_bit_sizeof_long_8_alignof_long_8( ): """This tests a 64-bit platform (ex. Ubuntu 18.04 on x86_64), where: - * sizeof(long) == 8 - * sizeof(long long) == 8 - * alignof(long) == 8 - * alignof(long long) == 8 + * sizeof(long) == 8 + * sizeof(long long) == 8 + * alignof(long) == 8 + * alignof(long long) == 8 """ def side_effect_ctypes_sizeof(value): diff --git a/test/test_vector.py b/test/test_vector.py index 152d04024..5bf2f14b4 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -100,9 +100,9 @@ def test_bus_creation_bitrate(self) -> None: 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 - ] + xlCanSetChannelBitrate_args = ( + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[0] + ) self.assertEqual(xlCanSetChannelBitrate_args[2], 200000) def test_bus_creation_fd(self) -> None: @@ -152,9 +152,9 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: 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 - ] + 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) From 8be488e66858ac10b867e4cc1d43db9af3e5c689 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 19 Nov 2020 16:32:33 +0100 Subject: [PATCH 0488/1235] revert version bump (it's now in #940) and format repository --- requirements-lint.txt | 8 ++++---- test/test_vector.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index 257c048e5..d0b374c20 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ -pylint==2.6.0 -black==20.8b1 -mypy==0.790 -mypy-extensions==0.4.3 +pylint==2.3.1 +black==19.3b0 +mypy==0.740 +mypy-extensions==0.4.1 diff --git a/test/test_vector.py b/test/test_vector.py index 5bf2f14b4..152d04024 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -100,9 +100,9 @@ def test_bus_creation_bitrate(self) -> None: 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] - ) + xlCanSetChannelBitrate_args = can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[ + 0 + ] self.assertEqual(xlCanSetChannelBitrate_args[2], 200000) def test_bus_creation_fd(self) -> None: @@ -152,9 +152,9 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: 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] - ) + 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) From b07278b214c611791a4c7cae32219ee48a4bf56a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 19 Nov 2020 16:35:53 +0100 Subject: [PATCH 0489/1235] Revert "revert version bump (it's now in #940) and format repository" This reverts commit 8be488e66858ac10b867e4cc1d43db9af3e5c689. --- requirements-lint.txt | 8 ++++---- test/test_vector.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index d0b374c20..257c048e5 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ -pylint==2.3.1 -black==19.3b0 -mypy==0.740 -mypy-extensions==0.4.1 +pylint==2.6.0 +black==20.8b1 +mypy==0.790 +mypy-extensions==0.4.3 diff --git a/test/test_vector.py b/test/test_vector.py index 152d04024..5bf2f14b4 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -100,9 +100,9 @@ def test_bus_creation_bitrate(self) -> None: 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 - ] + xlCanSetChannelBitrate_args = ( + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[0] + ) self.assertEqual(xlCanSetChannelBitrate_args[2], 200000) def test_bus_creation_fd(self) -> None: @@ -152,9 +152,9 @@ def test_bus_creation_fd_bitrate_timings(self) -> None: 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 - ] + 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) From dd42c283121bf8b9d77a351f36e8fc29f9bcceca Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 20 Nov 2020 16:16:17 +0100 Subject: [PATCH 0490/1235] update travis CI --- .travis.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 843bb9312..f62bb2425 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,35 +1,35 @@ language: python # Linux setup -dist: xenial +dist: focal cache: directories: - "$HOME/.cache/pip" python: - # CPython; only 3.6 is supported + # CPython - "3.6" - "3.7" - "3.8" + - "3.9" + - "3.10-dev" - nightly # PyPy: - pypy3 -env: - - install: - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - - python setup.py install + - travis_retry python setup.py install script: - | # install tox - pip install tox + travis_retry pip install tox # Run the tests tox -e travis +# in addition to the above jobs also run: jobs: allow_failures: # we allow all dev & nightly builds to fail, since these python versions might @@ -55,12 +55,12 @@ jobs: # testing on OSX - stage: test os: osx - osx_image: xcode8.3 - python: 3.6-dev + osx_image: xcode11.2 # Python 3.7.4 running on macOS 10.14.4 + language: shell # 'language: python' is an error on Travis CI macOS - stage: documentation name: "Sphinx Build" - python: "3.7" + python: "3.9" before_install: - travis_retry pip install -r doc/doc-requirements.txt script: @@ -70,7 +70,7 @@ jobs: - python -m sphinx -an doc build - stage: linter name: "Linter Checks" - python: "3.7" + python: "3.9" before_install: - travis_retry pip install -r requirements-lint.txt script: @@ -113,14 +113,14 @@ jobs: examples/**.py - stage: linter name: "Formatting Checks" - python: "3.7" + python: "3.9" before_install: - travis_retry pip install -r requirements-lint.txt script: - black --check --verbose . - stage: deploy name: "PyPi Deployment" - python: "3.7" + python: "3.9" deploy: provider: pypi user: hardbyte From 1948d6c505c60a31b2674064c2d6d99899a0be26 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 21 Nov 2020 17:06:45 +0100 Subject: [PATCH 0491/1235] Update can/interfaces/slcan.py Fix black formatter --- can/interfaces/slcan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 93b6e8d8d..30f346697 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -256,7 +256,9 @@ def fileno(self) -> int: try: return self.serialPortOrig.fileno() except io.UnsupportedOperation: - raise NotImplementedError("fileno is not implemented using current CAN bus on this platform") + raise NotImplementedError( + "fileno is not implemented using current CAN bus on this platform" + ) def get_version( self, timeout: Optional[float] From e90dd9f170ce09e06b599fad3cb717cd5f2c4783 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 21 Nov 2020 18:44:52 +0100 Subject: [PATCH 0492/1235] move "filelock" to neovi dependencies --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 8dc42bc46..534aa561e 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ extras_require = { "seeedstudio": ["pyserial>=3.0"], "serial": ["pyserial~=3.0"], - "neovi": ["python-ics>=2.12"], + "neovi": ["filelock", "python-ics>=2.12"], "cantact": ["cantact>=0.0.7"], "gs_usb": ["gs_usb>=0.2.1"], } @@ -83,7 +83,6 @@ "setuptools", "wrapt~=1.10", 'windows-curses;platform_system=="Windows"', - "filelock", "mypy_extensions >= 0.4.0, < 0.5.0", 'pywin32;platform_system=="Windows"', ], From 163805ea8505e35944c4a3cd6cc565e39c7dcb4b Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Sat, 21 Nov 2020 11:35:36 -0700 Subject: [PATCH 0493/1235] removes whitespace from mypy_extension in install_requires. The whitespace would cause an issue where the module would not be installed resulting in an error when importing python-can. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 16cb4b599..18322913c 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ "wrapt~=1.10", 'windows-curses;platform_system=="Windows"', "filelock", - "mypy_extensions >= 0.4.0, < 0.5.0", + "mypy_extensions>=0.4.0,<0.5.0", 'pywin32;platform_system=="Windows"', ], extras_require=extras_require, From 8555fb4ab46e3cdb520f23f6770fae5363205004 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 26 Nov 2020 08:32:55 +1300 Subject: [PATCH 0494/1235] Process auto black (#950) Add black github workflow to run black and commits changed code. --- .github/workflows/format-code.yml | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/format-code.yml diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml new file mode 100644 index 000000000..2bde298e2 --- /dev/null +++ b/.github/workflows/format-code.yml @@ -0,0 +1,32 @@ +name: Format Code + +on: + pull_request: + paths: + - '**.py' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-lint.txt + - name: Code Format Check with Black + run: | + black --verbose . + - name: Commit Formated Code + uses: EndBug/add-and-commit@v5 + env: + # This is necessary in order to push a commit to the repo + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + message: "Format code with black" + # Ref https://git-scm.com/docs/git-add#_examples + add: './*.py' From 9c3fc16e8f58bd8aea9a2c5f768184d46f37182e Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Wed, 25 Nov 2020 12:34:20 -0700 Subject: [PATCH 0495/1235] removes setuptools from install requires (#945) It would be rather redundant to have setuptools in install requires when setuptools is being imported at the top of the setup.py file. --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0bf3ff4f6..ea1b9f9c3 100644 --- a/setup.py +++ b/setup.py @@ -79,8 +79,9 @@ # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=3.6", install_requires=[ - # Setuptools provides pkg_resources which python-can makes use of. - "setuptools", + # Note setuptools provides pkg_resources which python-can makes use of, + # but we assume it is already installed. + # "setuptools", "wrapt~=1.10", 'windows-curses;platform_system=="Windows"', "mypy_extensions>=0.4.0,<0.5.0", From 01bb3e974ccee115e38c102d651264309bb60098 Mon Sep 17 00:00:00 2001 From: karl ding Date: Wed, 25 Nov 2020 11:51:04 -0800 Subject: [PATCH 0496/1235] Add Travis CI support for arm64 architectures (#705) Add support for the arm64 architecture from Travis CI's alpha offering. This enables the test suite to run within an arm64 LXD container (minus the tests that exercise SocketCAN, as the image is missing the required kernel modules). --- .travis.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.travis.yml b/.travis.yml index 843bb9312..8098383ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,11 +47,29 @@ jobs: - stage: test name: Socketcan os: linux + arch: amd64 dist: trusty python: "3.6" sudo: required env: TEST_SOCKETCAN=TRUE + # arm64 builds + - stage: test + name: Linux arm64 + os: linux + arch: arm64 + language: generic + sudo: required + addons: + apt: + update: true + env: HOST_ARM64=TRUE + before_install: + - sudo apt install -y python3 python3-pip + # Travis doesn't seem to provide Python binaries yet for this arch + - sudo update-alternatives --install /usr/bin/python python $(which python3) 10 + - sudo update-alternatives --install /usr/bin/pip pip $(which pip3) 10 + # testing on OSX - stage: test os: osx From d603a7a3e371d50bb5ac05e2a3e9e81faa2b9132 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 26 Nov 2020 08:59:59 +1300 Subject: [PATCH 0497/1235] Allow arm64 builds to fail on travis-ci --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8098383ed..580b0c5db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,6 +35,8 @@ jobs: # we allow all dev & nightly builds to fail, since these python versions might # still be very unstable - python: nightly + # Allow arm64 builds to fail + - arch: arm64 include: # Note no matrix support when using stages. From 974d8a8bd97e75cf6656e6396373edc2b4d1a914 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 26 Nov 2020 22:02:38 +1300 Subject: [PATCH 0498/1235] Run format workflow on push rather than PR --- .github/workflows/format-code.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index 2bde298e2..da0f7ba4f 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -1,7 +1,7 @@ name: Format Code on: - pull_request: + push: paths: - '**.py' From 24f5b262ea4e49307fdf048ecb39c387a7d97a58 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 26 Nov 2020 10:24:55 +0100 Subject: [PATCH 0499/1235] move most tests from travis to gitlab actions --- .github/workflows/build.yml | 4 ++-- .travis.yml | 46 +++++-------------------------------- 2 files changed, 8 insertions(+), 42 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 06e72125e..a84a6bd26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,11 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha, pypy3] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.travis.yml b/.travis.yml index f62bb2425..011e115af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,42 +7,15 @@ cache: directories: - "$HOME/.cache/pip" -python: - # CPython - - "3.6" - - "3.7" - - "3.8" - - "3.9" - - "3.10-dev" - - nightly - # PyPy: - - pypy3 - install: - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - travis_retry python setup.py install -script: - - | - # install tox - travis_retry pip install tox - # Run the tests - tox -e travis - -# in addition to the above jobs also run: jobs: - allow_failures: - # we allow all dev & nightly builds to fail, since these python versions might - # still be very unstable - - python: nightly - include: - # Note no matrix support when using stages. # Stages with the same name get run in parallel. # Jobs within a stage can also be named. - # Unit Testing Stage - # testing socketcan on Trusty & Python 3.6, since it is not available on Xenial - stage: test name: Socketcan @@ -51,12 +24,12 @@ jobs: python: "3.6" sudo: required env: TEST_SOCKETCAN=TRUE - - # testing on OSX - - stage: test - os: osx - osx_image: xcode11.2 # Python 3.7.4 running on macOS 10.14.4 - language: shell # 'language: python' is an error on Travis CI macOS + script: + - | + # install tox + travis_retry pip install tox + # Run the tests + tox -e travis - stage: documentation name: "Sphinx Build" @@ -111,13 +84,6 @@ jobs: can/io/**.py scripts/**.py examples/**.py - - stage: linter - name: "Formatting Checks" - python: "3.9" - before_install: - - travis_retry pip install -r requirements-lint.txt - script: - - black --check --verbose . - stage: deploy name: "PyPi Deployment" python: "3.9" From dab3c8618a4b766fff1014bc50eb19184dba1e13 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 26 Nov 2020 09:28:44 +0000 Subject: [PATCH 0500/1235] Format code with black --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ea1b9f9c3..88b83ce72 100644 --- a/setup.py +++ b/setup.py @@ -79,7 +79,7 @@ # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=3.6", install_requires=[ - # Note setuptools provides pkg_resources which python-can makes use of, + # Note setuptools provides pkg_resources which python-can makes use of, # but we assume it is already installed. # "setuptools", "wrapt~=1.10", From 61b5e164d9714dc88913536ec90a831b316c81f1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 26 Nov 2020 10:30:12 +0100 Subject: [PATCH 0501/1235] fix python version 3.10.0-alpha.2 on github actions --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a84a6bd26..e1a3fe8c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha, pypy3] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha.2, pypy3] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From b0273d93ab2c5a050a876b2fa7d347d4aa3ff7d7 Mon Sep 17 00:00:00 2001 From: tojoh511 <75361997+tojoh511@users.noreply.github.com> Date: Wed, 2 Dec 2020 10:39:54 +0100 Subject: [PATCH 0502/1235] Extract constant for start of file date format in ASCWriter (#955) --- can/io/asc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index f28806478..d646a1924 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -246,6 +246,7 @@ class ASCWriter(BaseIOHandler, Listener): "{bit_timing_conf_ext_data:>8}", ] ) + FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" FORMAT_DATE = "%a %b %d %I:%M:%S.{} %p %Y" FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" @@ -268,7 +269,7 @@ def __init__( self.channel = channel # write start of file header - now = datetime.now().strftime("%a %b %d %I:%M:%S.%f %p %Y") + now = datetime.now().strftime(self.FORMAT_START_OF_FILE_DATE) self.file.write("date %s\n" % now) self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") From e9b9ceac5023102e90721b758efba45d00409c8d Mon Sep 17 00:00:00 2001 From: tojoh511 <75361997+tojoh511@users.noreply.github.com> Date: Wed, 2 Dec 2020 09:40:35 +0000 Subject: [PATCH 0503/1235] Format code with black --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ea1b9f9c3..88b83ce72 100644 --- a/setup.py +++ b/setup.py @@ -79,7 +79,7 @@ # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=3.6", install_requires=[ - # Note setuptools provides pkg_resources which python-can makes use of, + # Note setuptools provides pkg_resources which python-can makes use of, # but we assume it is already installed. # "setuptools", "wrapt~=1.10", From ad7a9080f6f438b478e1ab8a68bc1c52fafa41d6 Mon Sep 17 00:00:00 2001 From: s0kr4t3s <46361381+s0kr4t3s@users.noreply.github.com> Date: Wed, 2 Dec 2020 11:38:06 +0100 Subject: [PATCH 0504/1235] solved issue #954 --- can/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/util.py b/can/util.py index c9c007b2b..021ad766f 100644 --- a/can/util.py +++ b/can/util.py @@ -195,7 +195,7 @@ def load_config( if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) if "fd" in config: - config["fd"] = config["fd"] not in ("0", "False", "false") + config["fd"] = config["fd"] not in ("0", "False", "false", False) if "data_bitrate" in config: config["data_bitrate"] = int(config["data_bitrate"]) From 7ddea9e17f26425fbe943e3151e0e47baf0d5a22 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 13 Dec 2020 21:09:49 +0100 Subject: [PATCH 0505/1235] Add udp multicast bus (#644) Adds a virtual bus interface udp_multicast that allows multiple processes - potentially on different hosts - to exchange CAN data. This is achieved by serializing the CAN frames using MessagePack, wrapping them in IPv6/4 packets and sending them to a multicast group. The TTL of the IP packets is set to one by default, disallowing the packet to escape the localhost. It is configurable however, and would allow the communicating processes to run on different machines as well. --- .travis.yml | 1 + can/interfaces/__init__.py | 1 + can/interfaces/udp_multicast/__init__.py | 3 + can/interfaces/udp_multicast/bus.py | 328 +++++++++++++++++++++++ can/interfaces/udp_multicast/utils.py | 67 +++++ can/interfaces/virtual.py | 24 +- can/typechecking.py | 2 + doc/development.rst | 5 +- doc/installation.rst | 2 +- doc/interfaces.rst | 23 +- doc/interfaces/udp_multicast.rst | 62 +++++ doc/interfaces/virtual.rst | 92 ++++++- setup.py | 1 + test/back2back_test.py | 120 +++++++-- test/config.py | 2 +- test/test_detect_available_configs.py | 21 +- 16 files changed, 692 insertions(+), 62 deletions(-) create mode 100644 can/interfaces/udp_multicast/__init__.py create mode 100644 can/interfaces/udp_multicast/bus.py create mode 100644 can/interfaces/udp_multicast/utils.py create mode 100644 doc/interfaces/udp_multicast.rst diff --git a/.travis.yml b/.travis.yml index 580b0c5db..19cc10f16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -117,6 +117,7 @@ jobs: can/broadcastmanager.py can/bus.py can/interface.py + can/interfaces/udp_multicast/**.py can/interfaces/slcan.py can/interfaces/socketcan/**.py can/interfaces/virtual.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 2fb1afce9..5d6942a8a 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -17,6 +17,7 @@ "nican": ("can.interfaces.nican", "NicanBus"), "iscan": ("can.interfaces.iscan", "IscanBus"), "virtual": ("can.interfaces.virtual", "VirtualBus"), + "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"), "neovi": ("can.interfaces.ics_neovi", "NeoViBus"), "vector": ("can.interfaces.vector", "VectorBus"), "slcan": ("can.interfaces.slcan", "slcanBus"), diff --git a/can/interfaces/udp_multicast/__init__.py b/can/interfaces/udp_multicast/__init__.py new file mode 100644 index 000000000..0ce1ce389 --- /dev/null +++ b/can/interfaces/udp_multicast/__init__.py @@ -0,0 +1,3 @@ +"""A module to allow CAN over UDP on IPv4/IPv6 multicast.""" + +from .bus import UdpMulticastBus diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py new file mode 100644 index 000000000..f4f0e5fd8 --- /dev/null +++ b/can/interfaces/udp_multicast/bus.py @@ -0,0 +1,328 @@ +import logging +import select +import socket +import struct + +from typing import List, Optional, Tuple, Union + +log = logging.getLogger(__name__) + +import can +from can import BusABC +from can.typechecking import AutoDetectedConfig + +from .utils import pack_message, unpack_message, check_msgpack_installed + + +# see socket.getaddrinfo() +IPv4_ADDRESS_INFO = Tuple[str, int] # address, port +IPv6_ADDRESS_INFO = Tuple[str, int, int, int] # address, port, flowinfo, scope_id +IP_ADDRESS_INFO = Union[IPv4_ADDRESS_INFO, IPv6_ADDRESS_INFO] + +# Additional constants for the interaction with Unix kernels +SO_TIMESTAMPNS = 35 + + +class UdpMulticastBus(BusABC): + """A virtual interface for CAN communications between multiple processes using UDP over Multicast IP. + + It supports IPv4 and IPv6, specified via the channel (which really is just a multicast IP address as a + string). You can also specify the port and the IPv6 *hop limit*/the IPv4 *time to live* (TTL). + + This bus does not support filtering based on message IDs on the kernel level but instead provides it in + user space (in Python) as a fallback. + + Both default addresses should allow for multi-host CAN networks in a normal local area network (LAN) where + multicast is enabled. + + .. note:: + The auto-detection of available interfaces (see) is implemented using heuristic that checks if the + required socket operations are available. It then returns two configurations, one based on + the :attr:`~UdpMulticastBus.DEFAULT_GROUP_IPv6` address and another one based on + the :attr:`~UdpMulticastBus.DEFAULT_GROUP_IPv4` address. + + .. warning:: + The parameter `receive_own_messages` is currently unsupported and setting it to `True` will raise an + exception. + + .. warning:: + This interface does not make guarantees on reliable delivery and message ordering, and also does not + implement rate limiting or ID arbitration/prioritization under high loads. Please refer to the section + :ref:`other_virtual_interfaces` for more information on this and a comparison to alternatives. + + :param channel: A multicast IPv4 address (in `224.0.0.0/4`) or an IPv6 address (in `ff00::/8`). + This defines which version of IP is used. See + `Wikipedia ("Multicast address") `__ + for more details on the addressing schemes. + Defaults to :attr:`~UdpMulticastBus.DEFAULT_GROUP_IPv6`. + :param port: The IP port to read from and write to. + :param hop_limit: The hop limit in IPv6 or in IPv4 the time to live (TTL). + :param receive_own_messages: If transmitted messages should also be received by this bus. + CURRENTLY UNSUPPORTED. + :param fd: + If CAN-FD frames should be supported. If set to false, an error will be raised upon sending such a + frame and such received frames will be ignored. + :param can_filters: See :meth:`~can.BusABC.set_filters`. + + :raises RuntimeError: If the *msgpack*-dependency is not available. It should be installed on all + non Windows platforms via the `setup.py` requirements. + :raises NotImplementedError: If the `receive_own_messages` is passed as `True`. + """ + + #: An arbitrary IPv6 multicast address with "site-local" scope, i.e. only to be routed within the local + #: physical network and not beyond it. It should allow for multi-host CAN networks in a normal IPv6 LAN. + #: This is the default channel and should work with most modern routers if multicast is allowed. + DEFAULT_GROUP_IPv6 = "ff15:7079:7468:6f6e:6465:6d6f:6d63:6173" + + #: An arbitrary IPv4 multicast address with "administrative" scope, i.e. only to be routed within + #: administrative organizational boundaries and not beyond it. + #: It should allow for multi-host CAN networks in a normal IPv4 LAN. + #: This is provided as a default fallback channel if IPv6 is (still) not supported. + DEFAULT_GROUP_IPv4 = "239.74.163.2" + + def __init__( + self, + channel: str = DEFAULT_GROUP_IPv6, + port: int = 43113, + hop_limit: int = 1, + receive_own_messages: bool = False, + fd: bool = True, + **kwargs, + ) -> None: + check_msgpack_installed() + + if receive_own_messages: + raise NotImplementedError("receiving own messages is not yet implemented") + + super().__init__(channel, **kwargs) + + self.is_fd = fd + self._multicast = GeneralPurposeUdpMulticastBus(channel, port, hop_limit) + + def _recv_internal(self, timeout: Optional[float]): + result = self._multicast.recv(timeout) + if not result: + return None, False + + data, _, timestamp = result + can_message = unpack_message(data, replace={"timestamp": timestamp}) + + if not self.is_fd and can_message.is_fd: + return None, False + + return can_message, False + + def send(self, message: can.Message, timeout: Optional[float] = None) -> None: + if not self.is_fd and message.is_fd: + raise RuntimeError("cannot send FD message over bus with CAN FD disabled") + + data = pack_message(message) + self._multicast.send(data, timeout) + + def fileno(self) -> int: + """Provides the internally used file descriptor of the socket or `-1` if not available.""" + return self._multicast.fileno() + + def shutdown(self) -> None: + """Close all sockets and free up any resources. + + Never throws errors and only logs them. + """ + self._multicast.shutdown() + + @staticmethod + def _detect_available_configs() -> List[AutoDetectedConfig]: + if hasattr(socket, "CMSG_SPACE"): + return [ + { + "interface": "udp_multicast", + "channel": UdpMulticastBus.DEFAULT_GROUP_IPv6, + }, + { + "interface": "udp_multicast", + "channel": UdpMulticastBus.DEFAULT_GROUP_IPv4, + }, + ] + + # else, this interface cannot be used + return [] + + +class GeneralPurposeUdpMulticastBus: + """A general purpose send and receive handler for multicast over IP/UDP.""" + + def __init__( + self, group: str, port: int, hop_limit: int, max_buffer: int = 4096 + ) -> None: + self.group = group + self.port = port + self.hop_limit = hop_limit + self.max_buffer = max_buffer + + # Look up multicast group address in name server and find out IP version of the first suitable target + # and then get the address family of it (socket.AF_INET or socket.AF_INET6) + connection_candidates = socket.getaddrinfo( # type: ignore + group, self.port, type=socket.SOCK_DGRAM + ) + sock = None + for connection_candidate in connection_candidates: + address_family: socket.AddressFamily = connection_candidate[0] + self.ip_version = 4 if address_family == socket.AF_INET else 6 + try: + sock = self._create_socket(address_family) + except OSError as error: + log.info( + f"could not connect to the multicast IP network of candidate %s; reason: {error}", + connection_candidates, + ) + if sock is not None: + self._socket = sock + else: + raise RuntimeError("could not connect to a multicast IP network") + + # used in recv() + self.received_timestamp_struct = "@II" + ancillary_data_size = struct.calcsize(self.received_timestamp_struct) + self.received_ancillary_buffer_size = socket.CMSG_SPACE(ancillary_data_size) + + # used by send() + self._send_destination = (self.group, self.port) + self._last_send_timeout: Optional[float] = None + + def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: + """Creates a new socket. This might fail and raise an exception! + + :param address_family: whether this is of type `socket.AF_INET` or `socket.AF_INET6` + :raises OSError: if the socket could not be opened or configured correctly; in this case, it is + guaranteed to be closed/cleaned up + """ + # create the UDP socket + # this might already fail but then there is nothing to clean up + sock = socket.socket(address_family, socket.SOCK_DGRAM) + + # configure the socket + try: + + # set hop limit / TTL + ttl_as_binary = struct.pack("@I", self.hop_limit) + if self.ip_version == 4: + sock.setsockopt( + socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl_as_binary + ) + else: + sock.setsockopt( + socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, ttl_as_binary + ) + + # Allow multiple programs to access that address + port + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + # set how to receive timestamps + sock.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) + + # Bind it to the port (on any interface) + sock.bind(("", self.port)) + + # Join the multicast group + group_as_binary = socket.inet_pton(address_family, self.group) + if self.ip_version == 4: + request = group_as_binary + struct.pack("@I", socket.INADDR_ANY) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, request) + else: + request = group_as_binary + struct.pack("@I", 0) + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, request) + + return sock + + except OSError as error: + # clean up the incompletely configured but opened socket + try: + sock.close() + except OSError as close_error: + # ignore but log any failures in here + log.warning("Could not close partly configured socket: %s", close_error) + + # still raise the error + raise error + + def send(self, data: bytes, timeout: Optional[float] = None) -> None: + """Send data to all group members. This call blocks. + + :param timeout: the timeout in seconds after which an Exception is raised is sending has failed + :param data: the data to be sent + :raises OSError: if an error occurred while writing to the underlying socket + :raises socket.timeout: if the timeout ran out before sending was completed (this is a subclass of + *OSError*) + """ + if timeout != self._last_send_timeout: + self._last_send_timeout = timeout + # this applies to all blocking calls on the socket, but sending is the only one that is blocking + self._socket.settimeout(timeout) + + bytes_sent = self._socket.sendto(data, self._send_destination) + if bytes_sent < len(data): + raise socket.timeout() + + def recv( + self, timeout: Optional[float] = None + ) -> Optional[Tuple[bytes, IP_ADDRESS_INFO, float]]: + """ + Receive up to **max_buffer** bytes. + + :param timeout: the timeout in seconds after which `None` is returned if no data arrived + :returns: `None` on timeout, or a 3-tuple comprised of: + - received data, + - the sender of the data, and + - a timestamp in seconds + """ + # get all sockets that are ready (can be a list with a single value + # being self.socket or an empty list if self.socket is not ready) + try: + # get all sockets that are ready (can be a list with a single value + # being self.socket or an empty list if self.socket is not ready) + ready_receive_sockets, _, _ = select.select([self._socket], [], [], timeout) + except socket.error as exc: + # something bad (not a timeout) happened (e.g. the interface went down) + raise can.CanError(f"Failed to wait for IP/UDP socket: {exc}") + + if ready_receive_sockets: # not empty + # fetch data & source address + ( + raw_message_data, + ancillary_data, + _, # flags + sender_address, + ) = self._socket.recvmsg( + self.max_buffer, self.received_ancillary_buffer_size + ) + + # fetch timestamp; this is configured in in _create_socket() + assert len(ancillary_data) == 1, "only requested a single extra field" + cmsg_level, cmsg_type, cmsg_data = ancillary_data[0] + assert ( + cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS + ), "received control message type that was not requested" + # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details + seconds, nanoseconds = struct.unpack( + self.received_timestamp_struct, cmsg_data + ) + timestamp = seconds + nanoseconds * 1.0e-9 + + return raw_message_data, sender_address, timestamp + + # socket wasn't readable or timeout occurred + return None + + def fileno(self) -> int: + """Provides the internally used file descriptor of the socket or `-1` if not available.""" + return self._socket.fileno() + + def shutdown(self) -> None: + """Close all sockets and free up any resources. + + Never throws errors and only logs them. + """ + try: + self._socket.close() + except OSError as exception: + log.error("could not close IP socket: %s", exception) diff --git a/can/interfaces/udp_multicast/utils.py b/can/interfaces/udp_multicast/utils.py new file mode 100644 index 000000000..dad368f0b --- /dev/null +++ b/can/interfaces/udp_multicast/utils.py @@ -0,0 +1,67 @@ +""" +Defines common functions. +""" + +from typing import Any +from typing import Dict +from typing import Optional + +from can import Message +from can.typechecking import ReadableBytesLike + +try: + import msgpack +except ImportError: + msgpack = None + + +def check_msgpack_installed() -> None: + """Raises a `RuntimeError` if `msgpack` is not installed.""" + if msgpack is None: + raise RuntimeError("msgpack not installed") + + +def pack_message(message: Message) -> bytes: + """ + Pack a can.Message into a msgpack byte blob. + + :param message: the message to be packed + """ + check_msgpack_installed() + as_dict = { + "timestamp": message.timestamp, + "arbitration_id": message.arbitration_id, + "is_extended_id": message.is_extended_id, + "is_remote_frame": message.is_remote_frame, + "is_error_frame": message.is_error_frame, + "channel": message.channel, + "dlc": message.dlc, + "data": message.data, + "is_fd": message.is_fd, + "bitrate_switch": message.bitrate_switch, + "error_state_indicator": message.error_state_indicator, + } + return msgpack.packb(as_dict, use_bin_type=True) + + +def unpack_message( + data: ReadableBytesLike, + replace: Optional[Dict[str, Any]] = None, + check: bool = False, +) -> Message: + """Unpack a can.Message from a msgpack byte blob. + + :param data: the raw data + :param replace: a mapping from field names to values to be replaced after decoding the new message, or + `None` to disable this feature + :param check: this is passed to :meth:`can.Message.__init__` to specify whether to validate the message + + :raise TypeError: if the data contains key that are not valid arguments for :meth:`can.Message.__init__` + :raise ValueError: if `check` is true and the message metadata is invalid in some way + :raise Exception: if there was another problem while unpacking + """ + check_msgpack_installed() + as_dict = msgpack.unpackb(data, raw=False) + if replace is not None: + as_dict.update(replace) + return Message(check=check, **as_dict) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 937084095..a00e60d71 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -5,8 +5,8 @@ Any VirtualBus instances connecting to the same channel and reside in the same process will receive the same messages. """ + from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING -from can import typechecking from copy import deepcopy import logging @@ -33,8 +33,7 @@ class VirtualBus(BusABC): """ - A virtual CAN bus using an internal message queue. It can be - used for example for testing. + A virtual CAN bus using an internal message queue. It can be used for example for testing. In this interface, a channel is an arbitrary object used as an identifier for connected buses. @@ -47,6 +46,11 @@ class VirtualBus(BusABC): The timeout when sending a message applies to each receiver individually. This means that sending can block up to 5 seconds if a message is sent to 5 receivers with the timeout set to 1.0. + + .. warning:: + This interface guarantees reliable delivery and message ordering, but does *not* implement rate + limiting or ID arbitration/prioritization under high loads. Please refer to the section + :ref:`other_virtual_interfaces` for more information on this and a comparison to alternatives. """ def __init__( @@ -116,15 +120,15 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: raise CanError("Could not send message to one or more recipients") def shutdown(self) -> None: - self._check_if_open() - self._open = False + if self._open: + self._open = False - with channels_lock: - self.channel.remove(self.queue) + with channels_lock: + self.channel.remove(self.queue) - # remove if empty - if not self.channel: - del channels[self.channel_id] + # remove if empty + if not self.channel: + del channels[self.channel_id] @staticmethod def _detect_available_configs(): diff --git a/can/typechecking.py b/can/typechecking.py index 50070b01e..4c81f0ea7 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -36,3 +36,5 @@ AutoDetectedConfig = mypy_extensions.TypedDict( "AutoDetectedConfig", {"interface": str, "channel": Channel} ) + +ReadableBytesLike = typing.Union[bytes, bytearray, memoryview] diff --git a/doc/development.rst b/doc/development.rst index 3bad8fc9b..87ca353c1 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -69,11 +69,10 @@ These steps are a guideline on how to add a new backend to python-can. - Implement the central part of the backend: the bus class that extends :class:`can.BusABC`. See :ref:`businternals` for more info on this one! -- Register your backend bus class in ``can.interface.BACKENDS`` and - ``can.interfaces.VALID_INTERFACES`` in ``can.interfaces.__init__.py``. +- Register your backend bus class in ``BACKENDS`` in the file ``can.interfaces.__init__.py``. - Add docs where appropriate. At a minimum add to ``doc/interfaces.rst`` and add a new interface specific document in ``doc/interface/*``. -- Update ``doc/scripts.rst`` accordingly. + Also, don't forget to document your classes, methods and function with docstrings. - Add tests in ``test/*`` where appropriate. diff --git a/doc/installation.rst b/doc/installation.rst index e97427cf2..710618cf7 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -98,7 +98,7 @@ To install ``python-can`` using the CANtact driver backend: ``python3 -m pip install "python-can[cantact]"`` -If ``python-can`` is already installed, the CANtact backend can be installed seperately: +If ``python-can`` is already installed, the CANtact backend can be installed separately: ``python3 -m pip install cantact`` diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 2541f4610..80016f287 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -11,23 +11,24 @@ The available interfaces are: .. toctree:: :maxdepth: 1 - interfaces/socketcan + interfaces/canalystii + interfaces/gs_usb + interfaces/iscan + interfaces/ixxat interfaces/kvaser + interfaces/udp_multicast + interfaces/neovi + interfaces/nican + interfaces/pcan + interfaces/robotell + interfaces/seeedstudio interfaces/serial interfaces/slcan - interfaces/robotell - interfaces/ixxat - interfaces/pcan + interfaces/socketcan + interfaces/systec interfaces/usb2can - interfaces/nican - interfaces/iscan - interfaces/neovi interfaces/vector interfaces/virtual - interfaces/canalystii - interfaces/systec - interfaces/seeedstudio - interfaces/gs_usb 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/udp_multicast.rst b/doc/interfaces/udp_multicast.rst new file mode 100644 index 000000000..be5882f20 --- /dev/null +++ b/doc/interfaces/udp_multicast.rst @@ -0,0 +1,62 @@ +.. _udp_multicast_doc: + +Multicast IP Interface +====================== + +This module implements transport of CAN and CAN FD messages over UDP via Multicast IPv4 and IPv6. +This virtual interface allows for communication between multiple processes and even hosts. +This differentiates it from the :ref:`virtual_interface_doc` interface, +which can only passes messages within a single process but does not require a network stack. + +It runs on UDP to have the lowest possible latency (as opposed to using TCP), and because +normal IP multicast is inherently unreliable, as the recipients are unknown. +This enables ad-hoc networks that do not require a central server but is also a so-called +*unreliable network*. In practice however, local area networks (LANs) should most often be +sufficiently reliable for this interface to function properly. + +.. note:: + For an overview over the different virtual buses in this library and beyond, please refer + to the section :ref:`other_virtual_interfaces`. It also describes important limitations + of this interface. + +Please refer to the `Bus class documentation`_ below for configuration options and useful resources +for specifying multicast IP addresses. + +Supported Platforms +------------------- + +It should work on most Unix systems (including Linux with kernel 2.6.22+) but currently not on Windows. + +Example +------- + +This example should print a single line indicating that a CAN message was successfully sent +from ``bus_1`` to ``bus_2``: + +.. code-block:: python + + import time + import can + from can.interfaces.udp_multicast import UdpMulticastBus + + # The bus can be created using the can.Bus wrapper class or using UdpMulticastBus directly + with can.Bus(channel=UdpMulticastBus.DEFAULT_GROUP_IPv6, bustype='udp_multicast') as bus_1, \ + UdpMulticastBus(channel=UdpMulticastBus.DEFAULT_GROUP_IPv6) as bus_2: + + # register a callback on the second bus that prints messages to the standard out + notifier = can.Notifier(bus_2, [can.Printer()]) + + # create and send a message with the first bus, which should arrive at the second one + message = can.Message(arbitration_id=0x123, data=[1, 2, 3]) + bus_1.send(message) + + # give the notifier enough time to get triggered by the second bus + time.sleep(2.0) + + +Bus Class Documentation +----------------------- + +.. autoclass:: can.interfaces.udp_multicast.UdpMulticastBus + :members: + :exclude-members: send diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst index ed6681a57..f583cee65 100644 --- a/doc/interfaces/virtual.rst +++ b/doc/interfaces/virtual.rst @@ -1,13 +1,88 @@ +.. _virtual_interface_doc: + Virtual ======= -The virtual interface can be used as a way to write OS and driver independent -tests. +The virtual interface can be used as a way to write OS and driver independent tests. +Any `VirtualBus` instances connecting to the same channel (from within the same Python +process) will receive each others messages. + +If messages shall be sent across process or host borders, consider using the +:ref:`udp_multicast_doc` and refer to (:ref:`the next section `) +for a comparison and general discussion of different virtual interfaces. + +.. _other_virtual_interfaces: + +Other Virtual Interfaces +------------------------ + +There are quite a few implementations for CAN networks that do not require physical +CAN hardware. +This section also describes common limitations of current virtual interfaces. + +Comparison +'''''''''' + +The following table compares some known virtual interfaces: + ++----------------------------------------------------+-----------------------------------------------------------------------+---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ +| **Name** | **Availability** | **Applicability** | **Implementation** | +| | +-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| | | **Within | **Between | **Via (IP) | **Without Central | **Transport | **Serialization | +| | | Process** | Processes** | Networks** | Server** | Technology** | Format** | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| ``virtual`` (this) | *included* | ✓ | ✗ | ✗ | ✓ | Singleton & Mutex | none | +| | | | | | | (reliable) | | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| ``udp_multicast`` (:ref:`doc `) | *included* | ✓ | ✓ | ✓ | ✓ | UDP via IP multicast | custom using `msgpack `__ | +| | | | | | | (unreliable) | | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| *christiansandberg/ | `external `__ | ✓ | ✓ | ✓ | ✗ | Websockets via TCP/IP | custom binary | +| python-can-remote* | | | | | | (reliable) | | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| *windelbouwman/ | `external `__ | ✓ | ✓ | ✓ | ✗ | `ZeroMQ `__ via TCP/IP | custom binary [#f1]_ | +| virtualcan* | | | | | | (reliable) | | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ -A virtual CAN bus that can be used for automatic tests. Any Bus instances -connecting to the same channel (in the same python program) will get each -others messages. +.. [#f1] + The only option in this list that implements interoperability with other languages + out of the box. For the others (except the first intra-process one), other programs written + in potentially different languages could effortlessly interface with the bus + once they mimic the serialization format. The last one, however, has already implemented + the entire bus functionality in *C++* and *Rust*, besides the Python variant. +Common Limitations +'''''''''''''''''' + +**Guaranteed delivery** and **message ordering** is one major point of difference: +While in a physical CAN network, a message is either sent or in queue (or an explicit error occurred), +this may not be the case for virtual networks. +The ``udp_multicast`` bus for example, drops this property for the benefit of lower +latencies by using unreliable UDP/IP instead of reliable TCP/IP (and because normal IP multicast +is inherently unreliable, as the recipients are unknown by design). The other three buses faithfully +model a physical CAN network in this regard: They ensure that all recipients actually receive +(and acknowledge each message), much like in a physical CAN network. They also ensure that +messages are relayed in the order they have arrived at the central server and that messages +arrive at the recipients exactly once. Both is not guaranteed to hold for the best-effort +``udp_multicast`` bus as it uses UDP/IP as a transport layer. + +**Central servers** are, however, required by interfaces 3 and 4 (the external tools) to provide +these guarantees of message delivery and message ordering. The central servers receive and distribute +the CAN messages to all other bus participants, unlike in a real physical CAN network. +The first intra-process ``virtual`` interface only runs within one Python process, effectively the +Python instance of :class:`VirtualBus` acts as a central server. Notably the ``udp_multicast`` bus +does not require a central server. + +**Arbitration and throughput** are two interrelated functions/properties of CAN networks which +are typically abstracted in virtual interfaces. In all four interfaces, an unlimited amount +of messages can be sent per unit of time (given the computational power of the machines and +networks that are involved). In a real CAN/CAN FD networks, however, throughput is usually much +more restricted and prioritization of arbitration IDs is thus an important feature once the bus +is starting to get saturated. None of the interfaces presented above support any sort of throttling +or ID arbitration under high loads. + +Example +------- .. code-block:: python @@ -21,3 +96,10 @@ others messages. msg2 = bus2.recv() assert msg1 == msg2 + + +Bus Class Documentation +----------------------- + +.. autoclass:: can.interfaces.virtual.VirtualBus + :members: diff --git a/setup.py b/setup.py index 88b83ce72..af63b8e90 100644 --- a/setup.py +++ b/setup.py @@ -86,6 +86,7 @@ 'windows-curses;platform_system=="Windows"', "mypy_extensions>=0.4.0,<0.5.0", 'pywin32;platform_system=="Windows"', + 'msgpack~=1.0.0;platform_system!="Windows"', ], extras_require=extras_require, ) diff --git a/test/back2back_test.py b/test/back2back_test.py index 8c1ded6c5..fe33ef213 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -5,23 +5,31 @@ This module tests two virtual buses attached to each other. """ -import sys import unittest from time import sleep from multiprocessing.dummy import Pool as ThreadPool +import random import pytest -import random import can +from can.interfaces.udp_multicast import UdpMulticastBus -from .config import * +from .config import ( + IS_CI, + IS_UNIX, + IS_OSX, + IS_TRAVIS, + TEST_INTERFACE_SOCKETCAN, + TEST_CAN_FD, +) class Back2BackTestCase(unittest.TestCase): - """ - Use two interfaces connected to the same CAN bus and test them against - each other. + """Use two interfaces connected to the same CAN bus and test them against each other. + + This very class declaration runs the test on the *virtual* interface but subclasses can be created for + other buses. """ BITRATE = 500000 @@ -52,7 +60,9 @@ def tearDown(self): self.bus1.shutdown() self.bus2.shutdown() - def _check_received_message(self, recv_msg, sent_msg): + def _check_received_message( + self, recv_msg: can.Message, sent_msg: can.Message + ) -> None: self.assertIsNotNone( recv_msg, "No message was received on %s" % self.INTERFACE_2 ) @@ -66,7 +76,7 @@ def _check_received_message(self, recv_msg, sent_msg): if not sent_msg.is_remote_frame: self.assertSequenceEqual(recv_msg.data, sent_msg.data) - def _send_and_receive(self, msg): + def _send_and_receive(self, msg: can.Message) -> None: # Send with bus 1, receive with bus 2 self.bus1.send(msg) recv_msg = self.bus2.recv(self.TIMEOUT) @@ -82,8 +92,13 @@ def _send_and_receive(self, msg): self._check_received_message(recv_msg, msg) def test_no_message(self): + """Tests that there is no message being received if none was sent.""" self.assertIsNone(self.bus1.recv(0.1)) + def test_multiple_shutdown(self): + """Tests whether shutting down ``bus1`` twice does not throw any errors.""" + self.bus1.shutdown() + @unittest.skipIf( IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server", @@ -125,13 +140,26 @@ def test_dlc_less_than_eight(self): msg = can.Message(is_extended_id=False, arbitration_id=0x300, data=[4, 5, 6]) self._send_and_receive(msg) - def test_message_direction(self): - # Verify that own message received has is_rx set to False while message - # received on the other virtual interfaces have is_rx set to True - if self.INTERFACE_1 != "virtual": - raise unittest.SkipTest( - "Message direction not yet implemented for socketcan" - ) + @unittest.skip( + "TODO: how shall this be treated if sending messages locally? should be done uniformly" + ) + def test_message_is_rx(self): + """Verify that received messages have is_rx set to `False` while messages + received on the other virtual interfaces have is_rx set to `True`. + """ + msg = can.Message( + is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3], is_rx=False + ) + self.bus1.send(msg) + self_recv_msg = self.bus2.recv(self.TIMEOUT) + self.assertIsNotNone(self_recv_msg) + self.assertTrue(self_recv_msg.is_rx) + + @unittest.skip( + "TODO: how shall this be treated if sending messages locally? should be done uniformly" + ) + def test_message_is_rx_receive_own_messages(self): + """The same as `test_message_direction` but testing with `receive_own_messages=True`.""" bus3 = can.Bus( channel=self.CHANNEL_2, bustype=self.INTERFACE_2, @@ -142,23 +170,18 @@ def test_message_direction(self): ) try: msg = can.Message( - is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3] + is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3], is_rx=False ) bus3.send(msg) - recv_msg_bus1 = self.bus1.recv(self.TIMEOUT) - recv_msg_bus2 = self.bus2.recv(self.TIMEOUT) self_recv_msg_bus3 = bus3.recv(self.TIMEOUT) - - self.assertTrue(recv_msg_bus1.is_rx) - self.assertTrue(recv_msg_bus2.is_rx) - self.assertFalse(self_recv_msg_bus3.is_rx) + self.assertTrue(self_recv_msg_bus3.is_rx) finally: bus3.shutdown() def test_unique_message_instances(self): - # Verify that we have a different instances of message for each bus - if self.INTERFACE_1 != "virtual": - raise unittest.SkipTest("Not relevant for socketcan") + """Verify that we have a different instances of message for each bus even with + `receive_own_messages=True`. + """ bus3 = can.Bus( channel=self.CHANNEL_2, bustype=self.INTERFACE_2, @@ -185,14 +208,12 @@ def test_unique_message_instances(self): finally: bus3.shutdown() - @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message(self): msg = can.Message( is_fd=True, is_extended_id=True, arbitration_id=0x56789, data=[0xFF] * 64 ) self._send_and_receive(msg) - @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") def test_fd_message_with_brs(self): msg = can.Message( is_fd=True, @@ -203,6 +224,16 @@ def test_fd_message_with_brs(self): ) self._send_and_receive(msg) + def test_fileno(self): + """Test is the values returned by fileno() are valid.""" + try: + fileno = self.bus1.fileno() + except NotImplementedError: + pass # allow it to be left non-implemented + else: + self.assertIsNotNone(fileno) + self.assertTrue(fileno == -1 or fileno > 0) + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class BasicTestSocketCan(Back2BackTestCase): @@ -213,6 +244,41 @@ class BasicTestSocketCan(Back2BackTestCase): CHANNEL_2 = "vcan0" +# this doesn't even work on Travis CI for macOS; for example, see +# https://travis-ci.org/github/hardbyte/python-can/jobs/745389871 +@unittest.skipUnless( + IS_UNIX and not (IS_TRAVIS and IS_OSX), + "only supported on Unix systems (but not on Travis CI on macOS)", +) +class BasicTestUdpMulticastBusIPv4(Back2BackTestCase): + + INTERFACE_1 = "udp_multicast" + CHANNEL_1 = UdpMulticastBus.DEFAULT_GROUP_IPv4 + INTERFACE_2 = "udp_multicast" + CHANNEL_2 = UdpMulticastBus.DEFAULT_GROUP_IPv4 + + def test_unique_message_instances(self): + with self.assertRaises(NotImplementedError): + super().test_unique_message_instances() + + +# this doesn't even work for loopback multicast addresses on Travis CI; for example, see +# https://travis-ci.org/github/hardbyte/python-can/builds/745065503 +@unittest.skipUnless( + IS_UNIX and not IS_TRAVIS, "only supported on Unix systems (but not on Travis CI)" +) +class BasicTestUdpMulticastBusIPv6(Back2BackTestCase): + + INTERFACE_1 = "udp_multicast" + CHANNEL_1 = UdpMulticastBus.DEFAULT_GROUP_IPv6 + INTERFACE_2 = "udp_multicast" + CHANNEL_2 = UdpMulticastBus.DEFAULT_GROUP_IPv6 + + def test_unique_message_instances(self): + with self.assertRaises(NotImplementedError): + super().test_unique_message_instances() + + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class SocketCanBroadcastChannel(unittest.TestCase): def setUp(self): diff --git a/test/config.py b/test/config.py index 3a9072712..4082b2887 100644 --- a/test/config.py +++ b/test/config.py @@ -12,7 +12,7 @@ from os import environ as environment -def env(name): # type: bool +def env(name: str) -> bool: return environment.get(name, "").lower() in ("yes", "true", "t", "1") diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py index 0d94e31f1..6e92ad9aa 100644 --- a/test/test_detect_available_configs.py +++ b/test/test_detect_available_configs.py @@ -6,12 +6,11 @@ :meth:`can.BusABC.detect_available_configs`. """ -import sys import unittest from can import detect_available_configs -from .config import IS_LINUX, IS_CI, TEST_INTERFACE_SOCKETCAN +from .config import IS_CI, IS_UNIX, TEST_INTERFACE_SOCKETCAN class TestDetectAvailableConfigs(unittest.TestCase): @@ -30,19 +29,33 @@ def test_general_values(self): for config in configs: self.assertIn("interface", config) self.assertIn("channel", config) - self.assertIsInstance(config["interface"], str) def test_content_virtual(self): configs = detect_available_configs(interfaces="virtual") + self.assertGreaterEqual(len(configs), 1) for config in configs: self.assertEqual(config["interface"], "virtual") + def test_content_udp_multicast(self): + configs = detect_available_configs(interfaces="udp_multicast") + for config in configs: + self.assertEqual(config["interface"], "udp_multicast") + def test_content_socketcan(self): configs = detect_available_configs(interfaces="socketcan") for config in configs: self.assertEqual(config["interface"], "socketcan") - @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "socketcan is not tested") + def test_count_udp_multicast(self): + configs = detect_available_configs(interfaces="udp_multicast") + if IS_UNIX: + self.assertGreaterEqual(len(configs), 2) + else: + self.assertEqual(len(configs), 0) + + @unittest.skipUnless( + TEST_INTERFACE_SOCKETCAN and IS_CI, "this setup is very specific" + ) def test_socketcan_on_ci_server(self): configs = detect_available_configs(interfaces="socketcan") self.assertGreaterEqual(len(configs), 1) From 80e2edcae145354693de8fd59f652b5708ab737f Mon Sep 17 00:00:00 2001 From: Crestani Fabio FRD DAXE2 Date: Fri, 8 Jan 2021 12:34:19 +0100 Subject: [PATCH 0506/1235] Add msg direction for CAN-FD in BlfReader --- can/io/blf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index eb6c69210..47514cad1 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -311,8 +311,7 @@ def _parse_data(self, data): channel=channel - 1, ) elif obj_type == CAN_FD_MESSAGE_64: - members = unpack_can_fd_64_msg(data, pos)[:7] - channel, dlc, valid_bytes, _, can_id, _, fd_flags = members + channel, dlc, valid_bytes, _, can_id, _, fd_flags, _, _, _, _, _, direction, _, _ = unpack_can_fd_64_msg(data, pos) pos += can_fd_64_msg_size yield Message( timestamp=timestamp, @@ -320,6 +319,7 @@ def _parse_data(self, data): is_extended_id=bool(can_id & CAN_MSG_EXT), is_remote_frame=bool(fd_flags & 0x0010), is_fd=bool(fd_flags & 0x1000), + is_rx=not direction, bitrate_switch=bool(fd_flags & 0x2000), error_state_indicator=bool(fd_flags & 0x4000), dlc=dlc2len(dlc), From 966a5e4d16ba88c1ee6271e82be40c2dbfe8a35f Mon Sep 17 00:00:00 2001 From: Daniel Hrisca Date: Mon, 18 Jan 2021 21:20:04 +0200 Subject: [PATCH 0507/1235] Windows time accuracy (#936) * fixes #732: add support for VN8900 xlGetChannelTime function * improve time stamp accuracy of the CAN messages for Kvaser and Vector buses on the Windows platform. (see #934) On Windows the value returned by time.time is refreshed at best every 0.5ms, and on some PCs the default refresh rate is every 10ms. The idea is to get the value of time.perf_counter at the moment when time.time is refreshed and use this as a reference point. * check the time.time resolution before using the new time correlation function --- can/interfaces/kvaser/canlib.py | 14 ++++++++++++-- can/interfaces/vector/canlib.py | 28 ++++++++++++++++++++++------ can/util.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index d0610019b..8049f5557 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -13,6 +13,7 @@ from can import CanError, BusABC from can import Message +from can.util import time_perfcounter_correlation from . import constants as canstat from . import structures @@ -492,11 +493,20 @@ def __init__(self, channel, can_filters=None, **kwargs): timer = ctypes.c_uint(0) try: - kvReadTimer(self._read_handle, ctypes.byref(timer)) + if time.get_clock_info("time").resolution > 1e-5: + ts, perfcounter = time_perfcounter_correlation() + kvReadTimer(self._read_handle, ctypes.byref(timer)) + current_perfcounter = time.perf_counter() + now = ts + (current_perfcounter - perfcounter) + self._timestamp_offset = now - (timer.value * TIMESTAMP_FACTOR) + else: + kvReadTimer(self._read_handle, ctypes.byref(timer)) + self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) + except Exception as exc: # timer is usually close to 0 log.info(str(exc)) - self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) + self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) self._is_filtered = False super().__init__(channel=channel, can_filters=can_filters, **kwargs) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 6c522d968..c5838de21 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -32,7 +32,12 @@ # Import Modules # ============== from can import BusABC, Message -from can.util import len2dlc, dlc2len, deprecated_args_alias +from can.util import ( + len2dlc, + dlc2len, + deprecated_args_alias, + time_perfcounter_correlation, +) from .exceptions import VectorError # Define Module Logger @@ -295,11 +300,22 @@ def __init__( # Calculate time offset for absolute timestamps offset = xlclass.XLuint64() try: - try: - xldriver.xlGetSyncTime(self.port_handle, offset) - except VectorError: - xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) - self._time_offset = time.time() - offset.value * 1e-9 + if time.get_clock_info("time").resolution > 1e-5: + ts, perfcounter = time_perfcounter_correlation() + try: + xldriver.xlGetSyncTime(self.port_handle, offset) + except VectorError: + xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) + current_perfcounter = time.perf_counter() + now = ts + (current_perfcounter - perfcounter) + self._time_offset = now - offset.value * 1e-9 + else: + try: + xldriver.xlGetSyncTime(self.port_handle, offset) + except VectorError: + xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) + self._time_offset = time.time() - offset.value * 1e-9 + except VectorError: self._time_offset = 0.0 diff --git a/can/util.py b/can/util.py index 021ad766f..da36d0df2 100644 --- a/can/util.py +++ b/can/util.py @@ -4,6 +4,7 @@ import functools import warnings from typing import Dict, Optional, Union +from time import time, perf_counter, get_clock_info from can import typechecking @@ -326,6 +327,35 @@ def rename_kwargs(func_name, kwargs, aliases): warnings.warn("{} is deprecated".format(alias), DeprecationWarning) +def time_perfcounter_correlation(): + """Get the `perf_counter` value nearest to when time.time() is updated + + Computed if the default timer used by `time.time` on this platform has a resolution + higher than 10μs, otherwise the current time and perf_counter is directly returned. + This was chosen as typical timer resolution on Linux/macOS is ~1μs, and the Windows + platform can vary from ~500μs to 10ms. + + Note this value is based on when `time.time()` is observed to update from Python, + it is not directly returned by the operating system. + + :returns: + (t, performance_counter) time.time value and time.perf_counter value when the time.time + is updated + + """ + + # use this if the resolution is higher than 10us + if get_clock_info("time").resolution > 1e-5: + t0 = time() + while True: + t1, performance_counter = time(), perf_counter() + if t1 != t0: + break + else: + return time(), perf_counter() + return t1, performance_counter + + if __name__ == "__main__": print("Searching for configuration named:") print("\n".join(CONFIG_FILES)) From 5d4eb03beae3cae8185aad194dd47cea246f0dd1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 23 Jan 2021 10:52:39 +0100 Subject: [PATCH 0508/1235] Update python 3.10 pre-release --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e1a3fe8c2..fa64e08bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha.2, pypy3] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha.4, pypy3] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 0a0c7bd6ac02479f0e0327d78ffbed4d5605b4bd Mon Sep 17 00:00:00 2001 From: chele69 Date: Mon, 1 Feb 2021 07:56:53 +0100 Subject: [PATCH 0509/1235] Add NI-XNET CANFD interface (#968) * Add initial NI-XNET support * Add NI-XNET doc * update authors information * Add queue len condition to read and use BRS flag to change message type --- can/interfaces/__init__.py | 1 + can/interfaces/nixnet.py | 257 +++++++++++++++++++++++++++++++++++++ doc/interfaces.rst | 1 + doc/interfaces/nixnet.rst | 20 +++ setup.py | 1 + 5 files changed, 280 insertions(+) create mode 100644 can/interfaces/nixnet.py create mode 100644 doc/interfaces/nixnet.rst diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 5d6942a8a..ae088c4bc 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -27,6 +27,7 @@ "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), "cantact": ("can.interfaces.cantact", "CantactBus"), "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), + "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"), } BACKENDS.update( diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py new file mode 100644 index 000000000..f8b54ea4e --- /dev/null +++ b/can/interfaces/nixnet.py @@ -0,0 +1,257 @@ +# coding: utf-8 + +""" +NI-XNET interface module. + +Implementation references: + NI-XNET Hardware and Software Manual: https://www.ni.com/pdf/manuals/372840h.pdf + NI-XNET Python implementation: https://github.com/ni/nixnet-python + +Authors: Javier Rubio Giménez , Jose A. Escobar +""" + +import logging +import sys +import time +import struct + +from can import CanError, BusABC, Message + +logger = logging.getLogger(__name__) + +if sys.platform == "win32": + try: + from nixnet import session, types, constants, errors, system, database + except ImportError: + logger.error("Error, NIXNET python module cannot be loaded.") + raise ImportError() +else: + logger.error("NI-XNET interface is only available on Windows systems") + raise NotImplementedError("NiXNET is not supported on not Win32 platforms") + + +class NiXNETcanBus(BusABC): + """ + The CAN Bus implemented for the NI-XNET interface. + + """ + + def __init__( + self, + channel, + can_filters=None, + bitrate=None, + fd=False, + fd_bitrate=None, + brs=False, + can_termination=False, + log_errors=True, + **kwargs + ): + """ + :param str channel: + Name of the object to open (e.g. 'CAN0') + + :param int bitrate: + Bitrate in bits/s + + :param list can_filters: + See :meth:`can.BusABC.set_filters`. + + :param bool log_errors: + If True, communication errors will appear as CAN messages with + ``is_error_frame`` set to True and ``arbitration_id`` will identify + the error (default True) + + :raises can.interfaces.nixnet.NiXNETError: + If starting communication fails + + """ + self._rx_queue = [] + self.channel = channel + self.channel_info = "NI-XNET: " + channel + + # Set database for the initialization + if not fd: + database_name = ":memory:" + else: + if not brs: + database_name = ":can_fd:" + else: + database_name = ":can_fd_brs:" + + try: + + # We need two sessions for this application, one to send frames and another to receive them + + self.__session_send = session.FrameOutStreamSession( + channel, database_name=database_name + ) + self.__session_receive = session.FrameInStreamSession( + channel, database_name=database_name + ) + + # We stop the sessions to allow reconfiguration, as by default they autostart at creation + self.__session_send.stop() + self.__session_receive.stop() + + # See page 1017 of NI-XNET Hardware and Software Manual to set custom can configuration + if bitrate: + self.__session_send.intf.baud_rate = bitrate + self.__session_receive.intf.baud_rate = bitrate + + if fd_bitrate: + # See page 951 of NI-XNET Hardware and Software Manual to set custom can configuration + self.__session_send.intf.can_fd_baud_rate = fd_bitrate + self.__session_receive.intf.can_fd_baud_rate = fd_bitrate + + if can_termination: + self.__session_send.intf.can_term = constants.CanTerm.ON + self.__session_receive.intf.can_term = constants.CanTerm.ON + + self.__session_receive.queue_size = 512 + # Once that all the parameters have been restarted, we start the sessions + self.__session_send.start() + self.__session_receive.start() + + except errors.XnetError as err: + raise NiXNETError(function="__init__", error_message=err.args[0]) from None + + self._is_filtered = False + super(NiXNETcanBus, self).__init__( + channel=channel, + can_filters=can_filters, + bitrate=bitrate, + log_errors=log_errors, + **kwargs + ) + + def _recv_internal(self, timeout): + try: + if len(self._rx_queue) == 0: + fr = self.__session_receive.frames.read(4, timeout=0) + for f in fr: + self._rx_queue.append(f) + can_frame = self._rx_queue.pop(0) + + # Timestamp should be converted from raw frame format(100ns increment from(12:00 a.m. January 1 1601 Coordinated + # Universal Time (UTC)) to epoch time(number of seconds from January 1, 1970 (midnight UTC/GMT)) + msg = Message( + timestamp=can_frame.timestamp / 10000000.0 - 11644473600, + channel=self.channel, + is_remote_frame=can_frame.type == constants.FrameType.CAN_REMOTE, + is_error_frame=can_frame.type == constants.FrameType.CAN_BUS_ERROR, + is_fd=( + can_frame.type == constants.FrameType.CANFD_DATA + or can_frame.type == constants.FrameType.CANFDBRS_DATA + ), + bitrate_switch=can_frame.type == constants.FrameType.CANFDBRS_DATA, + is_extended_id=can_frame.identifier.extended, + # Get identifier from CanIdentifier structure + arbitration_id=can_frame.identifier.identifier, + dlc=len(can_frame.payload), + data=can_frame.payload, + ) + + return msg, self._filters is None + except Exception as e: + # print('Error: ', e) + return None, self._filters is None + + def send(self, msg, timeout=None): + """ + Send a message using NI-XNET. + + :param can.Message msg: + Message to send + + :param float timeout: + Max time to wait for the device to be ready in seconds, None if time is infinite + + :raises can.interfaces.nixnet.NiXNETError: + If writing to transmit buffer fails. + It does not wait for message to be ACKed currently. + """ + if timeout is None: + timeout = constants.TIMEOUT_INFINITE + + if msg.is_remote_frame: + type_message = constants.FrameType.CAN_REMOTE + elif msg.is_error_frame: + type_message = constants.FrameType.CAN_BUS_ERROR + elif msg.is_fd: + if msg.bitrate_switch: + type_message = constants.FrameType.CANFDBRS_DATA + else: + type_message = constants.FrameType.CANFD_DATA + else: + type_message = constants.FrameType.CAN_DATA + + can_frame = types.CanFrame( + types.CanIdentifier(msg.arbitration_id, msg.is_extended_id), + type=type_message, + payload=msg.data, + ) + + try: + self.__session_send.frames.write([can_frame], timeout) + except errors.XnetError as err: + raise NiXNETError(function="send", error_message=err.args[0]) from None + + def reset(self): + """ + Resets network interface. Stops network interface, then resets the CAN + chip to clear the CAN error counters (clear error passive state). + Resetting includes clearing all entries from read and write queues. + """ + self.__session_send.flush() + self.__session_receive.flush() + + self.__session_send.stop() + self.__session_receive.stop() + + self.__session_send.start() + self.__session_receive.start() + + def shutdown(self): + """Close object.""" + self.__session_send.flush() + self.__session_receive.flush() + + self.__session_send.stop() + self.__session_receive.stop() + + self.__session_send.close() + self.__session_receive.close() + + @staticmethod + def _detect_available_configs(): + configs = [] + nixnet_system = system.System() + for can_intf in nixnet_system.intf_refs_can: + logger.info("Channel index %d: %s", can_intf.port_num, str(can_intf)) + configs.append( + { + "interface": "nixnet", + "channel": str(can_intf), + "can_term_available": can_intf.can_term_cap + == constants.CanTermCap.YES, + } + ) + nixnet_system.close() + return configs + + +# To-Do review error management, I don't like this implementation +class NiXNETError(CanError): + """Error from NI-XNET driver.""" + + def __init__(self, function="", error_message=""): + super(NiXNETError, self).__init__() + #: Function that failed + self.function = function + #: Arguments passed to function + self.error_message = error_message + + def __str__(self): + return "Function %s failed:\n%s" % (self.function, self.error_message) diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 80016f287..361ea4097 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -19,6 +19,7 @@ The available interfaces are: interfaces/udp_multicast interfaces/neovi interfaces/nican + interfaces/nixnet interfaces/pcan interfaces/robotell interfaces/seeedstudio diff --git a/doc/interfaces/nixnet.rst b/doc/interfaces/nixnet.rst new file mode 100644 index 000000000..36d1a8ef0 --- /dev/null +++ b/doc/interfaces/nixnet.rst @@ -0,0 +1,20 @@ +NI-XNET +======= + +This interface adds support for NI-XNET CAN controllers by `National Instruments`_. + + +.. warning:: + + NI-XNET only seems to support windows platforms. + + +Bus +--- + +.. autoclass:: can.interfaces.nican.NiXNETcanBus + +.. autoexception:: can.interfaces.nican.NiXNETError + + +.. _National Instruments: http://www.ni.com/can/ diff --git a/setup.py b/setup.py index af63b8e90..757393e59 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ "neovi": ["filelock", "python-ics>=2.12"], "cantact": ["cantact>=0.0.7"], "gs_usb": ["gs_usb>=0.2.1"], + "nixnet": ["nixnet>=0.3.1"], } setup( From 291af86e3898229a8bacebd82f4ff7a8b81a41c0 Mon Sep 17 00:00:00 2001 From: Niall Darwin <51384867+NiallDarwin@users.noreply.github.com> Date: Tue, 2 Feb 2021 16:14:08 +1300 Subject: [PATCH 0510/1235] Update virtual.rst (#975) Assert now works without error. --- doc/interfaces/virtual.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst index f583cee65..b3fa7b38e 100644 --- a/doc/interfaces/virtual.rst +++ b/doc/interfaces/virtual.rst @@ -95,7 +95,10 @@ Example bus1.send(msg1) msg2 = bus2.recv() - assert msg1 == msg2 + #assert msg1 == msg2 + assert msg1.arbitration_id == msg2.arbitration_id + assert msg1.data == msg2.data + assert msg1.timestamp != msg2.timestamp Bus Class Documentation From 68bee1457a90752450218a5bd4eb98f057caa613 Mon Sep 17 00:00:00 2001 From: Stephane Dorre Date: Mon, 8 Mar 2021 22:11:32 +0100 Subject: [PATCH 0511/1235] Try to find libPCBUSB.dylib before loading it (#983) Seems like path is not always resolved when the lib is installed. On my end the lib was installed under /usr/local/lib/. the only way to get it imported was to find the library and give the full path to the LoadLibrary call Signed-off-by: Stephane Dorre --- can/interfaces/pcan/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index e3031390b..982adc35e 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -589,7 +589,7 @@ def __init__(self): # Unfortunately cygwin python has no winreg module, so we can't # check for the registry key. elif platform.system() == "Darwin": - self.__m_dllBasic = cdll.LoadLibrary("libPCBUSB.dylib") + self.__m_dllBasic = cdll.LoadLibrary(util.find_library("libPCBUSB.dylib")) else: self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so") if self.__m_dllBasic is None: From 2ede107d67002c18463c5276b6a94cdb19fc8320 Mon Sep 17 00:00:00 2001 From: Gordon Schmidt <77705928+gordon-epc@users.noreply.github.com> Date: Mon, 15 Mar 2021 17:16:39 -0700 Subject: [PATCH 0512/1235] add set/get pcan device number (#987) --- can/interfaces/pcan/pcan.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 01287fd90..a37392bd0 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -297,6 +297,41 @@ def reset(self): status = self.m_objPCANBasic.Reset(self.m_PcanHandle) return status == PCAN_ERROR_OK + def get_device_number(self): + """ + Return the PCAN device number. + + :rtype: int + :return: PCAN device number + """ + error, value = self.m_objPCANBasic.GetValue( + self.m_PcanHandle, PCAN_DEVICE_NUMBER + ) + if error != PCAN_ERROR_OK: + return None + return value + + def set_device_number(self, device_number): + """ + Set the PCAN device number. + + :param device_number: new PCAN device number + :rtype: bool + :return: True if device number set successfully + """ + try: + if ( + self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_DEVICE_NUMBER, int(device_number) + ) + != PCAN_ERROR_OK + ): + raise ValueError + except ValueError: + log.error("Invalid value '%s' for device number.", device_number) + return False + return True + def _recv_internal(self, timeout): if HAS_EVENTS: From a1d60f8b822c0553106e11de6bfec6bafc0d0991 Mon Sep 17 00:00:00 2001 From: Alexander Mueller Date: Tue, 23 Mar 2021 15:06:39 +0100 Subject: [PATCH 0513/1235] pcan: force english error messages (#993) On non-english Windows systems the "neutral" language causes an UTF-8 encoding error while trying to log an error/warning. Force using English error message which will not cause these. --- can/interfaces/pcan/pcan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index a37392bd0..85e068bbe 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -253,12 +253,12 @@ def bits(n): # Toggle the lowest set bit n ^= masked_value - stsReturn = self.m_objPCANBasic.GetErrorText(error, 0) + stsReturn = self.m_objPCANBasic.GetErrorText(error, 0x9) if stsReturn[0] != PCAN_ERROR_OK: strings = [] for b in bits(error): - stsReturn = self.m_objPCANBasic.GetErrorText(b, 0) + stsReturn = self.m_objPCANBasic.GetErrorText(b, 0x9) if stsReturn[0] != PCAN_ERROR_OK: text = "An error occurred. Error-code's text ({0:X}h) couldn't be retrieved".format( error From 054922a9d9f51fef44f9b19eed6f4690088ef38b Mon Sep 17 00:00:00 2001 From: Alexander Mueller Date: Sun, 28 Feb 2021 09:22:54 +0100 Subject: [PATCH 0514/1235] pcan: allow a numerical value for channel (#981) --- can/interfaces/pcan/pcan.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 85e068bbe..fa3aa910b 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -90,7 +90,8 @@ def __init__( and :meth:`~can.interface.pcan.PcanBus.status` methods. :param str channel: - The can interface name. An example would be 'PCAN_USBBUS1' + The can interface name. An example would be 'PCAN_USBBUS1'. + Alternatively the value can an int with the numerical value. Default is 'PCAN_USBBUS1' :param can.bus.BusState state: @@ -180,8 +181,11 @@ def __init__( ioport = 0x02A0 interrupt = 11 + if type(channel) != int: + channel = globals()[channel] + self.m_objPCANBasic = PCANBasic() - self.m_PcanHandle = globals()[channel] + self.m_PcanHandle = channel if state is BusState.ACTIVE or state is BusState.PASSIVE: self.state = state From 7fda8318e4a41c2831448e16d75b99f56a034afb Mon Sep 17 00:00:00 2001 From: Alexander Mueller Date: Sun, 21 Mar 2021 22:58:06 +0100 Subject: [PATCH 0515/1235] pcan: limit channel to valid value (#981) --- can/interfaces/pcan/pcan.py | 73 ++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index fa3aa910b..ca4385e47 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -73,6 +73,75 @@ "data_sjw", ] +pcan_channel_names = { + "PCAN_NONEBUS" : PCAN_NONEBUS, + + "PCAN_ISABUS1" : PCAN_ISABUS1, + "PCAN_ISABUS2" : PCAN_ISABUS2, + "PCAN_ISABUS3" : PCAN_ISABUS3, + "PCAN_ISABUS4" : PCAN_ISABUS4, + "PCAN_ISABUS5" : PCAN_ISABUS5, + "PCAN_ISABUS6" : PCAN_ISABUS6, + "PCAN_ISABUS7" : PCAN_ISABUS7, + "PCAN_ISABUS8" : PCAN_ISABUS8, + + "PCAN_DNGBUS1" : PCAN_DNGBUS1, + + "PCAN_PCIBUS1" : PCAN_PCIBUS1, + "PCAN_PCIBUS2" : PCAN_PCIBUS2, + "PCAN_PCIBUS3" : PCAN_PCIBUS3, + "PCAN_PCIBUS4" : PCAN_PCIBUS4, + "PCAN_PCIBUS5" : PCAN_PCIBUS5, + "PCAN_PCIBUS6" : PCAN_PCIBUS6, + "PCAN_PCIBUS7" : PCAN_PCIBUS7, + "PCAN_PCIBUS8" : PCAN_PCIBUS8, + "PCAN_PCIBUS9" : PCAN_PCIBUS9, + "PCAN_PCIBUS10" : PCAN_PCIBUS10, + "PCAN_PCIBUS11" : PCAN_PCIBUS11, + "PCAN_PCIBUS12" : PCAN_PCIBUS12, + "PCAN_PCIBUS13" : PCAN_PCIBUS13, + "PCAN_PCIBUS14" : PCAN_PCIBUS14, + "PCAN_PCIBUS15" : PCAN_PCIBUS15, + "PCAN_PCIBUS16" : PCAN_PCIBUS16, + + "PCAN_USBBUS1" : PCAN_USBBUS1, + "PCAN_USBBUS2" : PCAN_USBBUS2, + "PCAN_USBBUS3" : PCAN_USBBUS3, + "PCAN_USBBUS4" : PCAN_USBBUS4, + "PCAN_USBBUS5" : PCAN_USBBUS5, + "PCAN_USBBUS6" : PCAN_USBBUS6, + "PCAN_USBBUS7" : PCAN_USBBUS7, + "PCAN_USBBUS8" : PCAN_USBBUS8, + "PCAN_USBBUS9" : PCAN_USBBUS9, + "PCAN_USBBUS10" : PCAN_USBBUS10, + "PCAN_USBBUS11" : PCAN_USBBUS11, + "PCAN_USBBUS12" : PCAN_USBBUS12, + "PCAN_USBBUS13" : PCAN_USBBUS13, + "PCAN_USBBUS14" : PCAN_USBBUS14, + "PCAN_USBBUS15" : PCAN_USBBUS15, + "PCAN_USBBUS16" : PCAN_USBBUS16, + + "PCAN_PCCBUS1" : PCAN_PCCBUS1, + "PCAN_PCCBUS2" : PCAN_PCCBUS2, + + "PCAN_LANBUS1" : PCAN_LANBUS1, + "PCAN_LANBUS2" : PCAN_LANBUS2, + "PCAN_LANBUS3" : PCAN_LANBUS3, + "PCAN_LANBUS4" : PCAN_LANBUS4, + "PCAN_LANBUS5" : PCAN_LANBUS5, + "PCAN_LANBUS6" : PCAN_LANBUS6, + "PCAN_LANBUS7" : PCAN_LANBUS7, + "PCAN_LANBUS8" : PCAN_LANBUS8, + "PCAN_LANBUS9" : PCAN_LANBUS9, + "PCAN_LANBUS10" : PCAN_LANBUS10, + "PCAN_LANBUS11" : PCAN_LANBUS11, + "PCAN_LANBUS12" : PCAN_LANBUS12, + "PCAN_LANBUS13" : PCAN_LANBUS13, + "PCAN_LANBUS14" : PCAN_LANBUS14, + "PCAN_LANBUS15" : PCAN_LANBUS15, + "PCAN_LANBUS16" : PCAN_LANBUS16 +} + class PcanBus(BusABC): def __init__( @@ -91,7 +160,7 @@ def __init__( :param str channel: The can interface name. An example would be 'PCAN_USBBUS1'. - Alternatively the value can an int with the numerical value. + Alternatively the value can be an int with the numerical value. Default is 'PCAN_USBBUS1' :param can.bus.BusState state: @@ -182,7 +251,7 @@ def __init__( interrupt = 11 if type(channel) != int: - channel = globals()[channel] + channel = pcan_channel_names[channel] self.m_objPCANBasic = PCANBasic() self.m_PcanHandle = channel From 9c164dcb853984a3c24fab47f2fe97d906db0922 Mon Sep 17 00:00:00 2001 From: Alexander Mueller Date: Sun, 21 Mar 2021 23:04:55 +0100 Subject: [PATCH 0516/1235] pcan: make sure channel_info is a str --- can/interfaces/pcan/pcan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index ca4385e47..3cb4bf608 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -242,7 +242,7 @@ def __init__( Ignored if not using CAN-FD. """ - self.channel_info = channel + self.channel_info = str(channel) self.fd = kwargs.get("fd", False) pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) From 7912632b0b07d38667623f71ae4ebbe53a7a6299 Mon Sep 17 00:00:00 2001 From: Alexander Mueller Date: Sun, 21 Mar 2021 22:08:02 +0000 Subject: [PATCH 0517/1235] Format code with black --- can/interfaces/pcan/pcan.py | 126 +++++++++++++++++------------------- 1 file changed, 60 insertions(+), 66 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 3cb4bf608..b8c6c4245 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -74,72 +74,66 @@ ] pcan_channel_names = { - "PCAN_NONEBUS" : PCAN_NONEBUS, - - "PCAN_ISABUS1" : PCAN_ISABUS1, - "PCAN_ISABUS2" : PCAN_ISABUS2, - "PCAN_ISABUS3" : PCAN_ISABUS3, - "PCAN_ISABUS4" : PCAN_ISABUS4, - "PCAN_ISABUS5" : PCAN_ISABUS5, - "PCAN_ISABUS6" : PCAN_ISABUS6, - "PCAN_ISABUS7" : PCAN_ISABUS7, - "PCAN_ISABUS8" : PCAN_ISABUS8, - - "PCAN_DNGBUS1" : PCAN_DNGBUS1, - - "PCAN_PCIBUS1" : PCAN_PCIBUS1, - "PCAN_PCIBUS2" : PCAN_PCIBUS2, - "PCAN_PCIBUS3" : PCAN_PCIBUS3, - "PCAN_PCIBUS4" : PCAN_PCIBUS4, - "PCAN_PCIBUS5" : PCAN_PCIBUS5, - "PCAN_PCIBUS6" : PCAN_PCIBUS6, - "PCAN_PCIBUS7" : PCAN_PCIBUS7, - "PCAN_PCIBUS8" : PCAN_PCIBUS8, - "PCAN_PCIBUS9" : PCAN_PCIBUS9, - "PCAN_PCIBUS10" : PCAN_PCIBUS10, - "PCAN_PCIBUS11" : PCAN_PCIBUS11, - "PCAN_PCIBUS12" : PCAN_PCIBUS12, - "PCAN_PCIBUS13" : PCAN_PCIBUS13, - "PCAN_PCIBUS14" : PCAN_PCIBUS14, - "PCAN_PCIBUS15" : PCAN_PCIBUS15, - "PCAN_PCIBUS16" : PCAN_PCIBUS16, - - "PCAN_USBBUS1" : PCAN_USBBUS1, - "PCAN_USBBUS2" : PCAN_USBBUS2, - "PCAN_USBBUS3" : PCAN_USBBUS3, - "PCAN_USBBUS4" : PCAN_USBBUS4, - "PCAN_USBBUS5" : PCAN_USBBUS5, - "PCAN_USBBUS6" : PCAN_USBBUS6, - "PCAN_USBBUS7" : PCAN_USBBUS7, - "PCAN_USBBUS8" : PCAN_USBBUS8, - "PCAN_USBBUS9" : PCAN_USBBUS9, - "PCAN_USBBUS10" : PCAN_USBBUS10, - "PCAN_USBBUS11" : PCAN_USBBUS11, - "PCAN_USBBUS12" : PCAN_USBBUS12, - "PCAN_USBBUS13" : PCAN_USBBUS13, - "PCAN_USBBUS14" : PCAN_USBBUS14, - "PCAN_USBBUS15" : PCAN_USBBUS15, - "PCAN_USBBUS16" : PCAN_USBBUS16, - - "PCAN_PCCBUS1" : PCAN_PCCBUS1, - "PCAN_PCCBUS2" : PCAN_PCCBUS2, - - "PCAN_LANBUS1" : PCAN_LANBUS1, - "PCAN_LANBUS2" : PCAN_LANBUS2, - "PCAN_LANBUS3" : PCAN_LANBUS3, - "PCAN_LANBUS4" : PCAN_LANBUS4, - "PCAN_LANBUS5" : PCAN_LANBUS5, - "PCAN_LANBUS6" : PCAN_LANBUS6, - "PCAN_LANBUS7" : PCAN_LANBUS7, - "PCAN_LANBUS8" : PCAN_LANBUS8, - "PCAN_LANBUS9" : PCAN_LANBUS9, - "PCAN_LANBUS10" : PCAN_LANBUS10, - "PCAN_LANBUS11" : PCAN_LANBUS11, - "PCAN_LANBUS12" : PCAN_LANBUS12, - "PCAN_LANBUS13" : PCAN_LANBUS13, - "PCAN_LANBUS14" : PCAN_LANBUS14, - "PCAN_LANBUS15" : PCAN_LANBUS15, - "PCAN_LANBUS16" : PCAN_LANBUS16 + "PCAN_NONEBUS": PCAN_NONEBUS, + "PCAN_ISABUS1": PCAN_ISABUS1, + "PCAN_ISABUS2": PCAN_ISABUS2, + "PCAN_ISABUS3": PCAN_ISABUS3, + "PCAN_ISABUS4": PCAN_ISABUS4, + "PCAN_ISABUS5": PCAN_ISABUS5, + "PCAN_ISABUS6": PCAN_ISABUS6, + "PCAN_ISABUS7": PCAN_ISABUS7, + "PCAN_ISABUS8": PCAN_ISABUS8, + "PCAN_DNGBUS1": PCAN_DNGBUS1, + "PCAN_PCIBUS1": PCAN_PCIBUS1, + "PCAN_PCIBUS2": PCAN_PCIBUS2, + "PCAN_PCIBUS3": PCAN_PCIBUS3, + "PCAN_PCIBUS4": PCAN_PCIBUS4, + "PCAN_PCIBUS5": PCAN_PCIBUS5, + "PCAN_PCIBUS6": PCAN_PCIBUS6, + "PCAN_PCIBUS7": PCAN_PCIBUS7, + "PCAN_PCIBUS8": PCAN_PCIBUS8, + "PCAN_PCIBUS9": PCAN_PCIBUS9, + "PCAN_PCIBUS10": PCAN_PCIBUS10, + "PCAN_PCIBUS11": PCAN_PCIBUS11, + "PCAN_PCIBUS12": PCAN_PCIBUS12, + "PCAN_PCIBUS13": PCAN_PCIBUS13, + "PCAN_PCIBUS14": PCAN_PCIBUS14, + "PCAN_PCIBUS15": PCAN_PCIBUS15, + "PCAN_PCIBUS16": PCAN_PCIBUS16, + "PCAN_USBBUS1": PCAN_USBBUS1, + "PCAN_USBBUS2": PCAN_USBBUS2, + "PCAN_USBBUS3": PCAN_USBBUS3, + "PCAN_USBBUS4": PCAN_USBBUS4, + "PCAN_USBBUS5": PCAN_USBBUS5, + "PCAN_USBBUS6": PCAN_USBBUS6, + "PCAN_USBBUS7": PCAN_USBBUS7, + "PCAN_USBBUS8": PCAN_USBBUS8, + "PCAN_USBBUS9": PCAN_USBBUS9, + "PCAN_USBBUS10": PCAN_USBBUS10, + "PCAN_USBBUS11": PCAN_USBBUS11, + "PCAN_USBBUS12": PCAN_USBBUS12, + "PCAN_USBBUS13": PCAN_USBBUS13, + "PCAN_USBBUS14": PCAN_USBBUS14, + "PCAN_USBBUS15": PCAN_USBBUS15, + "PCAN_USBBUS16": PCAN_USBBUS16, + "PCAN_PCCBUS1": PCAN_PCCBUS1, + "PCAN_PCCBUS2": PCAN_PCCBUS2, + "PCAN_LANBUS1": PCAN_LANBUS1, + "PCAN_LANBUS2": PCAN_LANBUS2, + "PCAN_LANBUS3": PCAN_LANBUS3, + "PCAN_LANBUS4": PCAN_LANBUS4, + "PCAN_LANBUS5": PCAN_LANBUS5, + "PCAN_LANBUS6": PCAN_LANBUS6, + "PCAN_LANBUS7": PCAN_LANBUS7, + "PCAN_LANBUS8": PCAN_LANBUS8, + "PCAN_LANBUS9": PCAN_LANBUS9, + "PCAN_LANBUS10": PCAN_LANBUS10, + "PCAN_LANBUS11": PCAN_LANBUS11, + "PCAN_LANBUS12": PCAN_LANBUS12, + "PCAN_LANBUS13": PCAN_LANBUS13, + "PCAN_LANBUS14": PCAN_LANBUS14, + "PCAN_LANBUS15": PCAN_LANBUS15, + "PCAN_LANBUS16": PCAN_LANBUS16, } From 9d10f35a8de6697d0e4767a6b1087c22cc0355c9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 10 Apr 2021 14:29:02 +0200 Subject: [PATCH 0518/1235] bump pylint version --- can/bus.py | 4 ++-- can/ctypesutil.py | 2 +- can/interface.py | 6 +++--- can/logger.py | 3 +-- can/message.py | 4 ++-- can/player.py | 3 +-- can/viewer.py | 5 ++--- requirements-lint.txt | 2 +- 8 files changed, 13 insertions(+), 16 deletions(-) diff --git a/can/bus.py b/can/bus.py index ecad55f1d..6258e9a82 100644 --- a/can/bus.py +++ b/can/bus.py @@ -103,8 +103,8 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: if time_left > 0: continue - else: - return None + + return None def _recv_internal( self, timeout: Optional[float] diff --git a/can/ctypesutil.py b/can/ctypesutil.py index cba8e797b..13668d1e6 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -42,7 +42,7 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): "Could not map function '{}' from library {}".format( func_name, self._name ) - ) + ) from None setattr(symbol, "_name", func_name) log.debug( diff --git a/can/interface.py b/can/interface.py index fd76b60bb..26b0a18cb 100644 --- a/can/interface.py +++ b/can/interface.py @@ -29,7 +29,7 @@ def _get_class_for_interface(interface): try: module_name, class_name = BACKENDS[interface] except KeyError: - raise NotImplementedError("CAN interface '{}' not supported".format(interface)) + raise NotImplementedError("CAN interface '{}' not supported".format(interface)) from None # Import the correct interface module try: @@ -39,7 +39,7 @@ def _get_class_for_interface(interface): "Cannot import module {} for CAN interface '{}': {}".format( module_name, interface, e ) - ) + ) from None # Get the correct class try: @@ -49,7 +49,7 @@ def _get_class_for_interface(interface): "Cannot import class {} from module {} for CAN interface '{}': {}".format( class_name, module_name, interface, e ) - ) + ) from None return bus_class diff --git a/can/logger.py b/can/logger.py index bf83ef10f..72977e143 100644 --- a/can/logger.py +++ b/can/logger.py @@ -18,6 +18,7 @@ import argparse import socket from datetime import datetime +import errno import can from can import Bus, BusState, Logger, SizedRotatingLogger @@ -107,8 +108,6 @@ def main(): # print help message when no arguments wre given if len(sys.argv) < 2: parser.print_help(sys.stderr) - import errno - raise SystemExit(errno.EINVAL) results = parser.parse_args() diff --git a/can/message.py b/can/message.py index 7ceaca489..8405e5928 100644 --- a/can/message.py +++ b/can/message.py @@ -94,9 +94,9 @@ def __init__( else: try: self.data = bytearray(data) - except TypeError: + except TypeError as error: err = "Couldn't create message from {} ({})".format(data, type(data)) - raise TypeError(err) + raise TypeError(err) from error if dlc is None: self.dlc = len(self.data) diff --git a/can/player.py b/can/player.py index d7ef866fb..ebd03f2f7 100644 --- a/can/player.py +++ b/can/player.py @@ -8,6 +8,7 @@ import sys import argparse from datetime import datetime +import errno import can from can import Bus, LogReader, MessageSync @@ -102,8 +103,6 @@ def main(): # print help message when no arguments were given if len(sys.argv) < 2: parser.print_help(sys.stderr) - import errno - raise SystemExit(errno.EINVAL) results = parser.parse_args() diff --git a/can/viewer.py b/can/viewer.py index 107c028ae..c7cfde40c 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -21,6 +21,7 @@ # e-mail : lauszus@gmail.com import argparse +import errno import logging import os import struct @@ -93,7 +94,7 @@ def run(self): break # Clear by pressing 'c' - elif key == ord("c"): + if key == ord("c"): self.ids = {} self.start_time = None self.scroll = 0 @@ -461,8 +462,6 @@ def parse_args(args): # Print help message when no arguments are given if not args: parser.print_help(sys.stderr) - import errno - raise SystemExit(errno.EINVAL) parsed_args = parser.parse_args(args) diff --git a/requirements-lint.txt b/requirements-lint.txt index 257c048e5..eec0f6b34 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ -pylint==2.6.0 +pylint==2.7.4 black==20.8b1 mypy==0.790 mypy-extensions==0.4.3 From 2adcd7546b3644712b933c7e2843834b0bddb757 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 10 Apr 2021 14:29:33 +0200 Subject: [PATCH 0519/1235] incorporate feedback --- .github/workflows/build.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa64e08bf..46f983b0c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,10 +5,16 @@ on: [push, pull_request] jobs: build: runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} # See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-alpha.4, pypy3] + experimental: [false] + python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + include: + - python-version: 3.10.0-alpha.7 # Newest: https://github.com/actions/python-versions/blob/main/versions-manifest.json + os: ubuntu-latest + experimental: true steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 2a934c25ee155a551ef966cd673b78a7e1832903 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 10 Apr 2021 14:34:56 +0200 Subject: [PATCH 0520/1235] version bumpy mypy --- requirements-lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index eec0f6b34..1c4967679 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ pylint==2.7.4 black==20.8b1 -mypy==0.790 +mypy==0.812 mypy-extensions==0.4.3 From 6f538117e7f7f0312704828128e4d9bcd86acc22 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 10 Apr 2021 12:35:35 +0000 Subject: [PATCH 0521/1235] Format code with black --- can/interface.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interface.py b/can/interface.py index 26b0a18cb..2d1ad0891 100644 --- a/can/interface.py +++ b/can/interface.py @@ -29,7 +29,9 @@ def _get_class_for_interface(interface): try: module_name, class_name = BACKENDS[interface] except KeyError: - raise NotImplementedError("CAN interface '{}' not supported".format(interface)) from None + raise NotImplementedError( + "CAN interface '{}' not supported".format(interface) + ) from None # Import the correct interface module try: From 60f6cdf699f6c811c917753ed15dd6c263e23837 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 11 Apr 2021 15:32:55 +0200 Subject: [PATCH 0522/1235] Update build.yml Set runs-on to ${{ matrix.os }} --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46f983b0c..bf9dd7282 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} # See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error strategy: matrix: From 6ba02f94bc21693c806a56fd2e60387debe96637 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 00:20:43 +0200 Subject: [PATCH 0523/1235] update format-code.yml to most recent python versions --- .github/workflows/format-code.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index da0f7ba4f..81e8fdf03 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -11,9 +11,9 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip From 4198a35738a6a48ad56a99af8a9d410ec8be9ece Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 00:25:57 +0200 Subject: [PATCH 0524/1235] separate out the black formatter --- .github/workflows/build.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf9dd7282..6bff4e218 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Tests on: [push, pull_request] jobs: - build: + test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} # See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error strategy: @@ -25,10 +25,23 @@ jobs: run: | python -m pip install --upgrade pip pip install tox - pip install -r requirements-lint.txt - name: Test with pytest via tox run: | tox -e gh + + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + pip install -r requirements-lint.txt - name: Code Format Check with Black run: | black --check --verbose . From 896fae7d74db2928769e56fa1808edc88507f1f0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 00:46:36 +0200 Subject: [PATCH 0525/1235] skip BasicTestUdpMulticastBusIPv4/BasicTestUdpMulticastBusIPv6 on all macOS instances --- test/back2back_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index fe33ef213..1d061a2a5 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -247,8 +247,8 @@ class BasicTestSocketCan(Back2BackTestCase): # this doesn't even work on Travis CI for macOS; for example, see # https://travis-ci.org/github/hardbyte/python-can/jobs/745389871 @unittest.skipUnless( - IS_UNIX and not (IS_TRAVIS and IS_OSX), - "only supported on Unix systems (but not on Travis CI on macOS)", + IS_UNIX and not IS_OSX, + "only supported on Unix systems (but not on macOS at Travis CI and GitHub Actions)", ) class BasicTestUdpMulticastBusIPv4(Back2BackTestCase): @@ -265,7 +265,8 @@ def test_unique_message_instances(self): # this doesn't even work for loopback multicast addresses on Travis CI; for example, see # https://travis-ci.org/github/hardbyte/python-can/builds/745065503 @unittest.skipUnless( - IS_UNIX and not IS_TRAVIS, "only supported on Unix systems (but not on Travis CI)" + IS_UNIX and not (IS_TRAVIS or IS_OSX), + "only supported on Unix systems (but not on Travis CI; and not an macOS at GitHub Actions)", ) class BasicTestUdpMulticastBusIPv6(Back2BackTestCase): From 3fdd6eabb4ba6dabd15ca0ae3cdac6d6c7f1bb4f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 01:02:14 +0200 Subject: [PATCH 0526/1235] add GITHUB_ACTIONS variable --- test/config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/config.py b/test/config.py index 4082b2887..dbfb0a049 100644 --- a/test/config.py +++ b/test/config.py @@ -16,7 +16,7 @@ def env(name: str) -> bool: return environment.get(name, "").lower() in ("yes", "true", "t", "1") -# ############################## Continuos integration +# ############################## Continuous integration # see here for the environment variables that are set on the CI servers: # - https://docs.travis-ci.com/user/environment-variables/ @@ -24,12 +24,13 @@ def env(name: str) -> bool: IS_TRAVIS = env("TRAVIS") IS_APPVEYOR = env("APPVEYOR") +IS_GITHUB_ACTIONS = env("GITHUB_ACTIONS") -IS_CI = IS_TRAVIS or IS_APPVEYOR or env("CI") or env("CONTINUOUS_INTEGRATION") +IS_CI = IS_TRAVIS or IS_APPVEYOR or IS_GITHUB_ACTIONS or env("CI") or env("CONTINUOUS_INTEGRATION") -if IS_APPVEYOR and IS_TRAVIS: +if IS_APPVEYOR and IS_TRAVIS and IS_GITHUB_ACTIONS: raise EnvironmentError( - "IS_APPVEYOR and IS_TRAVIS cannot be both True at the same time" + "only one of IS_APPVEYOR and IS_TRAVIS and GITHUB_ACTIONS max be True at the same time" ) From c57480b1988bdb590c6b976335824a59bf2d3c0c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 01:02:33 +0200 Subject: [PATCH 0527/1235] fix PCAN error on macOS on GitHub Actions --- can/interfaces/pcan/pcan.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index b8c6c4245..0af5e99dc 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -620,16 +620,19 @@ def _detect_available_configs(): } ) for i in interfaces: - error, value = library_handle.GetValue(i["id"], PCAN_CHANNEL_CONDITION) - if error != PCAN_ERROR_OK or value != PCAN_CHANNEL_AVAILABLE: - continue - has_fd = False - error, value = library_handle.GetValue(i["id"], PCAN_CHANNEL_FEATURES) - if error == PCAN_ERROR_OK: - has_fd = bool(value & FEATURE_FD_CAPABLE) - channels.append( - {"interface": "pcan", "channel": i["name"], "supports_fd": has_fd} - ) + try: + error, value = library_handle.GetValue(i["id"], PCAN_CHANNEL_CONDITION) + if error != PCAN_ERROR_OK or value != PCAN_CHANNEL_AVAILABLE: + continue + has_fd = False + error, value = library_handle.GetValue(i["id"], PCAN_CHANNEL_FEATURES) + if error == PCAN_ERROR_OK: + has_fd = bool(value & FEATURE_FD_CAPABLE) + channels.append( + {"interface": "pcan", "channel": i["name"], "supports_fd": has_fd} + ) + except AttributeError: # Ignore if this fails for some interfaces + pass return channels def status_string(self) -> Optional[str]: From 05942c934493a2023af15464ff3343a2efe76849 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 12 Apr 2021 23:03:11 +0000 Subject: [PATCH 0528/1235] Format code with black --- test/config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/config.py b/test/config.py index dbfb0a049..3f2516c79 100644 --- a/test/config.py +++ b/test/config.py @@ -26,7 +26,13 @@ def env(name: str) -> bool: IS_APPVEYOR = env("APPVEYOR") IS_GITHUB_ACTIONS = env("GITHUB_ACTIONS") -IS_CI = IS_TRAVIS or IS_APPVEYOR or IS_GITHUB_ACTIONS or env("CI") or env("CONTINUOUS_INTEGRATION") +IS_CI = ( + IS_TRAVIS + or IS_APPVEYOR + or IS_GITHUB_ACTIONS + or env("CI") + or env("CONTINUOUS_INTEGRATION") +) if IS_APPVEYOR and IS_TRAVIS and IS_GITHUB_ACTIONS: raise EnvironmentError( From efc5f26889a7e593e6d70ed7e486e65e6af1e467 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 01:11:17 +0200 Subject: [PATCH 0529/1235] skip TestMessageSync in some more CI configurations --- test/test_message_sync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_message_sync.py b/test/test_message_sync.py index b7b911bd0..b4d2f951d 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -14,7 +14,7 @@ from can import MessageSync, Message -from .config import IS_CI, IS_APPVEYOR, IS_TRAVIS, IS_OSX +from .config import IS_CI, IS_APPVEYOR, IS_TRAVIS, IS_OSX, IS_GITHUB_ACTIONS, IS_LINUX from .message_helper import ComparingMessagesTestCase from .data.example_data import TEST_MESSAGES_BASE @@ -31,7 +31,7 @@ def inc(value): @unittest.skipIf( - IS_APPVEYOR or (IS_TRAVIS and IS_OSX), + IS_APPVEYOR or (IS_TRAVIS and IS_OSX) or (IS_GITHUB_ACTIONS and not IS_LINUX), "this environment's timings are too unpredictable", ) class TestMessageSync(unittest.TestCase, ComparingMessagesTestCase): From ad735c8dd4d2fca61c1d6ffb1814ef8b362a60f4 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 09:28:02 +0200 Subject: [PATCH 0530/1235] pass environment variable GITHUB_ACTIONS --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 5cf50bd78..98479b49f 100644 --- a/tox.ini +++ b/tox.ini @@ -18,6 +18,7 @@ recreate = True [testenv:gh] passenv = CI + GITHUB_ACTIONS PYTHONPATH [testenv:travis] From 592265ee92fcf7f39e09aaee8e013fb81eeaa66c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 12:12:06 +0200 Subject: [PATCH 0531/1235] Update tox.ini --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 98479b49f..c992a37c6 100644 --- a/tox.ini +++ b/tox.ini @@ -18,8 +18,7 @@ recreate = True [testenv:gh] passenv = CI - GITHUB_ACTIONS - PYTHONPATH + GITHUB_* [testenv:travis] passenv = From 91485bf19b898501fe371392008f23f4f99f8ab9 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 12:19:44 +0200 Subject: [PATCH 0532/1235] set fail-fast to false --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6bff4e218..600c5e45b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,7 @@ jobs: - python-version: 3.10.0-alpha.7 # Newest: https://github.com/actions/python-versions/blob/main/versions-manifest.json os: ubuntu-latest experimental: true + fail-fast: false steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From a35c35df0b952b61640c045d1c9ee30e8d352e9f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 13:06:57 +0200 Subject: [PATCH 0533/1235] improve error message in viewer.py --- can/viewer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/viewer.py b/can/viewer.py index c7cfde40c..dde075f0e 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -38,9 +38,9 @@ import curses from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE except ImportError: - # Probably on windows + # Probably on Windows while windows-curses is not installed (e.g. in PyPy) logger.warning( - "You won't be able to use the viewer program without " "curses installed!" + "You won't be able to use the viewer program without curses installed!" ) curses = None # type: ignore From 011e3a5431dad872ae963a8677b871b8a062c760 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 13:07:23 +0200 Subject: [PATCH 0534/1235] only install windows-curses on CPython (and only on Windows, obviously) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 757393e59..0c9c3410b 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,7 @@ # but we assume it is already installed. # "setuptools", "wrapt~=1.10", - 'windows-curses;platform_system=="Windows"', + 'windows-curses;platform_system=="Windows" and platform_python_implementation!="CPython"', "mypy_extensions>=0.4.0,<0.5.0", 'pywin32;platform_system=="Windows"', 'msgpack~=1.0.0;platform_system!="Windows"', From f20932a4a02855fb570f97a51b52b30b5ff765b4 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 13:07:57 +0200 Subject: [PATCH 0535/1235] only install windows-curses on CPython (and only on Windows, obviously) [fix] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0c9c3410b..d6ec3679a 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,7 @@ # but we assume it is already installed. # "setuptools", "wrapt~=1.10", - 'windows-curses;platform_system=="Windows" and platform_python_implementation!="CPython"', + 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"', "mypy_extensions>=0.4.0,<0.5.0", 'pywin32;platform_system=="Windows"', 'msgpack~=1.0.0;platform_system!="Windows"', From 8f75e146de102b5363e15eb6484b7f1b51539cf2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 13:27:15 +0200 Subject: [PATCH 0536/1235] only install pywin32 on CPython --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d6ec3679a..7a9a62d37 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ "wrapt~=1.10", 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"', "mypy_extensions>=0.4.0,<0.5.0", - 'pywin32;platform_system=="Windows"', + 'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"', 'msgpack~=1.0.0;platform_system!="Windows"', ], extras_require=extras_require, From dca63571fca349734e4cd4c4e8e9641c2e442d54 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 18:23:14 +0200 Subject: [PATCH 0537/1235] add test for VectorChannelConfig (#942) Co-authored-by: Felix Divo --- can/interfaces/vector/__init__.py | 2 +- test/test_vector.py | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index 813608a86..7fc567017 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,5 +1,5 @@ """ """ -from .canlib import VectorBus +from .canlib import VectorBus, VectorChannelConfig from .exceptions import VectorError diff --git a/test/test_vector.py b/test/test_vector.py index 152d04024..76983dec4 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -14,7 +14,13 @@ import pytest import can -from can.interfaces.vector import canlib, xldefine, xlclass, VectorError +from can.interfaces.vector import ( + canlib, + xldefine, + xlclass, + VectorError, + VectorChannelConfig, +) class TestVectorBus(unittest.TestCase): @@ -296,6 +302,23 @@ def test_vector_error_pickle(self) -> None: raise exc_unpickled +class TestVectorChannelConfig: + def test_attributes(self): + assert hasattr(VectorChannelConfig, "name") + assert hasattr(VectorChannelConfig, "hwType") + assert hasattr(VectorChannelConfig, "hwIndex") + assert hasattr(VectorChannelConfig, "hwChannel") + assert hasattr(VectorChannelConfig, "channelIndex") + assert hasattr(VectorChannelConfig, "channelMask") + assert hasattr(VectorChannelConfig, "channelCapabilities") + assert hasattr(VectorChannelConfig, "channelBusCapabilities") + assert hasattr(VectorChannelConfig, "isOnBus") + assert hasattr(VectorChannelConfig, "connectedBusType") + assert hasattr(VectorChannelConfig, "serialNumber") + assert hasattr(VectorChannelConfig, "articleNumber") + assert hasattr(VectorChannelConfig, "transceiverName") + + def xlGetApplConfig( app_name_p: ctypes.c_char_p, app_channel: ctypes.c_uint, From 0736b15c5ec518dabf82f80f4684cf82a7c1cd81 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 19:02:35 +0200 Subject: [PATCH 0538/1235] do not install tox in formatter job --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 600c5e45b..0ec8a3aac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install tox pip install -r requirements-lint.txt - name: Code Format Check with Black run: | From f12e84f3eeddf285c07c84331634e209531e7baf Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 19:02:51 +0200 Subject: [PATCH 0539/1235] skip timing tests in unreliable environments --- test/test_message_sync.py | 50 ++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/test/test_message_sync.py b/test/test_message_sync.py index b4d2f951d..a20cc9d65 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -30,10 +30,13 @@ def inc(value): return value -@unittest.skipIf( +skip_on_unreliable_platforms = unittest.skipIf( IS_APPVEYOR or (IS_TRAVIS and IS_OSX) or (IS_GITHUB_ACTIONS and not IS_LINUX), "this environment's timings are too unpredictable", ) + + +@skip_on_unreliable_platforms class TestMessageSync(unittest.TestCase, ComparingMessagesTestCase): def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) @@ -93,29 +96,28 @@ def test_skip(self): self.assertMessagesEqual(messages, collected) -if not IS_APPVEYOR: # this environment's timings are too unpredictable - - @pytest.mark.timeout(inc(0.3)) - @pytest.mark.parametrize( - "timestamp_1,timestamp_2", [(0.0, 0.0), (0.0, 0.01), (0.01, 0.0)] - ) - def test_gap(timestamp_1, timestamp_2): - """This method is alone so it can be parameterized.""" - messages = [ - Message(arbitration_id=0x1, timestamp=timestamp_1), - Message(arbitration_id=0x2, timestamp=timestamp_2), - ] - sync = MessageSync(messages, gap=0.1) - - gc.disable() - before = time() - collected = list(sync) - after = time() - gc.enable() - took = after - before - - assert 0.1 <= took < inc(0.3) - assert messages == collected +@skip_on_unreliable_platforms +@pytest.mark.timeout(inc(0.3)) +@pytest.mark.parametrize( + "timestamp_1,timestamp_2", [(0.0, 0.0), (0.0, 0.01), (0.01, 0.0)] +) +def test_gap(timestamp_1, timestamp_2): + """This method is alone so it can be parameterized.""" + messages = [ + Message(arbitration_id=0x1, timestamp=timestamp_1), + Message(arbitration_id=0x2, timestamp=timestamp_2), + ] + sync = MessageSync(messages, gap=0.1) + + gc.disable() + before = time() + collected = list(sync) + after = time() + gc.enable() + took = after - before + + assert 0.1 <= took < inc(0.3) + assert messages == collected if __name__ == "__main__": From 3b0be9b51b2eb8be54d2987407d45fe2faea56cc Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 19:20:33 +0200 Subject: [PATCH 0540/1235] try to fix test under Windows/tox --- test/test_scripts.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test_scripts.py b/test/test_scripts.py index 59325dcd6..0628f2f2f 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -28,7 +28,12 @@ def test_do_commands_exist(self): """ for command in self._commands(): try: - subprocess.check_output(command.split(), stderr=subprocess.STDOUT) + subprocess.check_output( + command.split(), + stderr=subprocess.STDOUT, + encoding='utf-8', + shell=IS_WINDOWS, + ) except subprocess.CalledProcessError as e: return_code = e.returncode output = e.output From 11967dfb383f91e350dc357e8278d890b0cea4bd Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 17:21:19 +0000 Subject: [PATCH 0541/1235] Format code with black --- test/test_scripts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_scripts.py b/test/test_scripts.py index 0628f2f2f..34017c29e 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -31,7 +31,7 @@ def test_do_commands_exist(self): subprocess.check_output( command.split(), stderr=subprocess.STDOUT, - encoding='utf-8', + encoding="utf-8", shell=IS_WINDOWS, ) except subprocess.CalledProcessError as e: From 34641fadb3d35b030c4afec050b28f94d1393a6b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 19:49:50 +0200 Subject: [PATCH 0542/1235] Allow the curses module to be missing in test_viewer.py (e.g. on PyPy on Windows) --- test/test_viewer.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/test_viewer.py b/test/test_viewer.py index a0873d02b..575feb607 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -24,7 +24,6 @@ # e-mail : lauszus@gmail.com import argparse -import curses import math import os import random @@ -40,6 +39,15 @@ from can.viewer import KEY_ESC, KEY_SPACE, CanViewer, parse_args +# Allow the curses module to be missing (e.g. on PyPy on Windows) +try: + import curses + CURSES_AVAILABLE = True +except ImportError: + curses = None # type: ignore + CURSES_AVAILABLE = False + + # noinspection SpellCheckingInspection,PyUnusedLocal class StdscrDummy: def __init__(self): @@ -95,6 +103,7 @@ def getch(self): return KEY_ESC +@unittest.skipUnless(CURSES_AVAILABLE, "curses might be missing on some platforms") class CanViewerTest(unittest.TestCase): @classmethod def setUpClass(cls): From 4595fa44a32b72a9223277a14200e337693dccd6 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 13 Apr 2021 17:50:44 +0000 Subject: [PATCH 0543/1235] Format code with black --- test/test_viewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_viewer.py b/test/test_viewer.py index 575feb607..7714a3cbc 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -42,6 +42,7 @@ # Allow the curses module to be missing (e.g. on PyPy on Windows) try: import curses + CURSES_AVAILABLE = True except ImportError: curses = None # type: ignore From 1fc6ae2c8a0a0c64e3a80fef437df0d7260a4204 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 20:38:42 +0200 Subject: [PATCH 0544/1235] workaround for ImportError when curses is unavailable --- test/test_viewer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/test_viewer.py b/test/test_viewer.py index 7714a3cbc..4e7a87a57 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -36,7 +36,7 @@ import pytest import can -from can.viewer import KEY_ESC, KEY_SPACE, CanViewer, parse_args +from can.viewer import CanViewer, parse_args # Allow the curses module to be missing (e.g. on PyPy on Windows) @@ -81,6 +81,8 @@ def nodelay(_bool): pass def getch(self): + assert curses is not None + self.key_counter += 1 if self.key_counter == 1: # Send invalid key @@ -88,9 +90,9 @@ def getch(self): elif self.key_counter == 2: return ord("c") # Clear elif self.key_counter == 3: - return KEY_SPACE # Pause + return curses.ascii.SP # Pause elif self.key_counter == 4: - return KEY_SPACE # Unpause + return curses.ascii.SP # Unpause elif self.key_counter == 5: return ord("s") # Sort @@ -101,7 +103,7 @@ def getch(self): elif self.key_counter <= 200: return curses.KEY_UP - return KEY_ESC + return curses.ascii.ESC @unittest.skipUnless(CURSES_AVAILABLE, "curses might be missing on some platforms") From 6ef94a2303324af664ee3a0cccf57b97e0cb27cd Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 20:59:11 +0200 Subject: [PATCH 0545/1235] update badge for travis-ci.com --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 6ce0a8232..619bdcba0 100644 --- a/README.rst +++ b/README.rst @@ -25,8 +25,8 @@ python-can :target: https://python-can.readthedocs.io/en/stable/ :alt: Documentation -.. |build_travis| image:: https://img.shields.io/travis/hardbyte/python-can/develop.svg?label=Travis%20CI - :target: https://travis-ci.org/hardbyte/python-can/branches +.. |build_travis| image:: https://img.shields.io/travis/com/hardbyte/python-can/develop.svg?label=Travis%20CI + :target: https://travis-ci.com/hardbyte/python-can :alt: Travis CI Server for develop branch .. |build_appveyor| image:: https://img.shields.io/appveyor/ci/hardbyte/python-can/develop.svg?label=AppVeyor From 18ecd709d125da14d6b9f7bd6d6139beded91940 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Apr 2021 23:51:19 +0200 Subject: [PATCH 0546/1235] raise timeout for PyPy --- test/back2back_test.py | 9 +++++---- test/config.py | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 1d061a2a5..1ceec073b 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -22,6 +22,7 @@ IS_TRAVIS, TEST_INTERFACE_SOCKETCAN, TEST_CAN_FD, + IS_PYPY, ) @@ -319,7 +320,7 @@ def setUp(self): single_handle=True, ) - @pytest.mark.timeout(5.0) + @pytest.mark.timeout(180.0 if IS_PYPY else 5.0) def test_concurrent_writes(self): sender_pool = ThreadPool(100) receiver_pool = ThreadPool(100) @@ -337,7 +338,7 @@ def sender(msg): self.bus1.send(msg) def receiver(_): - return self.bus2.recv(timeout=2.0) + return self.bus2.recv() sender_pool.map_async(sender, workload) for msg in receiver_pool.map(receiver, len(workload) * [None]): @@ -350,7 +351,7 @@ def receiver(_): receiver_pool.close() receiver_pool.join() - @pytest.mark.timeout(5.0) + @pytest.mark.timeout(180.0 if IS_PYPY else 5.0) def test_filtered_bus(self): sender_pool = ThreadPool(100) receiver_pool = ThreadPool(100) @@ -378,7 +379,7 @@ def sender(msg): self.bus1.send(msg) def receiver(_): - return self.bus2.recv(timeout=2.0) + return self.bus2.recv() sender_pool.map_async(sender, workload) received_msgs = receiver_pool.map(receiver, 500 * [None]) diff --git a/test/config.py b/test/config.py index 3f2516c79..b786200a3 100644 --- a/test/config.py +++ b/test/config.py @@ -57,6 +57,8 @@ def env(name: str) -> bool: + '(platform.system() == "{}")'.format(platform.system()) ) +# ############################## Implementations +IS_PYPY = platform.python_implementation() == "PyPy" # ############################## What tests to run From 909d783a310c6bdb9b3d8686184378b96ad567ab Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 14 Apr 2021 00:06:51 +0200 Subject: [PATCH 0547/1235] colored output for github actions --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ec8a3aac..25834314a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,9 @@ name: Tests on: [push, pull_request] +env: + PY_COLORS: "1" + jobs: test: runs-on: ${{ matrix.os }} From 8547f07ee131b1d944451aac57acf5baf72040a5 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 14 Apr 2021 00:11:20 +0200 Subject: [PATCH 0548/1235] pass PY_COLORS to github tox environment --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index c992a37c6..54aedbea5 100644 --- a/tox.ini +++ b/tox.ini @@ -19,6 +19,7 @@ recreate = True passenv = CI GITHUB_* + PY_COLORS [testenv:travis] passenv = From f8cdb29fad076597e58b9fd61c5322c0aabf7595 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 13 Apr 2021 13:15:08 +0200 Subject: [PATCH 0549/1235] Supported Python implementations --- README.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 6ce0a8232..0d741224e 100644 --- a/README.rst +++ b/README.rst @@ -1,14 +1,18 @@ python-can ========== -|release| |downloads| |downloads_monthly| |formatter| +|release| |python_implementation| |downloads| |downloads_monthly| |formatter| -|docs| |build_travis| |build_appveyor| |coverage| +|docs| |build_travis| |build_appveyor| |coverage| |mergify| .. |release| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ :alt: Latest Version on PyPi +.. |python_implementation| image:: https://img.shields.io/pypi/implementation/python-can + :target: https://pypi.python.org/pypi/python-can/ + :alt: Supported Python implementations + .. |downloads| image:: https://pepy.tech/badge/python-can :target: https://pepy.tech/project/python-can :alt: Downloads on PePy @@ -37,7 +41,7 @@ python-can :target: https://codecov.io/gh/hardbyte/python-can/branch/develop :alt: Test coverage reports on Codecov.io -.. image:: https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/hardbyte/python-can&style=flat +.. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/hardbyte/python-can&style=flat :target: https://mergify.io :alt: Mergify Status From e96417da1a7f293dc058018d0518c1ef0e67db98 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 14 Apr 2021 00:28:15 +0200 Subject: [PATCH 0550/1235] call codecov after GHA tests --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 54aedbea5..46bb17456 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,9 @@ passenv = GITHUB_* PY_COLORS +commands_post = + codecov -X gcov + [testenv:travis] passenv = CI From 39dc1e3223d2f41397ab34439606df2981b03c21 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 14 Apr 2021 10:10:02 +0200 Subject: [PATCH 0551/1235] disable deadline healthcheck on test_message_class.py --- test/test_message_class.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_message_class.py b/test/test_message_class.py index 7156268a5..ad5a1015a 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -7,7 +7,7 @@ from copy import copy, deepcopy import pickle -from hypothesis import given, settings, reproduce_failure +from hypothesis import given, settings import hypothesis.strategies as st from can import Message @@ -35,7 +35,8 @@ class TestMessageClass(unittest.TestCase): bitrate_switch=st.booleans(), error_state_indicator=st.booleans(), ) - @settings(max_examples=2000) + # The first run may take a second on CI runners and will hit the deadline + @settings(max_examples=2000, deadline=None) def test_methods(self, **kwargs): is_valid = not ( ( From 06bfdb9d2e58043601820533603024385014466b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 14 Apr 2021 10:19:13 +0200 Subject: [PATCH 0552/1235] make deadline still count on platforms other than GHA --- test/test_message_class.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_message_class.py b/test/test_message_class.py index ad5a1015a..dd2acc1c9 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -6,6 +6,7 @@ from math import isinf, isnan from copy import copy, deepcopy import pickle +from datetime import timedelta from hypothesis import given, settings import hypothesis.strategies as st @@ -13,6 +14,7 @@ from can import Message from .message_helper import ComparingMessagesTestCase +from .config import IS_GITHUB_ACTIONS class TestMessageClass(unittest.TestCase): @@ -36,7 +38,7 @@ class TestMessageClass(unittest.TestCase): error_state_indicator=st.booleans(), ) # The first run may take a second on CI runners and will hit the deadline - @settings(max_examples=2000, deadline=None) + @settings(max_examples=2000, deadline=None if IS_GITHUB_ACTIONS else timedelta(milliseconds=500)) def test_methods(self, **kwargs): is_valid = not ( ( From 5a43b0048ee2b6db1b5b8b65b5283b5f3422be31 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 14 Apr 2021 08:20:39 +0000 Subject: [PATCH 0553/1235] Format code with black --- test/test_message_class.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_message_class.py b/test/test_message_class.py index dd2acc1c9..2654f79ef 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -38,7 +38,10 @@ class TestMessageClass(unittest.TestCase): error_state_indicator=st.booleans(), ) # The first run may take a second on CI runners and will hit the deadline - @settings(max_examples=2000, deadline=None if IS_GITHUB_ACTIONS else timedelta(milliseconds=500)) + @settings( + max_examples=2000, + deadline=None if IS_GITHUB_ACTIONS else timedelta(milliseconds=500), + ) def test_methods(self, **kwargs): is_valid = not ( ( From f2c93bafa8511683ed2034caa089ad63fd1ae5a0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 14 Apr 2021 10:39:18 +0200 Subject: [PATCH 0554/1235] skip test_asyncio_notifier on python 3.10 --- test/notifier_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/notifier_test.py b/test/notifier_test.py index 0a60bd25d..444874953 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # coding: utf-8 +import sys import unittest import time import asyncio @@ -40,6 +41,8 @@ def test_multiple_bus(self): class AsyncNotifierTest(unittest.TestCase): + + @unittest.skipIf(sys.version_info >= (3, 10), "tracked in #1005") def test_asyncio_notifier(self): loop = asyncio.get_event_loop() bus = can.Bus("test", bustype="virtual", receive_own_messages=True) From 42878186b8a48330d4e615be93d1d0602761c11d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 14 Apr 2021 08:40:28 +0000 Subject: [PATCH 0555/1235] Format code with black --- test/notifier_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/notifier_test.py b/test/notifier_test.py index 444874953..59720b91d 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -41,7 +41,6 @@ def test_multiple_bus(self): class AsyncNotifierTest(unittest.TestCase): - @unittest.skipIf(sys.version_info >= (3, 10), "tracked in #1005") def test_asyncio_notifier(self): loop = asyncio.get_event_loop() From 43ec7c905e31bc9fa32fcda74de61efc13b50581 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 14 Apr 2021 12:22:40 +0200 Subject: [PATCH 0556/1235] Update test/config.py --- test/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/config.py b/test/config.py index b786200a3..f04bf3206 100644 --- a/test/config.py +++ b/test/config.py @@ -36,7 +36,7 @@ def env(name: str) -> bool: if IS_APPVEYOR and IS_TRAVIS and IS_GITHUB_ACTIONS: raise EnvironmentError( - "only one of IS_APPVEYOR and IS_TRAVIS and GITHUB_ACTIONS max be True at the same time" + "only one of IS_APPVEYOR and IS_TRAVIS and GITHUB_ACTIONS may be True at the same time" ) From a39949fc951f386ce973f5288141b07329846988 Mon Sep 17 00:00:00 2001 From: Thomas Willson Date: Sat, 6 Feb 2021 11:36:52 -0800 Subject: [PATCH 0557/1235] Fix compatibility with latest PCAN macOS SDK. --- can/interfaces/pcan/basic.py | 337 ++++++++++++++++++++--------------- can/interfaces/pcan/pcan.py | 21 +-- 2 files changed, 200 insertions(+), 158 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 982adc35e..4220ff7b3 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -1,18 +1,29 @@ -""" -PCAN-Basic API - -Author: Keneth Wagner -Last change: 02.07.2020 Wagner - -Language: Python 2.7, 3.5 - -Copyright (C) 1999-2020 PEAK-System Technik GmbH, Darmstadt, Germany -http://www.peak-system.com -""" +# PCANBasic.py +# +# ~~~~~~~~~~~~ +# +# PCAN-Basic API +# +# ~~~~~~~~~~~~ +# +# ------------------------------------------------------------------ +# Author : Keneth Wagner +# Last change: 14.01.2021 Wagner +# +# Language: Python 2.7, 3.7 +# ------------------------------------------------------------------ +# +# Copyright (C) 1999-2021 PEAK-System Technik GmbH, Darmstadt +# more Info at http://www.peak-system.com +# +# Module Imports +# from ctypes import * +from ctypes.util import find_library from string import * import platform + import logging if platform.system() == "Windows": @@ -41,7 +52,7 @@ # /////////////////////////////////////////////////////////// # Currently defined and supported PCAN channels - +# PCAN_NONEBUS = TPCANHandle(0x00) # Undefined/default value for a PCAN bus PCAN_ISABUS1 = TPCANHandle(0x21) # PCAN-ISA interface, channel 1 @@ -110,6 +121,7 @@ PCAN_LANBUS16 = TPCANHandle(0x810) # PCAN-LAN interface, channel 16 # Represent the PCAN error and status codes +# PCAN_ERROR_OK = TPCANStatus(0x00000) # No error PCAN_ERROR_XMTFULL = TPCANStatus(0x00001) # Transmit buffer in CAN controller is full PCAN_ERROR_OVERRUN = TPCANStatus(0x00002) # CAN controller was read too late @@ -171,10 +183,9 @@ ) # Invalid operation [Value was changed from 0x80000 to 0x8000000] # PCAN devices +# PCAN_NONE = TPCANDevice(0x00) # Undefined, unknown or not selected PCAN device value -PCAN_PEAKCAN = TPCANDevice( - 0x01 -) # PCAN Non-Plug&Play devices. NOT USED WITHIN PCAN-Basic API +PCAN_PEAKCAN = TPCANDevice(0x01) # PCAN Non-PnP devices. NOT USED WITHIN PCAN-Basic API PCAN_ISA = TPCANDevice(0x02) # PCAN-ISA, PCAN-PC/104, and PCAN-PC/104-Plus PCAN_DNG = TPCANDevice(0x03) # PCAN-Dongle PCAN_PCI = TPCANDevice(0x04) # PCAN-PCI, PCAN-cPCI, PCAN-miniPCI, and PCAN-PCI Express @@ -186,8 +197,9 @@ PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices # PCAN parameters -PCAN_DEVICE_ID = TPCANParameter(0x01) # PCAN-USB device identifier parameter -PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 5-Volt power parameter +# +PCAN_DEVICE_ID = TPCANParameter(0x01) # Device identifier parameter +PCAN_5VOLTS_POWER = TPCANParameter(0x02) # 5-Volt power parameter PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter PCAN_MESSAGE_FILTER = TPCANParameter(0x04) # PCAN message filter parameter PCAN_API_VERSION = TPCANParameter(0x05) # PCAN-Basic API version parameter @@ -277,9 +289,11 @@ ) # Get information about PCAN channels attached to a system # DEPRECATED parameters +# PCAN_DEVICE_NUMBER = PCAN_DEVICE_ID # DEPRECATED. Use PCAN_DEVICE_ID instead # PCAN parameter values +# PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) PCAN_PARAMETER_ON = int(0x01) # The PCAN parameter is set (active) PCAN_FILTER_CLOSE = int(0x00) # The PCAN filter is closed. No messages will be received @@ -294,7 +308,7 @@ ) # The PCAN-Channel handle is illegal, or its associated hardware is not available PCAN_CHANNEL_AVAILABLE = int( 0x01 -) # The PCAN-Channel handle is available to be connected (Plug&Play Hardware: it means furthermore that the hardware is plugged-in) +) # The PCAN-Channel handle is available to be connected (PnP Hardware: it means furthermore that the hardware is plugged-in) PCAN_CHANNEL_OCCUPIED = int( 0x02 ) # The PCAN-Channel handle is valid, and is already being used @@ -340,6 +354,7 @@ SERVICE_STATUS_RUNNING = int(0x04) # The service is running # Other constants +# MAX_LENGTH_HARDWARE_NAME = int( 33 ) # Maximum length of the name of a device: 32 characters + terminator @@ -348,6 +363,7 @@ ) # Maximum length of a version string: 17 characters + terminator # PCAN message types +# PCAN_MESSAGE_STANDARD = TPCANMessageType( 0x00 ) # The PCAN message is a CAN Standard Frame (11-bit identifier) @@ -373,7 +389,19 @@ 0x80 ) # The PCAN message represents a PCAN status message +# LookUp Parameters +# +LOOKUP_DEVICE_TYPE = ( + b"devicetype" +) # Lookup channel by Device type (see PCAN devices e.g. PCAN_USB) +LOOKUP_DEVICE_ID = b"deviceid" # Lookup channel by device id +LOOKUP_CONTROLLER_NUMBER = ( + b"controllernumber" +) # Lookup channel by CAN controller 0-based index +LOOKUP_IP_ADDRESS = b"ipaddress" # Lookup channel by IP address (LAN channels only) + # Frame Type / Initialization Mode +# PCAN_MODE_STANDARD = PCAN_MESSAGE_STANDARD PCAN_MODE_EXTENDED = PCAN_MESSAGE_EXTENDED @@ -381,21 +409,21 @@ # You can define your own Baud rate with the BTROBTR1 register. # Take a look at www.peak-system.com for our free software "BAUDTOOL" # to calculate the BTROBTR1 register for every bit rate and sample point. - -PCAN_BAUD_1M = TPCANBaudrate(0x0014) # 1 MBit/s -PCAN_BAUD_800K = TPCANBaudrate(0x0016) # 800 kBit/s -PCAN_BAUD_500K = TPCANBaudrate(0x001C) # 500 kBit/s -PCAN_BAUD_250K = TPCANBaudrate(0x011C) # 250 kBit/s -PCAN_BAUD_125K = TPCANBaudrate(0x031C) # 125 kBit/s -PCAN_BAUD_100K = TPCANBaudrate(0x432F) # 100 kBit/s +# +PCAN_BAUD_1M = TPCANBaudrate(0x0014) # 1 MBit/s +PCAN_BAUD_800K = TPCANBaudrate(0x0016) # 800 kBit/s +PCAN_BAUD_500K = TPCANBaudrate(0x001C) # 500 kBit/s +PCAN_BAUD_250K = TPCANBaudrate(0x011C) # 250 kBit/s +PCAN_BAUD_125K = TPCANBaudrate(0x031C) # 125 kBit/s +PCAN_BAUD_100K = TPCANBaudrate(0x432F) # 100 kBit/s PCAN_BAUD_95K = TPCANBaudrate(0xC34E) # 95,238 kBit/s PCAN_BAUD_83K = TPCANBaudrate(0x852B) # 83,333 kBit/s -PCAN_BAUD_50K = TPCANBaudrate(0x472F) # 50 kBit/s +PCAN_BAUD_50K = TPCANBaudrate(0x472F) # 50 kBit/s PCAN_BAUD_47K = TPCANBaudrate(0x1414) # 47,619 kBit/s PCAN_BAUD_33K = TPCANBaudrate(0x8B2F) # 33,333 kBit/s -PCAN_BAUD_20K = TPCANBaudrate(0x532F) # 20 kBit/s -PCAN_BAUD_10K = TPCANBaudrate(0x672F) # 10 kBit/s -PCAN_BAUD_5K = TPCANBaudrate(0x7F7F) # 5 kBit/s +PCAN_BAUD_20K = TPCANBaudrate(0x532F) # 20 kBit/s +PCAN_BAUD_10K = TPCANBaudrate(0x672F) # 10 kBit/s +PCAN_BAUD_5K = TPCANBaudrate(0x7F7F) # 5 kBit/s # Represents the configuration for a CAN bit rate # Note: @@ -418,7 +446,8 @@ PCAN_BR_DATA_SJW = TPCANBitrateFD(b"data_sjw") PCAN_BR_DATA_SAMPLE = TPCANBitrateFD(b"data_ssp_offset") -# Supported No-Plug-And-Play Hardware types +# Supported Non-PnP Hardware types +# PCAN_TYPE_ISA = TPCANType(0x01) # PCAN-ISA 82C200 PCAN_TYPE_ISA_SJA = TPCANType(0x09) # PCAN-ISA SJA1000 PCAN_TYPE_ISA_PHYTEC = TPCANType(0x04) # PHYTEC ISA @@ -460,6 +489,8 @@ } +# Represents a PCAN message +# class TPCANMsg(Structure): """ Represents a PCAN message @@ -473,22 +504,9 @@ class TPCANMsg(Structure): ] # Data of the message (DATA[0]..DATA[7]) -class TPCANMsgMac(Structure): - """ - Represents a PCAN message - """ - - _fields_ = [ - ( - "ID", - c_ulong, - ), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("MSGTYPE", TPCANMessageType), # Type of the message - ("LEN", c_ubyte), # Data Length Code of the message (0..8) - ("DATA", c_ubyte * 8), - ] # Data of the message (DATA[0]..DATA[7]) - - +# Represents a timestamp of a received PCAN message +# Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow +# class TPCANTimestamp(Structure): """ Represents a timestamp of a received PCAN message @@ -502,22 +520,8 @@ class TPCANTimestamp(Structure): ] # Microseconds: 0..999 -class TPCANTimestampMac(Structure): - """ - Represents a timestamp of a received PCAN message - Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow - """ - - _fields_ = [ - ( - "millis", - c_ulong, - ), # Base-value: milliseconds: 0.. 2^32-1 - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("millis_overflow", c_ushort), # Roll-arounds of millis - ("micros", c_ushort), - ] # Microseconds: 0..999 - - +# Represents a PCAN message from a FD capable hardware +# class TPCANMsgFD(Structure): """ Represents a PCAN message @@ -531,22 +535,8 @@ class TPCANMsgFD(Structure): ] # Data of the message (DATA[0]..DATA[63]) -class TPCANMsgFDMac(Structure): - """ - Represents a PCAN message - """ - - _fields_ = [ - ( - "ID", - c_ulong, - ), # 11/29-bit message identifier - was changed from u_uint to c_ulong, so it is compatible with the PCAN-USB Driver for macOS - ("MSGTYPE", TPCANMessageType), # Type of the message - ("DLC", c_ubyte), # Data Length Code of the message (0..15) - ("DATA", c_ubyte * 64), - ] # Data of the message (DATA[0]..DATA[63]) - - +# Describes an available PCAN channel +# class TPCANChannelInformation(Structure): """ Describes an available PCAN channel @@ -567,14 +557,18 @@ class TPCANChannelInformation(Structure): # PCAN-Basic API function declarations # /////////////////////////////////////////////////////////// - +# PCAN-Basic API class implementation +# class PCANBasic: - """PCAN-Basic API class implementation + """ + PCAN-Basic API class implementation """ def __init__(self): - # Loads the PCANBasic.dll and checks if driver is available + # Loads the PCANBasic API + # if platform.system() == "Windows": + # Loads the API on Windows self.__m_dllBasic = windll.LoadLibrary("PCANBasic") aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) try: @@ -588,13 +582,18 @@ def __init__(self): self.__m_dllBasic = cdll.LoadLibrary("PCANBasic.dll") # Unfortunately cygwin python has no winreg module, so we can't # check for the registry key. + elif platform.system() == "Linux": + # Loads the API on Linux + self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so") elif platform.system() == "Darwin": - self.__m_dllBasic = cdll.LoadLibrary(util.find_library("libPCBUSB.dylib")) + self.__m_dllBasic = cdll.LoadLibrary(find_library("libPCBUSB.dylib")) else: self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so") if self.__m_dllBasic is None: logger.error("Exception: The PCAN-Basic DLL couldn't be loaded!") + # Initializes a PCAN Channel + # def Initialize( self, Channel, @@ -610,10 +609,10 @@ def Initialize( Parameters: Channel : A TPCANHandle representing a PCAN Channel Btr0Btr1 : The speed for the communication (BTR0BTR1 code) - HwType : NON PLUG&PLAY: The type of hardware and operation mode - IOPort : NON PLUG&PLAY: The I/O address for the parallel port - Interrupt: NON PLUG&PLAY: Interrupt number of the parallel port - + HwType : Non-PnP: The type of hardware and operation mode + IOPort : Non-PnP: The I/O address for the parallel port + Interrupt: Non-PnP: Interrupt number of the parallel port + Returns: A TPCANStatus error code """ @@ -626,24 +625,26 @@ def Initialize( logger.error("Exception on PCANBasic.Initialize") raise + # Initializes a FD capable PCAN Channel + # def InitializeFD(self, Channel, BitrateFD): """ - Initializes a FD capable PCAN Channel + Initializes a FD capable PCAN Channel Parameters: Channel : The handle of a FD capable PCAN Channel BitrateFD : The speed for the communication (FD bit rate string) - Remarks: - See PCAN_BR_* values. + Remarks: + See PCAN_BR_* values. * parameter and values must be separated by '=' * Couples of Parameter/value must be separated by ',' * Following Parameter must be filled out: f_clock, data_brp, data_sjw, data_tseg1, data_tseg2, nom_brp, nom_sjw, nom_tseg1, nom_tseg2. - * Following Parameters are optional (not used yet): data_ssp_offset, nom_samp + * Following Parameters are optional (not used yet): data_ssp_offset, nom_sam - Example: + Example: f_clock=80000000,nom_brp=10,nom_tseg1=5,nom_tseg2=2,nom_sjw=1,data_brp=4,data_tseg1=7,data_tseg2=2,data_sjw=1 Returns: @@ -656,17 +657,19 @@ def InitializeFD(self, Channel, BitrateFD): logger.error("Exception on PCANBasic.InitializeFD") raise + # Uninitializes one or all PCAN Channels initialized by CAN_Initialize + # def Uninitialize(self, Channel): """ Uninitializes one or all PCAN Channels initialized by CAN_Initialize - + Remarks: Giving the TPCANHandle value "PCAN_NONEBUS", uninitialize all initialized channels - + Parameters: Channel : A TPCANHandle representing a PCAN Channel - + Returns: A TPCANStatus error code """ @@ -677,17 +680,19 @@ def Uninitialize(self, Channel): logger.error("Exception on PCANBasic.Uninitialize") raise + # Resets the receive and transmit queues of the PCAN Channel + # def Reset(self, Channel): """ Resets the receive and transmit queues of the PCAN Channel - + Remarks: A reset of the CAN controller is not performed - + Parameters: Channel : A TPCANHandle representing a PCAN Channel - + Returns: A TPCANStatus error code """ @@ -698,14 +703,16 @@ def Reset(self, Channel): logger.error("Exception on PCANBasic.Reset") raise + # Gets the current status of a PCAN Channel + # def GetStatus(self, Channel): """ Gets the current status of a PCAN Channel - + Parameters: Channel : A TPCANHandle representing a PCAN Channel - + Returns: A TPCANStatus error code """ @@ -716,62 +723,59 @@ def GetStatus(self, Channel): logger.error("Exception on PCANBasic.GetStatus") raise + # Reads a CAN message from the receive queue of a PCAN Channel + # def Read(self, Channel): """ Reads a CAN message from the receive queue of a PCAN Channel Remarks: - The return value of this method is a 3-touple, where + The return value of this method is a 3-touple, where the first value is the result (TPCANStatus) of the method. The order of the values are: [0]: A TPCANStatus error code [1]: A TPCANMsg structure with the CAN message read [2]: A TPCANTimestamp structure with the time when a message was read - + Parameters: Channel : A TPCANHandle representing a PCAN Channel - + Returns: A touple with three values """ try: - if platform.system() == "Darwin": - msg = TPCANMsgMac() - timestamp = TPCANTimestampMac() - else: - msg = TPCANMsg() - timestamp = TPCANTimestamp() + msg = TPCANMsg() + timestamp = TPCANTimestamp() res = self.__m_dllBasic.CAN_Read(Channel, byref(msg), byref(timestamp)) return TPCANStatus(res), msg, timestamp except: logger.error("Exception on PCANBasic.Read") raise + # Reads a CAN message from the receive queue of a FD capable PCAN Channel + # def ReadFD(self, Channel): """ Reads a CAN message from the receive queue of a FD capable PCAN Channel Remarks: - The return value of this method is a 3-touple, where + The return value of this method is a 3-touple, where the first value is the result (TPCANStatus) of the method. The order of the values are: [0]: A TPCANStatus error code [1]: A TPCANMsgFD structure with the CAN message read [2]: A TPCANTimestampFD that is the time when a message was read - + Parameters: Channel : The handle of a FD capable PCAN Channel - + Returns: A touple with three values """ try: - if platform.system() == "Darwin": - msg = TPCANMsgFDMac() - else: - msg = TPCANMsgFD() + msg = TPCANMsgFD() timestamp = TPCANTimestampFD() res = self.__m_dllBasic.CAN_ReadFD(Channel, byref(msg), byref(timestamp)) return TPCANStatus(res), msg, timestamp @@ -779,15 +783,17 @@ def ReadFD(self, Channel): logger.error("Exception on PCANBasic.ReadFD") raise + # Transmits a CAN message + # def Write(self, Channel, MessageBuffer): """ - Transmits a CAN message - + Transmits a CAN message + Parameters: Channel : A TPCANHandle representing a PCAN Channel MessageBuffer: A TPCANMsg representing the CAN message to be sent - + Returns: A TPCANStatus error code """ @@ -798,15 +804,17 @@ def Write(self, Channel, MessageBuffer): logger.error("Exception on PCANBasic.Write") raise + # Transmits a CAN message over a FD capable PCAN Channel + # def WriteFD(self, Channel, MessageBuffer): """ - Transmits a CAN message over a FD capable PCAN Channel - + Transmits a CAN message over a FD capable PCAN Channel + Parameters: Channel : The handle of a FD capable PCAN Channel MessageBuffer: A TPCANMsgFD buffer with the message to be sent - + Returns: A TPCANStatus error code """ @@ -817,6 +825,8 @@ def WriteFD(self, Channel, MessageBuffer): logger.error("Exception on PCANBasic.WriteFD") raise + # Configures the reception filter + # def FilterMessages(self, Channel, FromID, ToID, Mode): """ @@ -825,14 +835,14 @@ def FilterMessages(self, Channel, FromID, ToID, Mode): Remarks: The message filter will be expanded with every call to this function. If it is desired to reset the filter, please use the 'SetValue' function. - + Parameters: Channel : A TPCANHandle representing a PCAN Channel FromID : A c_uint value with the lowest CAN ID to be received ToID : A c_uint value with the highest CAN ID to be received - Mode : A TPCANMode representing the message type (Standard, 11-bit + Mode : A TPCANMode representing the message type (Standard, 11-bit identifier, or Extended, 29-bit identifier) - + Returns: A TPCANStatus error code """ @@ -843,6 +853,8 @@ def FilterMessages(self, Channel, FromID, ToID, Mode): logger.error("Exception on PCANBasic.FilterMessages") raise + # Retrieves a PCAN Channel value + # def GetValue(self, Channel, Parameter): """ @@ -852,35 +864,37 @@ def GetValue(self, Channel, Parameter): Parameters can be present or not according with the kind of Hardware (PCAN Channel) being used. If a parameter is not available, a PCAN_ERROR_ILLPARAMTYPE error will be returned. - - The return value of this method is a 2-touple, where + + The return value of this method is a 2-touple, where the first value is the result (TPCANStatus) of the method and - the second one, the asked value - + the second one, the asked value + Parameters: Channel : A TPCANHandle representing a PCAN Channel Parameter : The TPCANParameter parameter to get - + Returns: A touple with 2 values """ try: - if Parameter in ( - PCAN_API_VERSION, - PCAN_HARDWARE_NAME, - PCAN_CHANNEL_VERSION, - PCAN_LOG_LOCATION, - PCAN_TRACE_LOCATION, - PCAN_BITRATE_INFO_FD, - PCAN_IP_ADDRESS, - PCAN_FIRMWARE_VERSION, + if ( + Parameter == PCAN_API_VERSION + or Parameter == PCAN_HARDWARE_NAME + or Parameter == PCAN_CHANNEL_VERSION + or Parameter == PCAN_LOG_LOCATION + or Parameter == PCAN_TRACE_LOCATION + or Parameter == PCAN_BITRATE_INFO_FD + or Parameter == PCAN_IP_ADDRESS + or Parameter == PCAN_FIRMWARE_VERSION ): mybuffer = create_string_buffer(256) + elif Parameter == PCAN_ATTACHED_CHANNELS: res = self.GetValue(Channel, PCAN_ATTACHED_CHANNELS_COUNT) if TPCANStatus(res[0]) != PCAN_ERROR_OK: return (TPCANStatus(res[0]),) mybuffer = (TPCANChannelInformation * res[1])() + else: mybuffer = c_int(0) @@ -895,6 +909,9 @@ def GetValue(self, Channel, Parameter): logger.error("Exception on PCANBasic.GetValue") raise + # Returns a descriptive text of a given TPCANStatus + # error code, in any desired language + # def SetValue(self, Channel, Parameter, Buffer): """ @@ -905,18 +922,22 @@ def SetValue(self, Channel, Parameter, Buffer): Parameters can be present or not according with the kind of Hardware (PCAN Channel) being used. If a parameter is not available, a PCAN_ERROR_ILLPARAMTYPE error will be returned. - + Parameters: Channel : A TPCANHandle representing a PCAN Channel Parameter : The TPCANParameter parameter to set Buffer : Buffer with the value to be set BufferLength : Size in bytes of the buffer - + Returns: A TPCANStatus error code """ try: - if Parameter in (PCAN_LOG_LOCATION, PCAN_LOG_TEXT, PCAN_TRACE_LOCATION): + if ( + Parameter == PCAN_LOG_LOCATION + or Parameter == PCAN_LOG_TEXT + or Parameter == PCAN_TRACE_LOCATION + ): mybuffer = create_string_buffer(256) else: mybuffer = c_int(0) @@ -939,16 +960,16 @@ def GetErrorText(self, Error, Language=0): The current languages available for translation are: Neutral (0x00), German (0x07), English (0x09), Spanish (0x0A), - Italian (0x10) and French (0x0C) + Italian (0x10) and French (0x0C) - The return value of this method is a 2-touple, where + The return value of this method is a 2-touple, where the first value is the result (TPCANStatus) of the method and the second one, the error text - + Parameters: Error : A TPCANStatus error code Language : Indicates a 'Primary language ID' (Default is Neutral(0)) - + Returns: A touple with 2 values """ @@ -959,3 +980,29 @@ def GetErrorText(self, Error, Language=0): except: logger.error("Exception on PCANBasic.GetErrorText") raise + + def LookUpChannel(self, Parameters): + + """ + Finds a PCAN-Basic channel that matches with the given parameters + + Remarks: + + The return value of this method is a 2-touple, where + the first value is the result (TPCANStatus) of the method and + the second one a TPCANHandle value + + Parameters: + Parameters : A comma separated string contained pairs of parameter-name/value + to be matched within a PCAN-Basic channel + + Returns: + A touple with 2 values + """ + try: + mybuffer = TPCANHandle(0) + res = self.__m_dllBasic.CAN_LookUpChannel(Parameters, byref(mybuffer)) + return TPCANStatus(res), mybuffer + except: + logger.error("Exception on PCANBasic.LookUpChannel") + raise diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index b8c6c4245..a1c66efa3 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -281,12 +281,13 @@ def __init__( if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) - result = self.m_objPCANBasic.SetValue( - self.m_PcanHandle, PCAN_ALLOW_ERROR_FRAMES, PCAN_PARAMETER_ON - ) + if platform.system() != "Darwin": + result = self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_ALLOW_ERROR_FRAMES, PCAN_PARAMETER_ON + ) - if result != PCAN_ERROR_OK: - raise PcanError(self._get_formatted_error(result)) + if result != PCAN_ERROR_OK: + raise PcanError(self._get_formatted_error(result)) if HAS_EVENTS: self._recv_event = CreateEvent(None, 0, 0, None) @@ -503,10 +504,7 @@ def send(self, msg, timeout=None): if self.fd: # create a TPCANMsg message structure - if platform.system() == "Darwin": - CANMsg = TPCANMsgFDMac() - else: - CANMsg = TPCANMsgFD() + CANMsg = TPCANMsgFD() # configure the message. ID, Length of data, message type and data CANMsg.ID = msg.arbitration_id @@ -524,10 +522,7 @@ def send(self, msg, timeout=None): else: # create a TPCANMsg message structure - if platform.system() == "Darwin": - CANMsg = TPCANMsgMac() - else: - CANMsg = TPCANMsg() + CANMsg = TPCANMsg() # configure the message. ID, Length of data, message type and data CANMsg.ID = msg.arbitration_id From 316a8813cf51d26a000c2f6d86da35ab4dc4380e Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 14 Apr 2021 12:10:38 +0000 Subject: [PATCH 0558/1235] Format code with black --- can/interfaces/pcan/basic.py | 86 ++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 334400a36..91996680d 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -392,12 +392,12 @@ # LookUp Parameters # LOOKUP_DEVICE_TYPE = ( - b"devicetype" -) # Lookup channel by Device type (see PCAN devices e.g. PCAN_USB) + b"devicetype" # Lookup channel by Device type (see PCAN devices e.g. PCAN_USB) +) LOOKUP_DEVICE_ID = b"deviceid" # Lookup channel by device id LOOKUP_CONTROLLER_NUMBER = ( - b"controllernumber" -) # Lookup channel by CAN controller 0-based index + b"controllernumber" # Lookup channel by CAN controller 0-based index +) LOOKUP_IP_ADDRESS = b"ipaddress" # Lookup channel by IP address (LAN channels only) # Frame Type / Initialization Mode @@ -610,7 +610,7 @@ def Initialize( HwType : Non-PnP: The type of hardware and operation mode IOPort : Non-PnP: The I/O address for the parallel port Interrupt: Non-PnP: Interrupt number of the parallel port - + Returns: A TPCANStatus error code """ @@ -628,7 +628,7 @@ def Initialize( def InitializeFD(self, Channel, BitrateFD): """ - Initializes a FD capable PCAN Channel + Initializes a FD capable PCAN Channel Parameters: Channel : The handle of a FD capable PCAN Channel @@ -661,13 +661,13 @@ def Uninitialize(self, Channel): """ Uninitializes one or all PCAN Channels initialized by CAN_Initialize - + Remarks: Giving the TPCANHandle value "PCAN_NONEBUS", uninitialize all initialized channels - + Parameters: Channel : A TPCANHandle representing a PCAN Channel - + Returns: A TPCANStatus error code """ @@ -684,13 +684,13 @@ def Reset(self, Channel): """ Resets the receive and transmit queues of the PCAN Channel - + Remarks: A reset of the CAN controller is not performed - + Parameters: Channel : A TPCANHandle representing a PCAN Channel - + Returns: A TPCANStatus error code """ @@ -707,10 +707,10 @@ def GetStatus(self, Channel): """ Gets the current status of a PCAN Channel - + Parameters: Channel : A TPCANHandle representing a PCAN Channel - + Returns: A TPCANStatus error code """ @@ -729,16 +729,16 @@ def Read(self, Channel): Reads a CAN message from the receive queue of a PCAN Channel Remarks: - The return value of this method is a 3-touple, where + The return value of this method is a 3-touple, where the first value is the result (TPCANStatus) of the method. The order of the values are: [0]: A TPCANStatus error code [1]: A TPCANMsg structure with the CAN message read [2]: A TPCANTimestamp structure with the time when a message was read - + Parameters: Channel : A TPCANHandle representing a PCAN Channel - + Returns: A touple with three values """ @@ -759,16 +759,16 @@ def ReadFD(self, Channel): Reads a CAN message from the receive queue of a FD capable PCAN Channel Remarks: - The return value of this method is a 3-touple, where + The return value of this method is a 3-touple, where the first value is the result (TPCANStatus) of the method. The order of the values are: [0]: A TPCANStatus error code [1]: A TPCANMsgFD structure with the CAN message read [2]: A TPCANTimestampFD that is the time when a message was read - + Parameters: Channel : The handle of a FD capable PCAN Channel - + Returns: A touple with three values """ @@ -786,12 +786,12 @@ def ReadFD(self, Channel): def Write(self, Channel, MessageBuffer): """ - Transmits a CAN message - + Transmits a CAN message + Parameters: Channel : A TPCANHandle representing a PCAN Channel MessageBuffer: A TPCANMsg representing the CAN message to be sent - + Returns: A TPCANStatus error code """ @@ -807,12 +807,12 @@ def Write(self, Channel, MessageBuffer): def WriteFD(self, Channel, MessageBuffer): """ - Transmits a CAN message over a FD capable PCAN Channel - + Transmits a CAN message over a FD capable PCAN Channel + Parameters: Channel : The handle of a FD capable PCAN Channel MessageBuffer: A TPCANMsgFD buffer with the message to be sent - + Returns: A TPCANStatus error code """ @@ -833,14 +833,14 @@ def FilterMessages(self, Channel, FromID, ToID, Mode): Remarks: The message filter will be expanded with every call to this function. If it is desired to reset the filter, please use the 'SetValue' function. - + Parameters: Channel : A TPCANHandle representing a PCAN Channel FromID : A c_uint value with the lowest CAN ID to be received ToID : A c_uint value with the highest CAN ID to be received - Mode : A TPCANMode representing the message type (Standard, 11-bit + Mode : A TPCANMode representing the message type (Standard, 11-bit identifier, or Extended, 29-bit identifier) - + Returns: A TPCANStatus error code """ @@ -862,15 +862,15 @@ def GetValue(self, Channel, Parameter): Parameters can be present or not according with the kind of Hardware (PCAN Channel) being used. If a parameter is not available, a PCAN_ERROR_ILLPARAMTYPE error will be returned. - - The return value of this method is a 2-touple, where + + The return value of this method is a 2-touple, where the first value is the result (TPCANStatus) of the method and - the second one, the asked value - + the second one, the asked value + Parameters: Channel : A TPCANHandle representing a PCAN Channel Parameter : The TPCANParameter parameter to get - + Returns: A touple with 2 values """ @@ -920,13 +920,13 @@ def SetValue(self, Channel, Parameter, Buffer): Parameters can be present or not according with the kind of Hardware (PCAN Channel) being used. If a parameter is not available, a PCAN_ERROR_ILLPARAMTYPE error will be returned. - + Parameters: Channel : A TPCANHandle representing a PCAN Channel Parameter : The TPCANParameter parameter to set Buffer : Buffer with the value to be set BufferLength : Size in bytes of the buffer - + Returns: A TPCANStatus error code """ @@ -958,16 +958,16 @@ def GetErrorText(self, Error, Language=0): The current languages available for translation are: Neutral (0x00), German (0x07), English (0x09), Spanish (0x0A), - Italian (0x10) and French (0x0C) + Italian (0x10) and French (0x0C) - The return value of this method is a 2-touple, where + The return value of this method is a 2-touple, where the first value is the result (TPCANStatus) of the method and the second one, the error text - + Parameters: Error : A TPCANStatus error code Language : Indicates a 'Primary language ID' (Default is Neutral(0)) - + Returns: A touple with 2 values """ @@ -986,12 +986,12 @@ def LookUpChannel(self, Parameters): Remarks: - The return value of this method is a 2-touple, where + The return value of this method is a 2-touple, where the first value is the result (TPCANStatus) of the method and the second one a TPCANHandle value - + Parameters: - Parameters : A comma separated string contained pairs of parameter-name/value + Parameters : A comma separated string contained pairs of parameter-name/value to be matched within a PCAN-Basic channel Returns: From bea8c7cca4a1722fd781f4c5bd67b9488b540311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Guti=C3=A9rrez?= Date: Wed, 14 Apr 2021 14:13:39 +0200 Subject: [PATCH 0559/1235] Add support for changing the loopback flag (#960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add flag to set/unset local loopback on the bus * fix typo * fix black formatting * add a test for the local_loopback flag * run black on the new test * assertion that we are not receiving messages on the local socket * add some more info on the local loopback variable Co-authored-by: Juanjo Gutiérrez --- can/interfaces/socketcan/socketcan.py | 33 +++++++++--- test/test_socketcan_loopback.py | 74 +++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 test/test_socketcan_loopback.py diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 28c16482a..6132653c6 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -133,7 +133,7 @@ def bcm_header_factory( def build_can_frame(msg: Message) -> bytes: - """ CAN frame packing/unpacking (see 'struct can_frame' in ) + """CAN frame packing/unpacking (see 'struct can_frame' in ) /** * struct can_frame - basic CAN frame structure * @can_id: the CAN ID of the frame and CAN_*_FLAG flags, see above. @@ -446,10 +446,7 @@ def start(self) -> None: class MultiRateCyclicSendTask(CyclicSendTask): - """Exposes more of the full power of the TX_SETUP opcode. - - - """ + """Exposes more of the full power of the TX_SETUP opcode.""" def __init__( self, @@ -583,7 +580,7 @@ def capture_message( class SocketcanBus(BusABC): - """ A SocketCAN interface to CAN. + """A SocketCAN interface to CAN. It implements :meth:`can.BusABC._detect_available_configs` to search for available interfaces. @@ -593,6 +590,7 @@ def __init__( self, channel: str = "", receive_own_messages: bool = False, + local_loopback: bool = True, fd: bool = False, can_filters: Optional[CanFilters] = None, **kwargs, @@ -613,6 +611,13 @@ def __init__( channel using :attr:`can.Message.channel`. :param receive_own_messages: If transmitted messages should also be received by this bus. + :param local_loopback: + If local loopback should be enabled on this bus. + Please note that local loopback does not mean that messages sent + on a socket will be readable on the same socket, they will only + be readable on other open sockets on the same machine. More info + can be read on the socketcan documentation: + See https://www.kernel.org/doc/html/latest/networking/can.html#socketcan-local-loopback1 :param fd: If CAN-FD frames should be supported. :param can_filters: @@ -626,6 +631,14 @@ def __init__( self._task_id = 0 self._task_id_guard = threading.Lock() + # set the local_loopback parameter + try: + self.socket.setsockopt( + SOL_CAN_RAW, CAN_RAW_LOOPBACK, 1 if local_loopback else 0 + ) + except socket.error as error: + log.error("Could not set local loopback flag(%s)", error) + # set the receive_own_messages parameter try: self.socket.setsockopt( @@ -648,7 +661,13 @@ def __init__( log.error("Could not enable error frames (%s)", error) bind_socket(self.socket, channel) - kwargs.update({"receive_own_messages": receive_own_messages, "fd": fd}) + kwargs.update( + { + "receive_own_messages": receive_own_messages, + "fd": fd, + "local_loopback": local_loopback, + } + ) super().__init__(channel=channel, can_filters=can_filters, **kwargs) def shutdown(self) -> None: diff --git a/test/test_socketcan_loopback.py b/test/test_socketcan_loopback.py new file mode 100644 index 000000000..9dce11dc5 --- /dev/null +++ b/test/test_socketcan_loopback.py @@ -0,0 +1,74 @@ +""" +This module tests sending messages on socketcan with and without local_loopback flag + +for a good explanation of why this might be needed: +https://www.kernel.org/doc/html/v4.17/networking/can.html#socketcan-local-loopback1 +""" +import unittest + +import can + +from .config import TEST_INTERFACE_SOCKETCAN + + +@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +class LocalLoopbackSocketCan(unittest.TestCase): + """ test local_loopback functionality""" + + BITRATE = 500000 + TIMEOUT = 0.1 + + INTERFACE_1 = "socketcan" + CHANNEL_1 = "vcan0" + INTERFACE_2 = "socketcan" + CHANNEL_2 = "vcan0" + + def setUp(self): + self._recv_bus = can.Bus( + interface=self.INTERFACE_2, channel=self.CHANNEL_2, bitrate=self.BITRATE + ) + + def tearDown(self): + self._recv_bus.shutdown() + + def test_sending_message_with_loopback_enabled(self): + """test that sending messages with local_loopback=True produces output even + on the local device""" + loopback_send_bus = can.Bus( + interface=self.INTERFACE_1, + channel=self.CHANNEL_1, + bitrate=self.BITRATE, + local_loopback=True, + ) + try: + msg = can.Message(arbitration_id=0x123, is_extended_id=False) + loopback_send_bus.send(msg) + recv_msg = self._recv_bus.recv(self.TIMEOUT) + self.assertIsNotNone(recv_msg) + recv_msg_lb = loopback_send_bus.recv(self.TIMEOUT) + self.assertIsNone(recv_msg_lb) + finally: + loopback_send_bus.shutdown() + + def test_sending_message_without_loopback_enabled(self): + """test that sending messages with local_loopback=False does not produce output + on the local device""" + noloopback_send_bus = can.Bus( + interface=self.INTERFACE_1, + channel=self.CHANNEL_1, + bitrate=self.BITRATE, + local_loopback=False, + ) + try: + msg = can.Message(arbitration_id=0x123, is_extended_id=False) + noloopback_send_bus.send(msg) + recv_msg = self._recv_bus.recv(self.TIMEOUT) + self.assertIsNone(recv_msg) + recv_msg_nlb = noloopback_send_bus.recv(self.TIMEOUT) + self.assertIsNone(recv_msg_nlb) + finally: + noloopback_send_bus.shutdown() + + +if __name__ == "__main__": + unittest.main() From 797f8902c4b5478c37cc4bed8cfe150f7b433226 Mon Sep 17 00:00:00 2001 From: wiboticalex <38794526+wiboticalex@users.noreply.github.com> Date: Wed, 14 Apr 2021 05:33:46 -0700 Subject: [PATCH 0560/1235] USB2CAN Windows error 8 fix (#989) * Include USB2CAN DLL status code definitions in an enum * Ignore USB2CAN FIFO_EMPTY receive * format with black --- can/interfaces/usb2can/usb2canInterface.py | 12 ++-- .../usb2can/usb2canabstractionlayer.py | 58 +++++++++++++++---- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 8e0f54687..479c54764 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -125,7 +125,7 @@ def send(self, msg, timeout=None): else: status = self.can.send(self.handle, byref(tx)) - if status != CANAL_ERROR_SUCCESS: + if status != CanalError.SUCCESS: raise CanError("could not send message: status == {}".format(status)) def _recv_internal(self, timeout): @@ -139,9 +139,13 @@ def _recv_internal(self, timeout): time = 0 if timeout is None else int(timeout * 1000) status = self.can.blocking_receive(self.handle, byref(messagerx), time) - if status == CANAL_ERROR_SUCCESS: + if status == CanalError.SUCCESS: rx = message_convert_rx(messagerx) - elif status in (CANAL_ERROR_RCV_EMPTY, CANAL_ERROR_TIMEOUT): + elif status in ( + CanalError.RCV_EMPTY, + CanalError.TIMEOUT, + CanalError.FIFO_EMPTY, + ): rx = None else: log.error("Canal Error %s", status) @@ -157,7 +161,7 @@ def shutdown(self): """ status = self.can.close(self.handle) - if status != CANAL_ERROR_SUCCESS: + if status != CanalError.SUCCESS: raise CanError("could not shut down bus: status == {}".format(status)) @staticmethod diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index 1f336b241..ce8157a09 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -5,6 +5,7 @@ from ctypes import * from struct import * +from enum import Enum import logging import can @@ -23,9 +24,46 @@ IS_REMOTE_FRAME = 2 IS_ID_TYPE = 1 -CANAL_ERROR_SUCCESS = 0 -CANAL_ERROR_RCV_EMPTY = 19 -CANAL_ERROR_TIMEOUT = 32 + +class CanalError(Enum): + SUCCESS = 0 + BAUDRATE = 1 + BUS_OFF = 2 + BUS_PASSIVE = 3 + BUS_WARNING = 4 + CAN_ID = 5 + CAN_MESSAGE = 6 + CHANNEL = 7 + FIFO_EMPTY = 8 + FIFO_FULL = 9 + FIFO_SIZE = 10 + FIFO_WAIT = 11 + GENERIC = 12 + HARDWARE = 13 + INIT_FAIL = 14 + INIT_MISSING = 15 + INIT_READY = 16 + NOT_SUPPORTED = 17 + OVERRUN = 18 + RCV_EMPTY = 19 + REGISTER = 20 + TRM_FULL = 21 + ERRFRM_STUFF = 22 + ERRFRM_FORM = 23 + ERRFRM_ACK = 24 + ERRFRM_BIT1 = 25 + ERRFRM_BIT0 = 26 + ERRFRM_CRC = 27 + LIBRARY = 28 + PROCADDRESS = 29 + ONLY_ONE_INSTANCE = 30 + SUB_DRIVER = 31 + TIMEOUT = 32 + NOT_OPEN = 33 + PARAMETER = 34 + MEMORY = 35 + INTERNAL = 36 + COMMUNICATION = 37 class CanalStatistics(Structure): @@ -119,7 +157,7 @@ def open(self, configuration, flags): def close(self, handle): try: res = self.__m_dllBasic.CanalClose(handle) - return res + return CanalError(res) except: log.warning("Failed to close") raise @@ -127,7 +165,7 @@ def close(self, handle): def send(self, handle, msg): try: res = self.__m_dllBasic.CanalSend(handle, msg) - return res + return CanalError(res) except: log.warning("Sending error") raise can.CanError("Failed to transmit frame") @@ -135,7 +173,7 @@ def send(self, handle, msg): def receive(self, handle, msg): try: res = self.__m_dllBasic.CanalReceive(handle, msg) - return res + return CanalError(res) except: log.warning("Receive error") raise @@ -143,7 +181,7 @@ def receive(self, handle, msg): def blocking_send(self, handle, msg, timeout): try: res = self.__m_dllBasic.CanalBlockingSend(handle, msg, timeout) - return res + return CanalError(res) except: log.warning("Blocking send error") raise @@ -151,7 +189,7 @@ def blocking_send(self, handle, msg, timeout): def blocking_receive(self, handle, msg, timeout): try: res = self.__m_dllBasic.CanalBlockingReceive(handle, msg, timeout) - return res + return CanalError(res) except: log.warning("Blocking Receive Failed") raise @@ -159,7 +197,7 @@ def blocking_receive(self, handle, msg, timeout): def get_status(self, handle, status): try: res = self.__m_dllBasic.CanalGetStatus(handle, status) - return res + return CanalError(res) except: log.warning("Get status failed") raise @@ -167,7 +205,7 @@ def get_status(self, handle, status): def get_statistics(self, handle, statistics): try: res = self.__m_dllBasic.CanalGetStatistics(handle, statistics) - return res + return CanalError(res) except: log.warning("Get Statistics failed") raise From 5378bf13fd6d78614b93f70a72e12860933675b5 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 15 Apr 2021 22:14:54 +0200 Subject: [PATCH 0561/1235] New wording for feature list in README (#1011) --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 07971b586..6f7ab3ed0 100644 --- a/README.rst +++ b/README.rst @@ -74,11 +74,11 @@ Features - support for many different backends (see the `docs `__) - receiving, sending, and periodically sending messages - normal and extended arbitration IDs -- limited `CAN FD `__ support +- `CAN FD `__ support - many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), CSV, SQLite and Canutils log - efficient in-kernel or in-hardware filtering of messages on supported interfaces -- bus configuration reading from file or environment variables -- CLI tools for working with CAN buses (see the `docs `__) +- bus configuration reading from a file or from environment variables +- command line tools for working with CAN buses (see the `docs `__) - more From 40078c7dea08d8e05c44c63b41f6673c3d3fc82d Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 15 Apr 2021 23:12:45 +0200 Subject: [PATCH 0562/1235] Remove AppVeyor since it is now redundant (#1009) Remove AppVeyor and clean up testing configuration --- .appveyor.yml | 24 ------------------------ README.rst | 6 +----- test/config.py | 29 ++++++++++++----------------- test/test_message_sync.py | 4 ++-- tox.ini | 11 ----------- 5 files changed, 15 insertions(+), 59 deletions(-) delete mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index e7e9c80f9..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,24 +0,0 @@ -environment: - matrix: - - # For Python versions available on Appveyor, see - # https://www.appveyor.com/docs/windows-images-software/#python - # Only Python 3.6+ is supported - - - PYTHON: "C:\\Python36" - - PYTHON: "C:\\Python37" - - PYTHON: "C:\\Python36-x64" - - PYTHON: "C:\\Python37-x64" - -install: - # Prepend Python installation and scripts (e.g. pytest) to PATH - - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% - - # Install tox - - "pip install tox" - -build: off - -test_script: - # run tests - - "tox -e appveyor" diff --git a/README.rst b/README.rst index 6f7ab3ed0..9000c5c77 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ python-can |release| |python_implementation| |downloads| |downloads_monthly| |formatter| -|docs| |build_travis| |build_appveyor| |coverage| |mergify| +|docs| |build_travis| |coverage| |mergify| .. |release| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ @@ -33,10 +33,6 @@ python-can :target: https://travis-ci.com/hardbyte/python-can :alt: Travis CI Server for develop branch -.. |build_appveyor| image:: https://img.shields.io/appveyor/ci/hardbyte/python-can/develop.svg?label=AppVeyor - :target: https://ci.appveyor.com/project/hardbyte/python-can/history - :alt: AppVeyor CI Server for develop branch - .. |coverage| image:: https://codecov.io/gh/hardbyte/python-can/branch/develop/graph/badge.svg :target: https://codecov.io/gh/hardbyte/python-can/branch/develop :alt: Test coverage reports on Codecov.io diff --git a/test/config.py b/test/config.py index f04bf3206..58ad780fc 100644 --- a/test/config.py +++ b/test/config.py @@ -5,7 +5,7 @@ This module contains various configuration for the tests. Some tests are skipped when run on a CI server because they are not -reproducible, see #243 (https://github.com/hardbyte/python-can/issues/243). +reproducible, see for example #243 and #940. """ import platform @@ -20,23 +20,17 @@ def env(name: str) -> bool: # see here for the environment variables that are set on the CI servers: # - https://docs.travis-ci.com/user/environment-variables/ -# - https://www.appveyor.com/docs/environment-variables/ +# - https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables IS_TRAVIS = env("TRAVIS") -IS_APPVEYOR = env("APPVEYOR") IS_GITHUB_ACTIONS = env("GITHUB_ACTIONS") -IS_CI = ( - IS_TRAVIS - or IS_APPVEYOR - or IS_GITHUB_ACTIONS - or env("CI") - or env("CONTINUOUS_INTEGRATION") -) +IS_CI = IS_TRAVIS or IS_GITHUB_ACTIONS or env("CI") or env("CONTINUOUS_INTEGRATION") -if IS_APPVEYOR and IS_TRAVIS and IS_GITHUB_ACTIONS: +if IS_TRAVIS and IS_GITHUB_ACTIONS: raise EnvironmentError( - "only one of IS_APPVEYOR and IS_TRAVIS and GITHUB_ACTIONS may be True at the same time" + f"only one of IS_TRAVIS ({IS_TRAVIS}) and IS_GITHUB_ACTIONS ({IS_GITHUB_ACTIONS}) may be True at the " + "same time" ) @@ -47,19 +41,20 @@ def env(name: str) -> bool: IS_LINUX = "linux" in _sys IS_OSX = "darwin" in _sys IS_UNIX = IS_LINUX or IS_OSX +del _sys if (IS_WINDOWS and IS_LINUX) or (IS_LINUX and IS_OSX) or (IS_WINDOWS and IS_OSX): raise EnvironmentError( - "only one of IS_WINDOWS ({}), IS_LINUX ({}) and IS_OSX ({}) ".format( - IS_WINDOWS, IS_LINUX, IS_OSX - ) - + "can be True at the same time " - + '(platform.system() == "{}")'.format(platform.system()) + f"only one of IS_WINDOWS ({IS_WINDOWS}), IS_LINUX ({IS_LINUX}) and IS_OSX ({IS_OSX}) " + f'can be True at the same time (platform.system() == "{platform.system()}")' ) + # ############################## Implementations + IS_PYPY = platform.python_implementation() == "PyPy" + # ############################## What tests to run TEST_CAN_FD = True diff --git a/test/test_message_sync.py b/test/test_message_sync.py index a20cc9d65..6e6a89a4d 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -14,7 +14,7 @@ from can import MessageSync, Message -from .config import IS_CI, IS_APPVEYOR, IS_TRAVIS, IS_OSX, IS_GITHUB_ACTIONS, IS_LINUX +from .config import IS_CI, IS_TRAVIS, IS_OSX, IS_GITHUB_ACTIONS, IS_LINUX from .message_helper import ComparingMessagesTestCase from .data.example_data import TEST_MESSAGES_BASE @@ -31,7 +31,7 @@ def inc(value): skip_on_unreliable_platforms = unittest.skipIf( - IS_APPVEYOR or (IS_TRAVIS and IS_OSX) or (IS_GITHUB_ACTIONS and not IS_LINUX), + (IS_TRAVIS and IS_OSX) or (IS_GITHUB_ACTIONS and not IS_LINUX), "this environment's timings are too unpredictable", ) diff --git a/tox.ini b/tox.ini index 46bb17456..a29c3cafe 100644 --- a/tox.ini +++ b/tox.ini @@ -31,17 +31,6 @@ passenv = TRAVIS_* TEST_SOCKETCAN -commands_post = - codecov -X gcov - -[testenv:appveyor] -passenv = - CI - APPVEYOR - APPVEYOR_* - -extras = neovi - commands_post = codecov -X gcov From fcb337df3b6fc664304abf6999a3050aa78723fe Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 16 Apr 2021 01:22:42 +0200 Subject: [PATCH 0563/1235] Deprecate loop argument (#1013) --- can/listener.py | 20 ++++++++++++++++---- test/listener_test.py | 15 ++++++++++++++- test/notifier_test.py | 2 -- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/can/listener.py b/can/listener.py index 05762cad0..2695e80c5 100644 --- a/can/listener.py +++ b/can/listener.py @@ -1,7 +1,8 @@ """ This module contains the implementation of `can.Listener` and some readers. """ - +import sys +import warnings from typing import AsyncIterator, Awaitable, Optional from can.message import Message @@ -135,9 +136,20 @@ class AsyncBufferedReader(Listener): print(msg) """ - def __init__(self, loop: Optional[asyncio.events.AbstractEventLoop] = None): - # set to "infinite" size - self.buffer: "asyncio.Queue[Message]" = asyncio.Queue(loop=loop) + def __init__(self, **kwargs): + self.buffer: "asyncio.Queue[Message]" + + if "loop" in kwargs.keys(): + warnings.warn( + "The 'loop' argument is deprecated since python-can 4.0.0 " + "and has no effect starting with Python 3.10", + DeprecationWarning, + ) + if sys.version_info < (3, 10): + self.buffer = asyncio.Queue(loop=kwargs["loop"]) + return + + self.buffer = asyncio.Queue() def on_message_received(self, msg: Message): """Append a message to the buffer. diff --git a/test/listener_test.py b/test/listener_test.py index 6d24df190..83b4a63fc 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -3,12 +3,13 @@ """ """ - +import asyncio import unittest import random import logging import tempfile import os +import warnings from os.path import join, dirname import can @@ -157,5 +158,17 @@ def testBufferedListenerReceives(self): self.assertIsNotNone(a_listener.get_message(0.1)) +def test_deprecated_loop_arg(recwarn): + warnings.simplefilter("always") + can.AsyncBufferedReader(loop=asyncio.get_event_loop()) + assert len(recwarn) > 0 + assert recwarn.pop(DeprecationWarning) + recwarn.clear() + + # assert that no warning is shown when loop argument is not used + can.AsyncBufferedReader() + assert len(recwarn) == 0 + + if __name__ == "__main__": unittest.main() diff --git a/test/notifier_test.py b/test/notifier_test.py index 59720b91d..0a60bd25d 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # coding: utf-8 -import sys import unittest import time import asyncio @@ -41,7 +40,6 @@ def test_multiple_bus(self): class AsyncNotifierTest(unittest.TestCase): - @unittest.skipIf(sys.version_info >= (3, 10), "tracked in #1005") def test_asyncio_notifier(self): loop = asyncio.get_event_loop() bus = can.Bus("test", bustype="virtual", receive_own_messages=True) From ab93ce8b934db3537c79e6346d79d9656ccb06c9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 16 Apr 2021 11:20:24 +0200 Subject: [PATCH 0564/1235] add nanosecond resolution timestamping to socketcan --- can/interfaces/socketcan/constants.py | 3 +++ can/interfaces/socketcan/socketcan.py | 39 ++++++++++++++++++--------- doc/interfaces/socketcan.rst | 8 +++--- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index 0db298371..37d4847c4 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -2,6 +2,9 @@ Defines shared CAN constants. """ +# Generic socket constants +SO_TIMESTAMPNS = 35 + CAN_ERR_FLAG = 0x20000000 CAN_RTR_FLAG = 0x40000000 CAN_EFF_FLAG = 0x80000000 diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 6132653c6..917febb19 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -494,6 +494,8 @@ def bind_socket(sock: socket.socket, channel: str = "can0") -> None: :param sock: The socket to be bound + :param channel: + The channel / interface to ind to :raises OSError: If the specified interface isn't found. """ @@ -517,24 +519,25 @@ def capture_message( """ # Fetching the Arb ID, DLC and Data try: + cf, ancillary_data, msg_flags, addr = sock.recvmsg(CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE) if get_channel: - cf, _, msg_flags, addr = sock.recvmsg(CANFD_MTU) channel = addr[0] if isinstance(addr, tuple) else addr else: - cf, _, msg_flags, _ = sock.recvmsg(CANFD_MTU) channel = None - except socket.error as exc: - raise can.CanError("Error receiving: %s" % exc) + except socket.error as error: + raise can.CanError(f"Error receiving: {error}") can_id, can_dlc, flags, data = dissect_can_frame(cf) - # log.debug('Received: can_id=%x, can_dlc=%x, data=%s', can_id, can_dlc, data) # Fetching the timestamp - binary_structure = "@LL" - res = fcntl.ioctl(sock.fileno(), SIOCGSTAMP, struct.pack(binary_structure, 0, 0)) - - seconds, microseconds = struct.unpack(binary_structure, res) - timestamp = seconds + microseconds * 1e-6 + assert len(ancillary_data) == 1, "only requested a single extra field" + cmsg_level, cmsg_type, cmsg_data = ancillary_data[0] + assert ( + cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS + ), "received control message type that was not requested" + # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details + seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data) + timestamp = seconds + nanoseconds * 1e-9 # EXT, RTR, ERR flags -> boolean attributes # /* special address description flags for the CAN_ID */ @@ -574,11 +577,14 @@ def capture_message( data=data, ) - # log_rx.debug('Received: %s', msg) - return msg +# Constants needed for precise handling of timestamps +RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@II") +RECEIVED_ANCILLARY_BUFFER_SIZE = socket.CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) + + class SocketcanBus(BusABC): """A SocketCAN interface to CAN. @@ -647,7 +653,7 @@ def __init__( except socket.error as error: log.error("Could not receive own messages (%s)", error) - # enable CAN-FD frames + # enable CAN-FD frames if desired if fd: try: self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1) @@ -660,6 +666,13 @@ def __init__( except socket.error as error: log.error("Could not enable error frames (%s)", error) + # enable nanosecond resolution timestamping + # we can always do this since + # 1) is is guaranteed to be at least as precise as without + # 2) it is available since Linux 2.6.22, and CAN support was only added afterward + # so this is always supported by the kernel + self.socket.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) + bind_socket(self.socket, channel) kwargs.update( { diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 1783c388c..4a6d093bd 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -18,9 +18,8 @@ The `SocketCAN`_ documentation can be found in the Linux kernel docs at .. important:: `python-can` versions before 2.2 had two different implementations named - ``socketcan_ctypes`` and ``socketcan_native``. These are now - deprecated and the aliases to ``socketcan`` will be removed in - version 4.0. 3.x releases raise a DeprecationWarning. + ``socketcan_ctypes`` and ``socketcan_native``. These were removed in + version 4.0.0 after a deprecation period. Socketcan Quickstart @@ -248,7 +247,8 @@ The :class:`~can.interfaces.socketcan.SocketcanBus` specializes :class:`~can.Bus to ensure usage of SocketCAN Linux API. The most important differences are: - usage of SocketCAN BCM for periodic messages scheduling; -- filtering of CAN messages on Linux kernel level. +- filtering of CAN messages on Linux kernel level; +- usage of nanosecond timings form the kernel. .. autoclass:: can.interfaces.socketcan.SocketcanBus :members: From 6167fac39080f5fb18439ccd4d8896f653ed0c3c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 16 Apr 2021 09:21:05 +0000 Subject: [PATCH 0565/1235] Format code with black --- can/interfaces/socketcan/socketcan.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 917febb19..293d5d841 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -519,7 +519,9 @@ def capture_message( """ # Fetching the Arb ID, DLC and Data try: - cf, ancillary_data, msg_flags, addr = sock.recvmsg(CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE) + cf, ancillary_data, msg_flags, addr = sock.recvmsg( + CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE + ) if get_channel: channel = addr[0] if isinstance(addr, tuple) else addr else: @@ -533,7 +535,7 @@ def capture_message( assert len(ancillary_data) == 1, "only requested a single extra field" cmsg_level, cmsg_type, cmsg_data = ancillary_data[0] assert ( - cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS + cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS ), "received control message type that was not requested" # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data) From 54c14bae93bc1a8ac25840ec60e9cae492549361 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 16 Apr 2021 11:32:23 +0200 Subject: [PATCH 0566/1235] fix crash on windows --- can/interfaces/socketcan/socketcan.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 917febb19..18166234a 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -27,6 +27,14 @@ log.error("fcntl not available on this platform") +try: + from socket import CMSG_SPACE + CMSG_SPACE_available = True +except ImportError: + CMSG_SPACE_available = False + log.error("socket.CMSG_SPACE not available on this platform") + + import can from can import Message, BusABC from can.broadcastmanager import ( @@ -38,6 +46,7 @@ from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces + # Setup BCM struct def bcm_header_factory( fields: List[Tuple[str, Union[Type[ctypes.c_uint32], Type[ctypes.c_long]]]], @@ -581,8 +590,9 @@ def capture_message( # Constants needed for precise handling of timestamps -RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@II") -RECEIVED_ANCILLARY_BUFFER_SIZE = socket.CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) +if CMSG_SPACE_available: + RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@II") + RECEIVED_ANCILLARY_BUFFER_SIZE = CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) class SocketcanBus(BusABC): From 6c022eb069fdc57767af3e3292c50d7245e47391 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 16 Apr 2021 09:33:38 +0000 Subject: [PATCH 0567/1235] Format code with black --- can/interfaces/socketcan/socketcan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 40dd068fd..0990d03ca 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -29,6 +29,7 @@ try: from socket import CMSG_SPACE + CMSG_SPACE_available = True except ImportError: CMSG_SPACE_available = False From 84e1f36aee68a0a67c62c9602c1025dd871916b2 Mon Sep 17 00:00:00 2001 From: Tuukka Pasanen Date: Sat, 17 Apr 2021 10:17:11 +0300 Subject: [PATCH 0568/1235] Add Neousys WDT_DIO CAN interface (#980) * Add Neousys WDT_DIO CAN interface * Neousys: Rename can/interfaces/neousys_wdt.py to can/interfaces/neousys/__init__.py * Neousys: reorganise stuff from init to own file and rename NeousysWdtBus to NeousysBus * Neousys: Remove threading code and start using Queue as thread safe message bug * Neousys: Add minimal unittest case(s) for creating, receiving, sending and shutdown * Update test_neousys.py * Neousys: Update Pylint disables that they are in more readable form Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> * Neousys: Make super-method more Python 3.x Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> Co-authored-by: Brian Thorne Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/interfaces/__init__.py | 1 + can/interfaces/neousys/__init__.py | 3 + can/interfaces/neousys/neousys.py | 271 +++++++++++++++++++++++++++++ test/test_neousys.py | 113 ++++++++++++ 4 files changed, 388 insertions(+) create mode 100644 can/interfaces/neousys/__init__.py create mode 100644 can/interfaces/neousys/neousys.py create mode 100644 test/test_neousys.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index ae088c4bc..bfedea60a 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -28,6 +28,7 @@ "cantact": ("can.interfaces.cantact", "CantactBus"), "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"), + "neousys": ("can.interfaces.neousys", "NeousysBus"), } BACKENDS.update( diff --git a/can/interfaces/neousys/__init__.py b/can/interfaces/neousys/__init__.py new file mode 100644 index 000000000..3aa87332c --- /dev/null +++ b/can/interfaces/neousys/__init__.py @@ -0,0 +1,3 @@ +""" Neousys CAN bus driver """ + +from can.interfaces.neousys.neousys import NeousysBus diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py new file mode 100644 index 000000000..a0037981b --- /dev/null +++ b/can/interfaces/neousys/neousys.py @@ -0,0 +1,271 @@ +""" Neousys CAN bus driver """ + +# +# This kind of interface can be found for example on Neousys POC-551VTC +# One needs to have correct drivers and DLL (Share object for Linux) from Neousys +# +# https://www.neousys-tech.com/en/support-service/resources/category/299-poc-551vtc-driver +# +# Beware this is only tested on Linux kernel higher than v5.3. This should be drop in +# with Windows but you have to replace with correct named DLL +# + +# pylint: disable=too-few-public-methods +# pylint: disable=too-many-instance-attributes +# pylint: disable=wrong-import-position +# pylint: disable=method-hidden +# pylint: disable=unused-import + +import warnings +import queue +import logging +import platform +import time + +from ctypes import ( + byref, + CFUNCTYPE, + c_ubyte, + c_uint, + c_ushort, + POINTER, + sizeof, + Structure, +) + +if platform.system() == "Windows": + from ctypes import WinDLL +else: + from ctypes import CDLL +from can import BusABC, Message + + +logger = logging.getLogger(__name__) + + +class NeousysCanSetup(Structure): + """ C CAN Setup struct """ + + _fields_ = [ + ("bitRate", c_uint), + ("recvConfig", c_uint), + ("recvId", c_uint), + ("recvMask", c_uint), + ] + + +class NeousysCanMsg(Structure): + """ C CAN Message struct """ + + _fields_ = [ + ("id", c_uint), + ("flags", c_ushort), + ("extra", c_ubyte), + ("len", c_ubyte), + ("data", c_ubyte * 8), + ] + + +# valid:2~16, sum of the Synchronization, Propagation, and +# Phase Buffer 1 segments, measured in time quanta. +# valid:1~8, the Phase Buffer 2 segment in time quanta. +# valid:1~4, Resynchronization Jump Width in time quanta +# valid:1~1023, CAN_CLK divider used to determine time quanta +class NeousysCanBitClk(Structure): + """ C CAN BIT Clock struct """ + + _fields_ = [ + ("syncPropPhase1Seg", c_ushort), + ("phase2Seg", c_ushort), + ("jumpWidth", c_ushort), + ("quantumPrescaler", c_ushort), + ] + + +NEOUSYS_CAN_MSG_CALLBACK = CFUNCTYPE(None, POINTER(NeousysCanMsg), c_uint) +NEOUSYS_CAN_STATUS_CALLBACK = CFUNCTYPE(None, c_uint) + +NEOUSYS_CAN_MSG_EXTENDED_ID = 0x0004 +NEOUSYS_CAN_MSG_REMOTE_FRAME = 0x0040 +NEOUSYS_CAN_MSG_DATA_NEW = 0x0080 +NEOUSYS_CAN_MSG_DATA_LOST = 0x0100 + +NEOUSYS_CAN_MSG_USE_ID_FILTER = 0x00000008 +NEOUSYS_CAN_MSG_USE_DIR_FILTER = ( + 0x00000010 | NEOUSYS_CAN_MSG_USE_ID_FILTER +) # only accept the direction specified in the message type +NEOUSYS_CAN_MSG_USE_EXT_FILTER = ( + 0x00000020 | NEOUSYS_CAN_MSG_USE_ID_FILTER +) # filters on only extended identifiers + +NEOUSYS_CAN_STATUS_BUS_OFF = 0x00000080 +NEOUSYS_CAN_STATUS_EWARN = ( + 0x00000040 # can controller error level has reached warning level. +) +NEOUSYS_CAN_STATUS_EPASS = ( + 0x00000020 # can controller error level has reached error passive level. +) +NEOUSYS_CAN_STATUS_LEC_STUFF = 0x00000001 # a bit stuffing error has occurred. +NEOUSYS_CAN_STATUS_LEC_FORM = 0x00000002 # a formatting error has occurred. +NEOUSYS_CAN_STATUS_LEC_ACK = 0x00000003 # an acknowledge error has occurred. +NEOUSYS_CAN_STATUS_LEC_BIT1 = ( + 0x00000004 # the bus remained a bit level of 1 for longer than is allowed. +) +NEOUSYS_CAN_STATUS_LEC_BIT0 = ( + 0x00000005 # the bus remained a bit level of 0 for longer than is allowed. +) +NEOUSYS_CAN_STATUS_LEC_CRC = 0x00000006 # a crc error has occurred. +NEOUSYS_CAN_STATUS_LEC_MASK = ( + 0x00000007 # this is the mask for the can last error code (lec). +) + +NEOUSYS_CANLIB = None + +try: + if platform.system() == "Windows": + NEOUSYS_CANLIB = WinDLL("./WDT_DIO.dll") + else: + NEOUSYS_CANLIB = CDLL("libwdt_dio.so") + logger.info("Loaded Neousys WDT_DIO Can driver") +except OSError as error: + logger.info("Cannot Neousys CAN bus dll or share object: %d", format(error)) + # NEOUSYS_CANLIB = None + + +class NeousysBus(BusABC): + """ Neousys CAN bus Class""" + + def __init__(self, channel, device=0, bitrate=500000, **kwargs): + """ + :param channel: channel number + :param device: device number + :param bitrate: bit rate. Renamed to bitrate in next release. + """ + super().__init__(channel, **kwargs) + + if NEOUSYS_CANLIB is not None: + self.channel = channel + + self.device = device + + self.channel_info = "Neousys Can: device {}, channel {}".format( + self.device, self.channel + ) + + self.queue = queue.Queue() + + # Init with accept all and wanted bitrate + self.init_config = NeousysCanSetup( + bitrate, NEOUSYS_CAN_MSG_USE_ID_FILTER, 0, 0 + ) + + self._neousys_recv_cb = NEOUSYS_CAN_MSG_CALLBACK(self._neousys_recv_cb) + self._neousys_status_cb = NEOUSYS_CAN_STATUS_CALLBACK( + self._neousys_status_cb + ) + + if NEOUSYS_CANLIB.CAN_RegisterReceived(0, self._neousys_recv_cb) == 0: + logger.error("Neousys CAN bus Setup receive callback") + + if NEOUSYS_CANLIB.CAN_RegisterStatus(0, self._neousys_status_cb) == 0: + logger.error("Neousys CAN bus Setup status callback") + + if ( + NEOUSYS_CANLIB.CAN_Setup( + channel, byref(self.init_config), sizeof(self.init_config) + ) + == 0 + ): + logger.error("Neousys CAN bus Setup Error") + + if NEOUSYS_CANLIB.CAN_Start(channel) == 0: + logger.error("Neousys CAN bus Start Error") + + def send(self, msg, timeout=None): + """ + :param msg: message to send + :param timeout: timeout is not used here + :return: + """ + + if NEOUSYS_CANLIB is None: + logger.error("Can't send msg as Neousys DLL/SO is not loaded") + else: + tx_msg = NeousysCanMsg( + msg.arbitration_id, 0, 0, msg.dlc, (c_ubyte * 8)(*msg.data) + ) + + if ( + NEOUSYS_CANLIB.CAN_Send(self.channel, byref(tx_msg), sizeof(tx_msg)) + == 0 + ): + logger.error("Neousys Can can't send message") + + def _recv_internal(self, timeout): + msg = None + + if not self.queue.empty(): + msg = self.queue.get() + + return msg, False + + def _neousys_recv_cb(self, msg, sizeof_msg): + """ + :param msg struct CAN_MSG + :param sizeof_msg message number + :return: + """ + remote_frame = False + extended_frame = False + + msg_bytes = bytearray(msg.contents.data) + + if msg.contents.flags & NEOUSYS_CAN_MSG_REMOTE_FRAME: + remote_frame = True + + if msg.contents.flags & NEOUSYS_CAN_MSG_EXTENDED_ID: + extended_frame = True + + if msg.contents.flags & NEOUSYS_CAN_MSG_DATA_LOST: + logger.error("_neousys_recv_cb flag CAN_MSG_DATA_LOST") + + msg = Message( + timestamp=time.time(), + arbitration_id=msg.contents.id, + is_remote_frame=remote_frame, + is_extended_id=extended_frame, + channel=self.channel, + dlc=msg.contents.len, + data=msg_bytes[: msg.contents.len], + ) + + # Reading happens in Callback function and + # with Python-CAN it happens polling + # so cache stuff in array to for poll + if not self.queue.full(): + self.queue.put(msg) + else: + logger.error("Neousys message Queue is full") + + def _neousys_status_cb(self, status): + """ + :param status BUS Status + :return: + """ + logger.info("%s _neousys_status_cb: %d", self.init_config, status) + + def shutdown(self): + if NEOUSYS_CANLIB is not None: + NEOUSYS_CANLIB.CAN_Stop(self.channel) + + def fileno(self): + # Return an invalid file descriptor as not used + return -1 + + @staticmethod + def _detect_available_configs(): + channels = [] + + # There is only one channel + channels.append({"interface": "neousys", "channel": 0}) + return channels diff --git a/test/test_neousys.py b/test/test_neousys.py new file mode 100644 index 000000000..d5a59ed0e --- /dev/null +++ b/test/test_neousys.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# coding: utf-8 + +import ctypes +import os +import pickle +import unittest +from unittest.mock import Mock + +from ctypes import ( + byref, + cast, + POINTER, + sizeof, + c_ubyte, +) + +import pytest + +import can +from can.interfaces.neousys import neousys + + +class TestNeousysBus(unittest.TestCase): + def setUp(self) -> None: + can.interfaces.neousys.neousys.NEOUSYS_CANLIB = Mock() + can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_RegisterReceived = Mock( + return_value=1 + ) + can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_RegisterStatus = Mock( + return_value=1 + ) + can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup = Mock(return_value=1) + can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Start = Mock(return_value=1) + can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Send = Mock(return_value=1) + can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Stop = Mock(return_value=1) + self.bus = can.Bus(channel=0, bustype="neousys", _testing=True) + + def tearDown(self) -> None: + if self.bus: + self.bus.shutdown() + self.bus = None + + def test_bus_creation(self) -> None: + self.assertIsInstance(self.bus, neousys.NeousysBus) + self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Setup.called) + self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Start.called) + self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_RegisterReceived.called) + self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_RegisterStatus.called) + self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Send.not_called) + self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Stop.not_called) + + CAN_Start_args = ( + can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup.call_args[0] + ) + + # sizeof struct should be 16 + self.assertEqual(CAN_Start_args[0], 0) + self.assertEqual(CAN_Start_args[2], 16) + NeousysCanSetup_struct = cast( + CAN_Start_args[1], POINTER(neousys.NeousysCanSetup) + ) + self.assertEqual(NeousysCanSetup_struct.contents.bitRate, 500000) + self.assertEqual( + NeousysCanSetup_struct.contents.recvConfig, + neousys.NEOUSYS_CAN_MSG_USE_ID_FILTER, + ) + + def test_bus_creation_bitrate(self) -> None: + self.bus = can.Bus(channel=0, bustype="neousys", bitrate=200000, _testing=True) + self.assertIsInstance(self.bus, neousys.NeousysBus) + CAN_Start_args = ( + can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup.call_args[0] + ) + + # sizeof struct should be 16 + self.assertEqual(CAN_Start_args[0], 0) + self.assertEqual(CAN_Start_args[2], 16) + NeousysCanSetup_struct = cast( + CAN_Start_args[1], POINTER(neousys.NeousysCanSetup) + ) + self.assertEqual(NeousysCanSetup_struct.contents.bitRate, 200000) + self.assertEqual( + NeousysCanSetup_struct.contents.recvConfig, + neousys.NEOUSYS_CAN_MSG_USE_ID_FILTER, + ) + + def test_receive(self) -> None: + recv_msg = self.bus.recv(timeout=0.05) + self.assertEqual(recv_msg, None) + msg_data = [0x01, 0x02, 0x03, 0x04, 0x05] + NeousysCanMsg_msg = neousys.NeousysCanMsg( + 0x01, 0x00, 0x00, 0x05, (c_ubyte * 8)(*msg_data) + ) + self.bus._neousys_recv_cb(byref(NeousysCanMsg_msg), sizeof(NeousysCanMsg_msg)) + recv_msg = self.bus.recv(timeout=0.05) + self.assertEqual(recv_msg.dlc, 5) + self.assertSequenceEqual(recv_msg.data, msg_data) + + def test_send(self) -> None: + msg = can.Message( + arbitration_id=0x01, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=False + ) + self.bus.send(msg) + self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Send.called) + + def test_shutdown(self) -> None: + self.bus.shutdown() + self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Stop.called) + + +if __name__ == "__main__": + unittest.main() From 7eaed00228c14b17f50a68b202e8dae3df7078dd Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 18 Apr 2021 09:52:20 +1200 Subject: [PATCH 0569/1235] Fixed is_extended_id for canalystii (#1006) Authored-by: gmarescotti --- can/interfaces/canalystii.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index a57260490..e0bdf006e 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -185,6 +185,7 @@ def _recv_internal(self, timeout=None): timestamp=raw_message.TimeStamp if raw_message.TimeFlag else 0.0, arbitration_id=raw_message.ID, is_remote_frame=raw_message.RemoteFlag, + is_extended_id=raw_message.ExternFlag, channel=0, dlc=raw_message.DataLen, data=raw_message.Data, From f14f699f6e6f12d6cf9285991cd7da21ebdfd867 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 18 Apr 2021 12:04:31 +0200 Subject: [PATCH 0570/1235] Update can/interfaces/socketcan/socketcan.py fix typo Co-authored-by: Brian Thorne --- can/interfaces/socketcan/socketcan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 0990d03ca..920109581 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -505,7 +505,7 @@ def bind_socket(sock: socket.socket, channel: str = "can0") -> None: :param sock: The socket to be bound :param channel: - The channel / interface to ind to + The channel / interface to bind to :raises OSError: If the specified interface isn't found. """ From 2476dbab1d69cb95becd813377368f7becb443fc Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 18 Apr 2021 12:04:43 +0200 Subject: [PATCH 0571/1235] Update doc/interfaces/socketcan.rst fix typo Co-authored-by: Brian Thorne --- doc/interfaces/socketcan.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 4a6d093bd..95aa32bd1 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -248,7 +248,7 @@ to ensure usage of SocketCAN Linux API. The most important differences are: - usage of SocketCAN BCM for periodic messages scheduling; - filtering of CAN messages on Linux kernel level; -- usage of nanosecond timings form the kernel. +- usage of nanosecond timings from the kernel. .. autoclass:: can.interfaces.socketcan.SocketcanBus :members: From fd0dbb1e60cbc3975969f812de86b4ed417dbc6a Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 18 Apr 2021 12:09:01 +0200 Subject: [PATCH 0572/1235] Improve code of serial interface (#1010) * Improve serial: Add typing annotations, simplify code, more robust exceptions * Format code with black * add debug exception * Format code with black * fix failing PyPy test * remove problematic typing annotation * better message validation in serial interface * tiny improvemtn in message class --- can/bus.py | 12 +- can/interfaces/serial/serial_can.py | 169 ++++++++++++++++------------ can/message.py | 4 +- test/serial_test.py | 9 +- 4 files changed, 110 insertions(+), 84 deletions(-) diff --git a/can/bus.py b/can/bus.py index 6258e9a82..d385504a9 100644 --- a/can/bus.py +++ b/can/bus.py @@ -151,7 +151,7 @@ def _recv_internal( raise NotImplementedError("Trying to read from a write only bus?") @abstractmethod - def send(self, msg: Message, timeout: Optional[float] = None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: """Transmit a message to the CAN bus. Override this method to enable the transmit path. @@ -317,7 +317,9 @@ def filters(self) -> Optional[can.typechecking.CanFilters]: def filters(self, filters: Optional[can.typechecking.CanFilters]): self.set_filters(filters) - def set_filters(self, filters: Optional[can.typechecking.CanFilters] = None): + def set_filters( + self, filters: Optional[can.typechecking.CanFilters] = None + ) -> None: """Apply filtering to all messages received by this Bus. All messages that match at least one filter are returned. @@ -342,7 +344,7 @@ def set_filters(self, filters: Optional[can.typechecking.CanFilters] = None): self._filters = filters or None self._apply_filters(self._filters) - def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]): + def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: """ Hook for applying the filters to the underlying kernel or hardware if supported/implemented by the interface. @@ -387,10 +389,10 @@ def _matches_filters(self, msg: Message) -> bool: # nothing matched return False - def flush_tx_buffer(self): + def flush_tx_buffer(self) -> None: """Discard every message that may be queued in the output buffer(s).""" - def shutdown(self): + def shutdown(self) -> None: """ Called to carry out any interface specific cleanup required in shutting down a bus. diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 63bca5477..9fe10e3b3 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -3,12 +3,16 @@ "/dev/ttyS1" or "/dev/ttyUSB0" on Linux machines or "COM1" on Windows. The interface is a simple implementation that has been used for recording CAN traces. + +See the interface documentation for the format being used. """ import logging import struct +from typing import Any, List, Tuple, Optional -from can import BusABC, Message +from can import BusABC, Message, CanError +from can.typechecking import AutoDetectedConfig logger = logging.getLogger("can.serial") @@ -22,62 +26,69 @@ serial = None try: - from serial.tools import list_ports + from serial.tools.list_ports import comports as list_comports except ImportError: - list_ports = None + # If unavailable on some platform, just return nothing + def list_comports() -> List[Any]: + return [] class SerialBus(BusABC): """ Enable basic can communication over a serial device. - .. note:: See :meth:`can.interfaces.serial.SerialBus._recv_internal` - for some special semantics. + .. note:: See :meth:`~_recv_internal` for some special semantics. """ def __init__( - self, channel, baudrate=115200, timeout=0.1, rtscts=False, *args, **kwargs - ): + self, + channel: str, + baudrate: int = 115200, + timeout: float = 0.1, + rtscts: bool = False, + *args, + **kwargs, + ) -> None: """ - :param str channel: + :param channel: The serial device to open. For example "/dev/ttyS1" or "/dev/ttyUSB0" on Linux or "COM1" on Windows systems. - :param int baudrate: + :param baudrate: Baud rate of the serial device in bit/s (default 115200). .. warning:: Some serial port implementations don't care about the baudrate. - :param float timeout: + :param timeout: Timeout for the serial device in seconds (default 0.1). - :param bool rtscts: + :param rtscts: turn hardware handshake (RTS/CTS) on and off """ if not channel: raise ValueError("Must specify a serial port.") - self.channel_info = "Serial interface: " + channel - self.ser = serial.serial_for_url( + self.channel_info = f"Serial interface: {channel}" + self._ser = serial.serial_for_url( channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts ) - super().__init__(channel=channel, *args, **kwargs) + super().__init__(channel, *args, **kwargs) - def shutdown(self): + def shutdown(self) -> None: """ Close the serial interface. """ - self.ser.close() + self._ser.close() - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: """ Send a message over the serial device. - :param can.Message msg: + :param msg: Message to send. .. note:: Flags like ``extended_id``, ``is_remote_frame`` and @@ -91,27 +102,33 @@ def send(self, msg, timeout=None): used instead. """ + # Pack timestamp try: timestamp = struct.pack(" Tuple[Optional[Message], bool]: """ Read a message from the serial device. @@ -121,61 +138,63 @@ def _recv_internal(self, timeout): This parameter will be ignored. The timeout value of the channel is used. :returns: - Received message and False (because not filtering as taken place). + Received message and `False` (because no filtering as taken place). .. warning:: Flags like is_extended_id, is_remote_frame and is_error_frame will not be set over this function, the flags in the return message are the default values. - - :rtype: - Tuple[can.Message, Bool] """ try: - # ser.read can return an empty string - # or raise a SerialException - rx_byte = self.ser.read() - except serial.SerialException: - return None, False - - if rx_byte and ord(rx_byte) == 0xAA: - s = bytearray(self.ser.read(4)) - timestamp = (struct.unpack(" 8: + raise CanError("received DLC may not exceed 8 bytes") + + s = self._ser.read(4) + arbitration_id = struct.unpack("= 0x20000000: + raise CanError( + "received arbitration id may not exceed 2^29 (0x20000000)" + ) + + data = self._ser.read(dlc) + + delimiter_byte = ord(self._ser.read()) + if delimiter_byte == 0xBB: + # received message data okay + msg = Message( + # TODO: We are only guessing that they are milliseconds + timestamp=timestamp / 1000, + arbitration_id=arbitration_id, + dlc=dlc, + data=data, + ) + return msg, False + + else: + raise CanError( + f"invalid delimiter byte while reading message: {delimiter_byte}" + ) + + else: + return None, False + + except serial.SerialException as error: + raise CanError("could not read from serial") from error + + def fileno(self) -> int: + if hasattr(self._ser, "fileno"): + return self._ser.fileno() # Return an invalid file descriptor on Windows return -1 @staticmethod - def _detect_available_configs(): - channels = [] - serial_ports = [] - - if list_ports: - serial_ports = list_ports.comports() - - for port in serial_ports: - channels.append({"interface": "serial", "channel": port.device}) - return channels + def _detect_available_configs() -> List[AutoDetectedConfig]: + return [ + {"interface": "serial", "channel": port.device} for port in list_comports() + ] diff --git a/can/message.py b/can/message.py index 8405e5928..9ad9c2118 100644 --- a/can/message.py +++ b/can/message.py @@ -74,7 +74,7 @@ def __init__( 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 + :raises ValueError: if and only if `check` is set to `True` and one or more arguments were invalid """ self.timestamp = timestamp self.arbitration_id = arbitration_id @@ -95,7 +95,7 @@ def __init__( try: self.data = bytearray(data) except TypeError as error: - err = "Couldn't create message from {} ({})".format(data, type(data)) + err = f"Couldn't create message from {data} ({type(data)})" raise TypeError(err) from error if dlc is None: diff --git a/test/serial_test.py b/test/serial_test.py index d0e11f974..29f1ea2b3 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -14,6 +14,11 @@ from can.interfaces.serial.serial_can import SerialBus from .message_helper import ComparingMessagesTestCase +from .config import IS_PYPY + + +# Mentioned in #1010 +TIMEOUT = 0.5 if IS_PYPY else 0.1 # 0.1 is the default set in SerialBus class SerialDummy: @@ -145,7 +150,7 @@ def setUp(self): self.mock_serial.return_value.write = self.serial_dummy.write self.mock_serial.return_value.read = self.serial_dummy.read self.addCleanup(self.patcher.stop) - self.bus = SerialBus("bus") + self.bus = SerialBus("bus", timeout=TIMEOUT) def tearDown(self): self.serial_dummy.reset() @@ -157,7 +162,7 @@ def __init__(self, *args, **kwargs): SimpleSerialTestBase.__init__(self) def setUp(self): - self.bus = SerialBus("loop://") + self.bus = SerialBus("loop://", timeout=TIMEOUT) def tearDown(self): self.bus.shutdown() From 2d8b36704dcb73e872e05feca5196ab6289d0280 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 18 Apr 2021 12:10:23 +0200 Subject: [PATCH 0573/1235] Fix a typo in serial docs --- doc/interfaces/serial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/interfaces/serial.rst b/doc/interfaces/serial.rst index 413d9cfd1..59ffef21a 100644 --- a/doc/interfaces/serial.rst +++ b/doc/interfaces/serial.rst @@ -98,5 +98,5 @@ Examples of serial frames +----------------+---------------------+------+---------------------+--------------+ | Start of frame | Timestamp | DLC | Arbitration ID | End of frame | +================+=====================+======+=====================+==============+ -| 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x00 | 0xBBS | +| 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x00 | 0xBB | +----------------+---------------------+------+---------------------+--------------+ From 448deedb7722e5d091f930ed9addc966d6cb3161 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 19 Apr 2021 10:03:16 +1200 Subject: [PATCH 0574/1235] Add feature request template --- .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From ccc6988394c2a59b494b5407aef771282889a61e Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 19 Apr 2021 21:07:48 +1200 Subject: [PATCH 0575/1235] Update .github/ISSUE_TEMPLATE/feature_request.md Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d6..36014cde5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: Suggest an idea for this project title: '' -labels: '' +labels: 'enhancement' assignees: '' --- From 941d2aaa68db81a1f5c49ee25811bdd99036e792 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:02:41 +0200 Subject: [PATCH 0576/1235] Add more typying (#1012) * simplify test_socketcan.py * type base classes * type ctypesutil.py; the last file in can/*.py not being typed --- .travis.yml | 17 +-------- can/broadcastmanager.py | 34 +++++++++-------- can/bus.py | 56 ++++++++++++++++++---------- can/ctypesutil.py | 82 +++++++++++++++++------------------------ test/test_socketcan.py | 30 ++++++++------- 5 files changed, 106 insertions(+), 113 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9977551a3..785aa2a6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,22 +89,7 @@ jobs: --python-version=3.7 --ignore-missing-imports --no-implicit-optional - can/bit_timing.py - can/broadcastmanager.py - can/bus.py - can/interface.py - can/interfaces/udp_multicast/**.py - can/interfaces/slcan.py - can/interfaces/socketcan/**.py - can/interfaces/virtual.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/*.py can/io/**.py scripts/**.py examples/**.py diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 62fe34988..6dd4f3fd7 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -36,7 +36,7 @@ class CyclicTask: """ @abc.abstractmethod - def stop(self): + def stop(self) -> None: """Cancel this periodic task. :raises can.CanError: @@ -49,7 +49,9 @@ class CyclicSendTaskABC(CyclicTask): Message send task with defined period """ - def __init__(self, messages: Union[Sequence[Message], Message], period: float): + def __init__( + self, messages: Union[Sequence[Message], Message], period: float + ) -> None: """ :param messages: The messages to be sent periodically. @@ -104,7 +106,7 @@ def __init__( messages: Union[Sequence[Message], Message], period: float, duration: Optional[float], - ): + ) -> None: """Message send task with a defined duration and period. :param messages: @@ -122,14 +124,14 @@ class RestartableCyclicTaskABC(CyclicSendTaskABC): """Adds support for restarting a stopped cyclic task""" @abc.abstractmethod - def start(self): + def start(self) -> None: """Restart a stopped periodic task.""" class ModifiableCyclicTaskABC(CyclicSendTaskABC): """Adds support for modifying a periodic message""" - def _check_modified_messages(self, messages: Tuple[Message, ...]): + def _check_modified_messages(self, messages: Tuple[Message, ...]) -> None: """Helper function to perform error checking when modifying the data in the cyclic task. @@ -149,7 +151,7 @@ def _check_modified_messages(self, messages: Tuple[Message, ...]): "from when the task was created" ) - def modify_data(self, messages: Union[Sequence[Message], Message]): + def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: """Update the contents of the periodically sent messages, without altering the timing. @@ -178,7 +180,7 @@ def __init__( count: int, initial_period: float, subsequent_period: float, - ): + ) -> None: """ Transmits a message `count` times at `initial_period` then continues to transmit messages at `subsequent_period`. @@ -206,12 +208,12 @@ def __init__( period: float, duration: Optional[float] = None, on_error: Optional[Callable[[Exception], bool]] = None, - ): + ) -> None: """Transmits `messages` with a `period` seconds for `duration` seconds on a `bus`. The `on_error` is called if any error happens on `bus` while sending `messages`. If `on_error` present, and returns ``False`` when invoked, thread is - stopped immediately, otherwise, thread continuiously tries to send `messages` + stopped immediately, otherwise, thread continuously tries to send `messages` ignoring errors on a `bus`. Absence of `on_error` means that thread exits immediately on error. @@ -224,22 +226,24 @@ def __init__( self.bus = bus self.send_lock = lock self.stopped = True - self.thread = None - self.end_time = time.perf_counter() + duration if duration else None + self.thread: Optional[threading.Thread] = None + self.end_time: Optional[float] = ( + time.perf_counter() + duration if duration else None + ) self.on_error = on_error if HAS_EVENTS: - self.period_ms: int = int(round(period * 1000, 0)) + self.period_ms = int(round(period * 1000, 0)) self.event = win32event.CreateWaitableTimer(None, False, None) self.start() - def stop(self): + def stop(self) -> None: if HAS_EVENTS: win32event.CancelWaitableTimer(self.event.handle) self.stopped = True - def start(self): + def start(self) -> None: self.stopped = False if self.thread is None or not self.thread.is_alive(): name = "Cyclic send task for 0x%X" % (self.messages[0].arbitration_id) @@ -253,7 +257,7 @@ def start(self): self.thread.start() - def _run(self): + def _run(self) -> None: msg_index = 0 while not self.stopped: # Prevent calling bus.send from multiple threads diff --git a/can/bus.py b/can/bus.py index d385504a9..246f45d19 100644 --- a/can/bus.py +++ b/can/bus.py @@ -6,14 +6,14 @@ import can.typechecking -from abc import ABCMeta, abstractmethod +from abc import ABC, ABCMeta, abstractmethod import can import logging import threading from time import time from enum import Enum, auto -from can.broadcastmanager import ThreadBasedCyclicSendTask +from can.broadcastmanager import ThreadBasedCyclicSendTask, CyclicSendTaskABC from can.message import Message LOG = logging.getLogger(__name__) @@ -61,7 +61,7 @@ def __init__( :param dict kwargs: Any backend dependent configurations are passed in this dictionary """ - self._periodic_tasks: List[can.broadcastmanager.CyclicSendTaskABC] = [] + self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) def __str__(self) -> str: @@ -172,7 +172,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: def send_periodic( self, - msgs: Union[Sequence[Message], Message], + msgs: Union[Message, Sequence[Message]], period: float, duration: Optional[float] = None, store_task: bool = True, @@ -188,7 +188,7 @@ def send_periodic( - the task's :meth:`CyclicTask.stop()` method is called. :param msgs: - Messages to transmit + Message(s) to transmit :param period: Period in seconds between each message :param duration: @@ -215,26 +215,35 @@ def send_periodic( appropriate as the stopped tasks are still taking up memory as they are associated with the Bus instance. """ - if not isinstance(msgs, (list, tuple)): - if isinstance(msgs, Message): - msgs = [msgs] - else: - raise ValueError("Must be either a list, tuple, or a Message") - if not msgs: - raise ValueError("Must be at least a list or tuple of length 1") - task = self._send_periodic_internal(msgs, period, duration) + if isinstance(msgs, Message): + msgs = [msgs] + elif isinstance(msgs, Sequence): + # A Sequence does not necessarily provide __bool__ we need to use len() + if len(msgs) == 0: + raise ValueError("Must be a sequence at least of length 1") + else: + raise ValueError("Must be either a message or a sequence of messages") + + # Create a backend specific task; will be patched to a _SelfRemovingCyclicTask later + task = cast( + _SelfRemovingCyclicTask, + self._send_periodic_internal(msgs, period, duration), + ) + # we wrap the task's stop method to also remove it from the Bus's list of tasks + periodic_tasks = self._periodic_tasks original_stop_method = task.stop - def wrapped_stop_method(remove_task=True): + def wrapped_stop_method(remove_task: bool = True) -> None: + nonlocal task, periodic_tasks, original_stop_method if remove_task: try: - self._periodic_tasks.remove(task) + periodic_tasks.remove(task) except ValueError: - pass + pass # allow the task to be already removed original_stop_method() - setattr(task, "stop", wrapped_stop_method) + task.stop = wrapped_stop_method # type: ignore if store_task: self._periodic_tasks.append(task) @@ -273,13 +282,13 @@ def _send_periodic_internal( ) return task - def stop_all_periodic_tasks(self, remove_tasks=True): + def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None: """Stop sending any messages that were started using **bus.send_periodic**. .. note:: The result is undefined if a single task throws an exception while being stopped. - :param bool remove_tasks: + :param remove_tasks: Stop tracking the stopped tasks. """ for task in self._periodic_tasks: @@ -288,7 +297,7 @@ def stop_all_periodic_tasks(self, remove_tasks=True): task.stop(remove_task=False) if remove_tasks: - self._periodic_tasks = [] + self._periodic_tasks.clear() def __iter__(self) -> Iterator[Message]: """Allow iteration on messages as they are received. @@ -434,3 +443,10 @@ def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: def fileno(self) -> int: raise NotImplementedError("fileno is not implemented using current CAN bus") + + +class _SelfRemovingCyclicTask(CyclicSendTaskABC, ABC): + """Removes itself from a bus. Only needed for typing :meth:`Bus._periodic_tasks`. Do not instantiate.""" + + def stop(self, remove_task: bool = True) -> None: + raise NotImplementedError() diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 13668d1e6..1063130d3 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -6,23 +6,40 @@ import logging import sys +from typing import Any, Callable, Optional, Tuple, Union + log = logging.getLogger("can.ctypesutil") __all__ = ["CLibrary", "HANDLE", "PHANDLE", "HRESULT"] + try: - _LibBase = ctypes.WinDLL + _LibBase = ctypes.WinDLL # type: ignore + _FUNCTION_TYPE = ctypes.WINFUNCTYPE # type: ignore except AttributeError: _LibBase = ctypes.CDLL + _FUNCTION_TYPE = ctypes.CFUNCTYPE -class LibraryMixin: - def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): +class CLibrary(_LibBase): # type: ignore + def __init__(self, library_or_path: Union[str, ctypes.CDLL]) -> None: + if isinstance(library_or_path, str): + super().__init__(library_or_path) + else: + super().__init__(library_or_path._name, library_or_path._handle) + + def map_symbol( + self, + func_name: str, + restype: Any = None, + argtypes: Tuple[Any, ...] = (), + errcheck: Optional[Callable[..., Any]] = None, + ) -> Any: """ Map and return a symbol (function) from a C library. A reference to the mapped symbol is also held in the instance - :param str func_name: + :param func_name: symbol_name :param ctypes.c_* restype: function result type (i.e. ctypes.c_ulong...), defaults to void @@ -32,67 +49,36 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): optional error checking function, see ctypes docs for _FuncPtr """ if argtypes: - prototype = self.function_type(restype, *argtypes) + prototype = _FUNCTION_TYPE(restype, *argtypes) else: - prototype = self.function_type(restype) + prototype = _FUNCTION_TYPE(restype) try: - symbol = prototype((func_name, self)) + symbol: Any = prototype((func_name, self)) except AttributeError: raise ImportError( - "Could not map function '{}' from library {}".format( - func_name, self._name - ) + f'Could not map function "{func_name}" from library {self._name}' ) from None - setattr(symbol, "_name", func_name) + symbol._name = func_name log.debug( f'Wrapped function "{func_name}", result type: {type(restype)}, error_check {errcheck}' ) - if errcheck: + if errcheck is not None: symbol.errcheck = errcheck - setattr(self, func_name, symbol) - return symbol - - -class CLibrary_Win32(_LibBase, LibraryMixin): - " Basic ctypes.WinDLL derived class + LibraryMixin " - - def __init__(self, library_or_path): - if isinstance(library_or_path, str): - super().__init__(library_or_path) - else: - super().__init__(library_or_path._name, library_or_path._handle) - - @property - def function_type(self): - return ctypes.WINFUNCTYPE - + self.func_name = symbol -class CLibrary_Unix(ctypes.CDLL, LibraryMixin): - " Basic ctypes.CDLL derived class + LibraryMixin " - - def __init__(self, library_or_path): - if isinstance(library_or_path, str): - super().__init__(library_or_path) - else: - super().__init__(library_or_path._name, library_or_path._handle) - - @property - def function_type(self): - return ctypes.CFUNCTYPE + return symbol if sys.platform == "win32": - CLibrary = CLibrary_Win32 HRESULT = ctypes.HRESULT -else: - CLibrary = CLibrary_Unix - if sys.platform == "cygwin": - # Define HRESULT for cygwin - class HRESULT(ctypes.c_long): - pass + +elif sys.platform == "cygwin": + + class HRESULT(ctypes.c_long): + pass # Common win32 definitions diff --git a/test/test_socketcan.py b/test/test_socketcan.py index d4077e36f..c322bbf75 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -245,12 +245,13 @@ def side_effect_ctypes_alignment(value): "Should only run on platforms where sizeof(long) == 4 and alignof(long) == 4", ) def test_build_bcm_header_sizeof_long_4_alignof_long_4(self): - expected_result = b"" - expected_result += b"\x02\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x00\x00\x00\x00\x01\x04\x00\x00" - expected_result += b"\x01\x00\x00\x00\x00\x00\x00\x00" + expected_result = ( + b"\x02\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x01\x04\x00\x00" + b"\x01\x00\x00\x00\x00\x00\x00\x00" + ) self.assertEqual( expected_result, @@ -274,14 +275,15 @@ def test_build_bcm_header_sizeof_long_4_alignof_long_4(self): "Should only run on platforms where sizeof(long) == 8 and alignof(long) == 8", ) def test_build_bcm_header_sizeof_long_8_alignof_long_8(self): - expected_result = b"" - expected_result += b"\x02\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x00\x00\x00\x00\x00\x00\x00\x00" - expected_result += b"\x01\x04\x00\x00\x01\x00\x00\x00" + expected_result = ( + b"\x02\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x01\x04\x00\x00\x01\x00\x00\x00" + ) self.assertEqual( expected_result, From 2739d1ee34a08bcfdaa8add769d8556fd8d87a7a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 20 Apr 2021 11:02:58 +1200 Subject: [PATCH 0577/1235] Mergify: configuration update (#1019) --- .mergify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mergify.yml b/.mergify.yml index e837cced6..d80fc8c2b 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -5,7 +5,7 @@ pull_request_rules: - "#review-requested=0" - "#changes-requested-reviews-by=0" - "#commented-reviews-by=0" - - "status-success=continuous-integration/travis-ci/pr" + - "status-success=Travis CI - Pull Request" - "label!=work-in-progress" actions: merge: From d5454e02c5c904c2ae346b85ad0f59e0874c8149 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 21 Apr 2021 22:11:34 +0200 Subject: [PATCH 0578/1235] refactor util.py --- can/util.py | 63 +++++++++++++++++++++-------------------------- test/test_util.py | 6 ++--- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/can/util.py b/can/util.py index da36d0df2..718ca203a 100644 --- a/can/util.py +++ b/can/util.py @@ -1,13 +1,11 @@ """ Utilities and configuration file parsing. """ + import functools import warnings -from typing import Dict, Optional, Union +from typing import Any, Dict, Tuple, Optional from time import time, perf_counter, get_clock_info - -from can import typechecking - import json import os import os.path @@ -18,6 +16,7 @@ import can from can.interfaces import VALID_INTERFACES +from can import typechecking log = logging.getLogger("can.util") @@ -89,9 +88,8 @@ def load_environment_config(context: Optional[str] = None) -> Dict[str, str]: "bitrate": "CAN_BITRATE", } - context_suffix = "_{}".format(context) if context else "" - - can_config_key = "CAN_CONFIG" + context_suffix + context_suffix = f"_{context}" if context else "" + can_config_key = f"CAN_CONFIG{context_suffix}" config: Dict[str, str] = json.loads(os.environ.get(can_config_key, "{}")) for key, val in mapper.items(): @@ -104,7 +102,7 @@ def load_environment_config(context: Optional[str] = None) -> Dict[str, str]: def load_config( path: Optional[typechecking.AcceptedIOType] = None, - config=None, + config: Optional[Dict[str, Any]] = None, context: Optional[str] = None, ) -> typechecking.BusConfig: """ @@ -189,9 +187,7 @@ def load_config( config[key] = None if config["interface"] not in VALID_INTERFACES: - raise NotImplementedError( - "Invalid CAN Bus Type - {}".format(config["interface"]) - ) + raise NotImplementedError(f'Invalid CAN Bus Type "{config["interface"]}"') if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) @@ -216,30 +212,32 @@ def load_config( timing_conf[key] = int(config[key], base=0) del config[key] if timing_conf: - timing_conf["bitrate"] = config.get("bitrate") + timing_conf["bitrate"] = config["bitrate"] config["timing"] = can.BitTiming(**timing_conf) - can.log.debug("can config: {}".format(config)) + can.log.debug("can config: %s", config) return config -def set_logging_level(level_name: Optional[str] = None): - """Set the logging level for the "can" logger. - Expects one of: 'critical', 'error', 'warning', 'info', 'debug', 'subdebug' +def set_logging_level(level_name: str) -> None: + """Set the logging level for the `"can"` logger. + + :param level_name: One of: `'critical'`, `'error'`, `'warning'`, `'info'`, + `'debug'`, `'subdebug'`, or the value `None` (=default). Defaults to `'debug'`. """ can_logger = logging.getLogger("can") try: - can_logger.setLevel(getattr(logging, level_name.upper())) # type: ignore + can_logger.setLevel(getattr(logging, level_name.upper())) except AttributeError: can_logger.setLevel(logging.DEBUG) - log.debug("Logging set to {}".format(level_name)) + log.debug("Logging set to %s", level_name) def len2dlc(length: int) -> int: """Calculate the DLC from data length. - :param int length: Length in number of bytes (0-64) + :param length: Length in number of bytes (0-64) :returns: DLC (0-15) """ @@ -261,20 +259,17 @@ def dlc2len(dlc: int) -> int: return CAN_FD_DLC[dlc] if dlc <= 15 else 64 -def channel2int(channel: Optional[Union[typechecking.Channel]]) -> Optional[int]: +def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]: """Try to convert the channel to an integer. :param channel: - Channel string (e.g. can0, CAN1) or integer + Channel string (e.g. `"can0"`, `"CAN1"`) or an integer - :returns: Channel integer or `None` if unsuccessful + :returns: Channel integer or ``None`` if unsuccessful """ - if channel is None: - return None if isinstance(channel, int): return channel - # String and byte objects have a lower() method - if hasattr(channel, "lower"): + if isinstance(channel, str): match = re.match(r".*(\d+)$", channel) if match: return int(match.group(1)) @@ -299,7 +294,7 @@ def library_function(new_arg): def deco(f): @functools.wraps(f) def wrapper(*args, **kwargs): - rename_kwargs(f.__name__, kwargs, aliases) + _rename_kwargs(f.__name__, kwargs, aliases) return f(*args, **kwargs) return wrapper @@ -307,27 +302,25 @@ def wrapper(*args, **kwargs): return deco -def rename_kwargs(func_name, kwargs, aliases): +def _rename_kwargs( + func_name: str, kwargs: Dict[str, str], aliases: Dict[str, str] +) -> None: """Helper function for `deprecated_args_alias`""" for alias, new in aliases.items(): if alias in kwargs: value = kwargs.pop(alias) if new is not None: - warnings.warn( - "{} is deprecated; use {}".format(alias, new), DeprecationWarning - ) + warnings.warn(f"{alias} is deprecated; use {new}", DeprecationWarning) if new in kwargs: raise TypeError( - "{} received both {} (deprecated) and {}".format( - func_name, alias, new - ) + f"{func_name} received both {alias} (deprecated) and {new}" ) kwargs[new] = value else: warnings.warn("{} is deprecated".format(alias), DeprecationWarning) -def time_perfcounter_correlation(): +def time_perfcounter_correlation() -> Tuple[float, float]: """Get the `perf_counter` value nearest to when time.time() is updated Computed if the default timer used by `time.time` on this platform has a resolution diff --git a/test/test_util.py b/test/test_util.py index 3dcde04fd..bbfb9d580 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -1,7 +1,7 @@ import unittest import warnings -from can.util import rename_kwargs +from can.util import _rename_kwargs class RenameKwargsTest(unittest.TestCase): @@ -11,7 +11,7 @@ def _test(self, kwargs, aliases): # Test that we do get the DeprecationWarning when called with deprecated kwargs with self.assertWarnsRegex(DeprecationWarning, "is deprecated"): - rename_kwargs("unit_test", kwargs, aliases) + _rename_kwargs("unit_test", kwargs, aliases) # Test that the aliases contains the deprecated values and # the obsolete kwargs have been removed @@ -23,7 +23,7 @@ def _test(self, kwargs, aliases): # Cause all warnings to always be triggered. warnings.simplefilter("error", DeprecationWarning) try: - rename_kwargs("unit_test", kwargs, aliases) + _rename_kwargs("unit_test", kwargs, aliases) finally: warnings.resetwarnings() From d546609872b3220c8bb2938ec267d44be2f4d0fa Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 22 Apr 2021 08:25:28 +1200 Subject: [PATCH 0579/1235] Add Bug report issue template (#1017) --- .github/ISSUE_TEMPLATE/bug_report.md | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..2f73865bb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior (please add longer code examples below): + + +**Expected behavior** +A clear and concise description of what you expected to happen. + + +**Additional context** + +OS-version: +Python version: +python-can version: +python-can interface/s: + + + +
Traceback and logs + + + + + +```python +def func(): + return 'hello, world!' +``` +
From 53b9a5adc3665b118d2777668b19debf9a5397a6 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 22 Apr 2021 08:33:01 +1200 Subject: [PATCH 0580/1235] Mergify: configuration update (#1024) --- .mergify.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.mergify.yml b/.mergify.yml index d80fc8c2b..9a6141e6d 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -4,7 +4,6 @@ pull_request_rules: - "#approved-reviews-by>=1" - "#review-requested=0" - "#changes-requested-reviews-by=0" - - "#commented-reviews-by=0" - "status-success=Travis CI - Pull Request" - "label!=work-in-progress" actions: From be08bc60d20a7d8f9afa3c904c553f3e11abaed9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 21 Apr 2021 22:51:47 +0200 Subject: [PATCH 0581/1235] fix typing --- can/typechecking.py | 2 +- can/util.py | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/can/typechecking.py b/can/typechecking.py index 4c81f0ea7..d29f1ccaf 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -31,7 +31,7 @@ StringPathLike = typing.Union[str, "os.PathLike[str]"] AcceptedIOType = typing.Optional[typing.Union[FileLike, StringPathLike]] -BusConfig = typing.NewType("BusConfig", dict) +BusConfig = typing.NewType("BusConfig", typing.Dict[str, typing.Any]) AutoDetectedConfig = mypy_extensions.TypedDict( "AutoDetectedConfig", {"interface": str, "channel": Channel} diff --git a/can/util.py b/can/util.py index 718ca203a..723b43e7b 100644 --- a/can/util.py +++ b/can/util.py @@ -4,7 +4,7 @@ import functools import warnings -from typing import Any, Dict, Tuple, Optional +from typing import Any, Callable, cast, Dict, Iterable, List, Tuple, Optional, Union from time import time, perf_counter, get_clock_info import json import os @@ -156,16 +156,19 @@ def load_config( config = {} # use the given dict for default values - config_sources = [ - given_config, - can.rc, - lambda _context: load_environment_config( # pylint: disable=unnecessary-lambda - _context - ), - lambda _context: load_environment_config(), - lambda _context: load_file_config(path, _context), - lambda _context: load_file_config(path), - ] + config_sources = cast( + Iterable[Union[Dict[str, Any], Callable[[Any], Dict[str, Any]]]], + [ + given_config, + can.rc, + lambda _context: load_environment_config( # pylint: disable=unnecessary-lambda + _context + ), + lambda _context: load_environment_config(), + lambda _context: load_file_config(path, _context), + lambda _context: load_file_config(path), + ], + ) # Slightly complex here to only search for the file config if required for cfg in config_sources: @@ -216,7 +219,8 @@ def load_config( config["timing"] = can.BitTiming(**timing_conf) can.log.debug("can config: %s", config) - return config + + return cast(typechecking.BusConfig, config) def set_logging_level(level_name: str) -> None: From 604a24af43602c2fc8f6f4b707e92581844b194e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 21 Apr 2021 23:03:00 +0200 Subject: [PATCH 0582/1235] remove unused import --- can/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/util.py b/can/util.py index 723b43e7b..07fa1986a 100644 --- a/can/util.py +++ b/can/util.py @@ -4,7 +4,7 @@ import functools import warnings -from typing import Any, Callable, cast, Dict, Iterable, List, Tuple, Optional, Union +from typing import Any, Callable, cast, Dict, Iterable, Tuple, Optional, Union from time import time, perf_counter, get_clock_info import json import os From 21f3ae7ac1e7408abcac64186984d03434be141b Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 22 Apr 2021 13:53:52 +0000 Subject: [PATCH 0583/1235] Format code with black --- can/__init__.py | 8 +++++- can/exceptions.py | 43 ++++++++++++++++-------------- can/interfaces/ixxat/canlib.py | 20 +++++++------- can/interfaces/ixxat/exceptions.py | 9 ++++++- test/test_interface_ixxat.py | 22 ++++++++------- 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index a1996d39e..2ce879fa8 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -16,7 +16,13 @@ from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader -from .exceptions import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError +from .exceptions import ( + CanError, + CanBackEndError, + CanInitializationError, + CanOperationError, + CanTimeoutError, +) from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync from .io import ASCWriter, ASCReader diff --git a/can/exceptions.py b/can/exceptions.py index a91fc7a1b..e7a301d59 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -2,36 +2,39 @@ Exception classes. """ + class CanError(Exception): - """ Base class for all can related exceptions. - """ - pass + """Base class for all can related exceptions.""" + + pass class CanBackEndError(CanError): - """ Indicates an error related to the backend (e.g. driver/OS/library) - Examples: - - A call to a library function results in an unexpected return value - """ - pass + """Indicates an error related to the backend (e.g. driver/OS/library) + Examples: + - A call to a library function results in an unexpected return value + """ + + pass class CanInitializationError(CanError): - """ Indicates an error related to the initialization. - Examples for situations when this exception may occur: - - Try to open a non-existent device and/or channel - - Try to use an invalid setting, which is ok by value, but not ok for the interface - """ - pass + """Indicates an error related to the initialization. + Examples for situations when this exception may occur: + - Try to open a non-existent device and/or channel + - Try to use an invalid setting, which is ok by value, but not ok for the interface + """ + + pass class CanOperationError(CanError): - """ Indicates an error while in operation. - """ - pass + """Indicates an error while in operation.""" + + pass class CanTimeoutError(CanError): - """ Indicates a timeout of an operation. - """ - pass + """Indicates a timeout of an operation.""" + + pass diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 2f37d3a71..5523ad2cb 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -114,7 +114,7 @@ def __check_status(result, function, arguments): # Real return value is an unsigned long result = ctypes.c_ulong(result).value - #print(hex(result), function) + # print(hex(result), function) if result == constants.VCI_E_TIMEOUT: raise VCITimeout("Function {} timed out".format(function._name)) @@ -443,13 +443,13 @@ def __init__(self, channel, can_filters=None, **kwargs): if bitrate not in self.CHANNEL_BITRATES[0]: raise ValueError("Invalid bitrate {}".format(bitrate)) - + if rxFifoSize <= 0: raise ValueError("rxFifoSize must be > 0") - + if txFifoSize <= 0: raise ValueError("txFifoSize must be > 0") - + if channel < 0: raise ValueError("channel number must be >= 0") @@ -514,11 +514,11 @@ def __init__(self, channel, can_filters=None, **kwargs): try: _canlib.canChannelOpen( - self._device_handle, - channel, - constants.FALSE, - ctypes.byref(self._channel_handle), - ) + self._device_handle, + channel, + constants.FALSE, + ctypes.byref(self._channel_handle), + ) except: raise CanInitializationError("Could not open and initialize channel.") @@ -710,7 +710,7 @@ def _recv_internal(self, timeout): def send(self, msg, timeout=None): """ Sends a message on the bus. The interface may buffer the message. - + :param can.Message msg: The message to send. :param float timeout: diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index f6e66b385..21e00a94f 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -5,7 +5,13 @@ Copyright (C) 2019 Marcel Kanter """ -from can import CanError, CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError +from can import ( + CanError, + CanBackEndError, + CanInitializationError, + CanOperationError, + CanTimeoutError, +) __all__ = [ "VCITimeout", @@ -15,6 +21,7 @@ "VCIDeviceNotFoundError", ] + class VCITimeout(CanTimeoutError): """ Wraps the VCI_E_TIMEOUT error """ diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 44a1b61bb..f9bb135df 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -13,25 +13,26 @@ class SoftwareTestCase(unittest.TestCase): """ Test cases that test the software only and do not rely on an existing/connected hardware. """ + def setUp(self): try: bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: - raise(unittest.SkipTest()) - + raise (unittest.SkipTest()) + def tearDown(self): pass - + def test_bus_creation(self): # channel must be >= 0 with self.assertRaises(ValueError): bus = can.Bus(interface="ixxat", channel=-1) - + # rxFifoSize must be > 0 with self.assertRaises(ValueError): bus = can.Bus(interface="ixxat", channel=0, rxFifoSize=0) - + # txFifoSize must be > 0 with self.assertRaises(ValueError): bus = can.Bus(interface="ixxat", channel=0, txFifoSize=0) @@ -41,21 +42,22 @@ class HardwareTestCase(unittest.TestCase): """ Test cases that rely on an existing/connected hardware. """ + def setUp(self): try: bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: - raise(unittest.SkipTest()) - + raise (unittest.SkipTest()) + def tearDown(self): pass - + def test_bus_creation(self): # non-existent channel -> use arbitrary high value with self.assertRaises(can.CanInitializationError): bus = can.Bus(interface="ixxat", channel=0xFFFF) - + def test_send_after_shutdown(self): bus = can.Bus(interface="ixxat", channel=0) msg = can.Message(arbitration_id=0x3FF, dlc=0) @@ -64,5 +66,5 @@ def test_send_after_shutdown(self): bus.send(msg) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() From cd06b779effbdb39b4628e1b901d05d7419f135c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 16:32:51 +0200 Subject: [PATCH 0584/1235] clean up --- can/exceptions.py | 22 +++++++++++----------- doc/api.rst | 8 ++------ doc/doc-requirements.txt | 2 +- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index e7a301d59..c8f670e17 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -1,13 +1,21 @@ """ -Exception classes. +There are several specific :class:`Exception` classes to allow user +code to react to specific scenarios related to CAN busses:: + + Exception (Python standard library) + +-- ... + +-- CanError (python-can) + +-- CanBackendError + +-- CanInitializationError + +-- CanOperationError + +-- CanTimeoutError + """ class CanError(Exception): """Base class for all can related exceptions.""" - pass - class CanBackEndError(CanError): """Indicates an error related to the backend (e.g. driver/OS/library) @@ -15,8 +23,6 @@ class CanBackEndError(CanError): - A call to a library function results in an unexpected return value """ - pass - class CanInitializationError(CanError): """Indicates an error related to the initialization. @@ -25,16 +31,10 @@ class CanInitializationError(CanError): - Try to use an invalid setting, which is ok by value, but not ok for the interface """ - pass - class CanOperationError(CanError): """Indicates an error while in operation.""" - pass - class CanTimeoutError(CanError): """Indicates a timeout of an operation.""" - - pass diff --git a/doc/api.rst b/doc/api.rst index 551d6c53e..8674334c4 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -41,10 +41,6 @@ The Notifier object is used as a message distributor for a bus. Notifier creates Errors ------ -| CanError -| CanBackendError -| CanInitializationError -| CanOperationError -| CanTimeoutError - .. automodule:: can.exceptions + :members: + :show-inheritance: diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt index a63beee71..dead5e2e5 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,3 +1,3 @@ sphinx>=1.8.1 sphinxcontrib-programoutput -sphinx-autodoc-typehints==1.6.0 +sphinx-autodoc-typehints From 251b039bfb7f18c22220dcb52502facfc2da33f3 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 16:35:23 +0200 Subject: [PATCH 0585/1235] typos and formatting --- can/interfaces/nican.py | 2 +- doc/interfaces/ixxat.rst | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index cc22c4f12..fc75ba14a 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -8,7 +8,7 @@ TODO: We could implement this interface such that setting other filters could work when the initial filters were set to zero using the software fallback. Or could the software filters even be changed - after the connection was opened? We need to document that bahaviour! + after the connection was opened? We need to document that behaviour! See also the IXXAT interface. """ diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 9ff463a0d..929221733 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -54,12 +54,13 @@ bit 0 of can_id/mask parameters represents the RTR field in CAN frame. See IXXAT VCI documentation, section "Message filters" for more info. List available devices ------------------ +---------------------- In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id. To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids()`` as demonstrated below: >>> from can.interfaces.ixxat import get_ixxat_hwids - >>> for hwid in get_ixxat_hwids(): print("Found IXXAT with hardware id '%s'." % hwid) + >>> for hwid in get_ixxat_hwids(): + ... print("Found IXXAT with hardware id '%s'." % hwid) Found IXXAT with hardware id 'HW441489'. Found IXXAT with hardware id 'HW107422'. @@ -71,11 +72,11 @@ The IXXAT :class:`~can.BusABC` object is a fairly straightforward interface to the IXXAT VCI library. It can open a specific device ID or use the first one found. -The frame exchange *do not involve threads* in the background but is +The frame exchange *does not involve threads* in the background but is explicitly instantiated by the caller. - ``recv()`` is a blocking call with optional timeout. - ``send()`` is not blocking but may raise a VCIError if the TX FIFO is full RX and TX FIFO sizes are configurable with ``rxFifoSize`` and ``txFifoSize`` -options, defaulting at 16 for both. +options, defaulting to 16 for both. From 89d09ff4333068616a60c7af92e6b3e57d81be43 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 16:50:56 +0200 Subject: [PATCH 0586/1235] cleanup old unwanted changes --- can/interfaces/ixxat/canlib.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 5523ad2cb..b7e9f25f1 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -13,6 +13,7 @@ import functools import logging import sys +from typing import Optional from can import BusABC, Message from can.exceptions import * @@ -114,8 +115,6 @@ def __check_status(result, function, arguments): # Real return value is an unsigned long result = ctypes.c_ulong(result).value - # print(hex(result), function) - if result == constants.VCI_E_TIMEOUT: raise VCITimeout("Function {} timed out".format(function._name)) elif result == constants.VCI_E_RXQUEUE_EMPTY: @@ -459,6 +458,7 @@ def __init__(self, channel, can_filters=None, **kwargs): self._channel_handle = HANDLE() self._channel_capabilities = structures.CANCAPABILITIES() self._message = structures.CANMSG() + self._payload = (ctypes.c_byte * 8)() # Search for supplied device if UniqueHardwareId is None: @@ -707,13 +707,13 @@ def _recv_internal(self, timeout): return rx_msg, True - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: """ Sends a message on the bus. The interface may buffer the message. - :param can.Message msg: + :param msg: The message to send. - :param float timeout: + :param timeout: Timeout after some time. :raise: :class:CanTimeoutError From bfbb0f1962207f0d890a6a250794c094078fa7c9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 16:59:57 +0200 Subject: [PATCH 0587/1235] improve docs --- can/exceptions.py | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index c8f670e17..cedad3ee8 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -10,31 +10,51 @@ +-- CanOperationError +-- CanTimeoutError +Keep in mind that some functions and methods may raise different exceptions. +For example, validating typical arguments and parameters might result in a +:class:`ValueError`. This should be documented for the function at hand. """ class CanError(Exception): - """Base class for all can related exceptions.""" + """Base class for all CAN related exceptions.""" class CanBackEndError(CanError): - """Indicates an error related to the backend (e.g. driver/OS/library) - Examples: - - A call to a library function results in an unexpected return value + """Indicates an error related to the backend (e.g. driver/OS/library). + + Example scenarios: + - The driver is not present or has the wrong version + - The interface is unsupported on the current platform """ class CanInitializationError(CanError): - """Indicates an error related to the initialization. - Examples for situations when this exception may occur: - - Try to open a non-existent device and/or channel - - Try to use an invalid setting, which is ok by value, but not ok for the interface + """Indicates an error the occurred while initializing a :class:`can.Bus`. + + Example scenarios: + - Try to open a non-existent device and/or channel + - Try to use an invalid setting, which is ok by value, but not ok for the interface + - The device or other resources are already used """ class CanOperationError(CanError): - """Indicates an error while in operation.""" + """Indicates an error while in operation. + + Example scenarios: + - A call to a library function results in an unexpected return value + - An invalid message was received + - Attempted to send an invalid message + - Cyclic redundancy check (CRC) failed + - Message remained unacknowledged + """ -class CanTimeoutError(CanError): - """Indicates a timeout of an operation.""" +class CanTimeoutError(CanError, TimeoutError): + """Indicates a timeout of an operation. + + Example scenarios: + - Some message could not be sent after the timeout elapsed + - No message was read within the given time + """ From daf66175719b84563ba79a72a2ef9e8520bd246f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 17:13:30 +0200 Subject: [PATCH 0588/1235] finalize ixxat interface --- can/exceptions.py | 2 +- can/interfaces/ixxat/canlib.py | 8 ++++---- can/interfaces/ixxat/exceptions.py | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index cedad3ee8..7bc8aeb29 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -52,7 +52,7 @@ class CanOperationError(CanError): class CanTimeoutError(CanError, TimeoutError): - """Indicates a timeout of an operation. + """Indicates the timeout of an operation. Example scenarios: - Some message could not be sent after the timeout elapsed diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index b7e9f25f1..503a40372 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -500,8 +500,8 @@ def __init__(self, channel, can_filters=None, **kwargs): ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle), ) - except: - raise CanInitializationError("Could not open device.") + except Exception as exception: + raise CanInitializationError(f"Could not open device: {exception}") log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) @@ -519,8 +519,8 @@ def __init__(self, channel, can_filters=None, **kwargs): constants.FALSE, ctypes.byref(self._channel_handle), ) - except: - raise CanInitializationError("Could not open and initialize channel.") + except Exception as exception: + raise CanInitializationError(f"Could not open and initialize channel: {exception}") # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 21e00a94f..3bc0e1111 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -6,8 +6,6 @@ """ from can import ( - CanError, - CanBackEndError, CanInitializationError, CanOperationError, CanTimeoutError, From a92928c4b59178261452c4120249807414a901a7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 15:20:50 +0000 Subject: [PATCH 0589/1235] Format code with black --- can/interfaces/ixxat/canlib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 503a40372..9abef1210 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -520,7 +520,9 @@ def __init__(self, channel, can_filters=None, **kwargs): ctypes.byref(self._channel_handle), ) except Exception as exception: - raise CanInitializationError(f"Could not open and initialize channel: {exception}") + raise CanInitializationError( + f"Could not open and initialize channel: {exception}" + ) # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) From 8d0d20ae1660bf61ebd7e7fe4cf5e68c70346bd7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Apr 2021 18:02:29 +0200 Subject: [PATCH 0590/1235] finalize adding specific exceptions to the generic part of the library --- can/__init__.py | 2 +- can/broadcastmanager.py | 16 +++++++++++++++- can/bus.py | 25 +++++++++++++++---------- can/exceptions.py | 5 +++-- can/interface.py | 16 ++++++++++------ can/listener.py | 6 ++---- can/util.py | 9 +++++---- doc/api.rst | 3 +++ 8 files changed, 54 insertions(+), 28 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 2ce879fa8..d8bf17a5f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.0.0-dev0" +__version__ = "4.0.0-dev" log = logging.getLogger("can") diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 6dd4f3fd7..457467d65 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -56,6 +56,8 @@ def __init__( :param messages: The messages to be sent periodically. :param period: The rate in seconds at which to send the messages. + + :raises ValueError: If the given messages are invalid """ messages = self._check_and_convert_messages(messages) @@ -74,7 +76,9 @@ def _check_and_convert_messages( Performs error checking to ensure that all Messages have the same arbitration ID and channel. - Should be called when the cyclic task is initialized + Should be called when the cyclic task is initialized. + + :raises ValueError: If the given messages are invalid """ if not isinstance(messages, (list, tuple)): if isinstance(messages, Message): @@ -115,6 +119,8 @@ def __init__( :param duration: Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. + + :raises ValueError: If the given messages are invalid """ super().__init__(messages, period) self.duration = duration @@ -139,6 +145,8 @@ def _check_modified_messages(self, messages: Tuple[Message, ...]) -> None: cyclic messages hasn't changed. Should be called when modify_data is called in the cyclic task. + + :raises ValueError: If the given messages are invalid """ if len(self.messages) != len(messages): raise ValueError( @@ -163,6 +171,8 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: Note: The number of new cyclic messages to be sent must be equal to the original number of messages originally specified for this task. + + :raises ValueError: If the given messages are invalid """ messages = self._check_and_convert_messages(messages) self._check_modified_messages(messages) @@ -190,6 +200,8 @@ def __init__( :param count: :param initial_period: :param subsequent_period: + + :raises ValueError: If the given messages are invalid """ super().__init__(messages, subsequent_period) self._channel = channel @@ -221,6 +233,8 @@ def __init__( error happened on a `bus` while sending `messages`, it shall return either ``True`` or ``False`` depending on desired behaviour of `ThreadBasedCyclicSendTask`. + + :raises ValueError: If the given messages are invalid """ super().__init__(messages, period, duration) self.bus = bus diff --git a/can/bus.py b/can/bus.py index 246f45d19..231979407 100644 --- a/can/bus.py +++ b/can/bus.py @@ -31,7 +31,11 @@ class BusABC(metaclass=ABCMeta): """The CAN Bus Abstract Base Class that serves as the basis for all concrete interfaces. - This class may be used as an iterator over the received messages. + This class may be used as an iterator over the received messages + and as a context manager for auto-closing the bus when done using it. + + Please refer to :ref:`errors` for possible exceptions that may be + thrown by certain operations on this bus. """ #: a string describing the underlying bus and/or channel @@ -60,6 +64,10 @@ def __init__( :param dict kwargs: Any backend dependent configurations are passed in this dictionary + + :raises ValueError: If parameters are out of range + :raises can.CanBackEndError: If the driver cannot be accessed + :raises can.CanInitializationError: If the bus cannot be initialized """ self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) @@ -73,10 +81,9 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: :param timeout: seconds to wait for a message or None to wait indefinitely - :return: - None on timeout or a :class:`Message` object. - :raises can.CanError: - if an error occurred while reading + :return: ``None`` on timeout or a :class:`Message` object. + + :raises can.CanOperationError: If an error occurred while reading """ start = time() time_left = timeout @@ -141,8 +148,7 @@ def _recv_internal( 2. a bool that is True if message filtering has already been done and else False - :raises can.CanError: - if an error occurred while reading + :raises can.CanOperationError: If an error occurred while reading :raises NotImplementedError: if the bus provides it's own :meth:`~can.BusABC.recv` implementation (legacy implementation) @@ -165,8 +171,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: Might not be supported by all interfaces. None blocks indefinitely. - :raises can.CanError: - if the message could not be sent + :raises can.CanOperationError: If an error occurred while sending """ raise NotImplementedError("Trying to write to a readonly bus?") @@ -336,7 +341,7 @@ def set_filters( messages are matched. Calling without passing any filters will reset the applied - filters to `None`. + filters to ``None``. :param filters: A iterable of dictionaries each containing a "can_id", diff --git a/can/exceptions.py b/can/exceptions.py index 7bc8aeb29..3a279a449 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -24,13 +24,14 @@ class CanBackEndError(CanError): """Indicates an error related to the backend (e.g. driver/OS/library). Example scenarios: - - The driver is not present or has the wrong version + - The interface does not exist - The interface is unsupported on the current platform + - The driver is not present or has the wrong version """ class CanInitializationError(CanError): - """Indicates an error the occurred while initializing a :class:`can.Bus`. + """Indicates an error the occurred while initializing a :class:`can.BusABC`. Example scenarios: - Try to open a non-existent device and/or channel diff --git a/can/interface.py b/can/interface.py index 2d1ad0891..5a4d7caa7 100644 --- a/can/interface.py +++ b/can/interface.py @@ -10,6 +10,7 @@ from .bus import BusABC from .util import load_config from .interfaces import BACKENDS +from .exceptions import CanBackEndError log = logging.getLogger("can.interface") log_autodetect = log.getChild("detect_available_configs") @@ -22,7 +23,7 @@ def _get_class_for_interface(interface): :raises: NotImplementedError if the interface is not known :raises: - ImportError if there was a problem while importing the + CanBackEndError if there was a problem while importing the interface or the bus class within that """ # Find the correct backend @@ -37,7 +38,7 @@ def _get_class_for_interface(interface): try: module = importlib.import_module(module_name) except Exception as e: - raise ImportError( + raise CanBackEndError( "Cannot import module {} for CAN interface '{}': {}".format( module_name, interface, e ) @@ -47,7 +48,7 @@ def _get_class_for_interface(interface): try: bus_class = getattr(module, class_name) except Exception as e: - raise ImportError( + raise CanBackEndError( "Cannot import class {} from module {} for CAN interface '{}': {}".format( class_name, module_name, interface, e ) @@ -79,8 +80,11 @@ def __new__(cls, channel=None, *args, **kwargs): Should contain an ``interface`` key with a valid interface name. If not, it is completed using :meth:`can.util.load_config`. - :raises: NotImplementedError - if the ``interface`` isn't recognized + :raises: can.CanBackEndError + if the ``interface`` isn't recognized or cannot be loaded + + :raises: can.CanInitializationError + if the bus cannot be instantiated :raises: ValueError if the ``channel`` could not be determined @@ -148,7 +152,7 @@ def detect_available_configs(interfaces=None): try: bus_class = _get_class_for_interface(interface) - except ImportError: + except CanBackEndError: log_autodetect.debug( 'interface "%s" can not be loaded for detection of available configurations', interface, diff --git a/can/listener.py b/can/listener.py index 2695e80c5..2cd643d44 100644 --- a/can/listener.py +++ b/can/listener.py @@ -41,7 +41,6 @@ def on_message_received(self, msg: Message): """This method is called to handle the given message. :param msg: the delivered message - """ def __call__(self, msg: Message): @@ -65,7 +64,6 @@ def stop(self): class RedirectReader(Listener): """ A RedirectReader sends all received messages to another Bus. - """ def __init__(self, bus: BusABC): @@ -86,13 +84,13 @@ class BufferedReader(Listener): Putting in messages after :meth:`~can.BufferedReader.stop` has been called will raise an exception, see :meth:`~can.BufferedReader.on_message_received`. - :attr bool is_stopped: ``True`` if the reader has been stopped + :attr is_stopped: ``True`` if the reader has been stopped """ def __init__(self): # set to "infinite" size self.buffer = SimpleQueue() - self.is_stopped = False + self.is_stopped: bool = False def on_message_received(self, msg: Message): """Append a message to the buffer. diff --git a/can/util.py b/can/util.py index 07fa1986a..56b1ca22a 100644 --- a/can/util.py +++ b/can/util.py @@ -15,8 +15,9 @@ from configparser import ConfigParser import can -from can.interfaces import VALID_INTERFACES -from can import typechecking +from .interfaces import VALID_INTERFACES +from . import typechecking +from .exceptions import CanBackEndError log = logging.getLogger("can.util") @@ -148,7 +149,7 @@ def load_config( All unused values are passed from ``config`` over to this. :raises: - NotImplementedError if the ``interface`` isn't recognized + CanBackEndError if the ``interface`` isn't recognized """ # start with an empty dict to apply filtering to all sources @@ -190,7 +191,7 @@ def load_config( config[key] = None if config["interface"] not in VALID_INTERFACES: - raise NotImplementedError(f'Invalid CAN Bus Type "{config["interface"]}"') + raise CanBackEndError(f'Invalid CAN Bus Type "{config["interface"]}"') if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) diff --git a/doc/api.rst b/doc/api.rst index 8674334c4..011553b1b 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -38,6 +38,9 @@ The Notifier object is used as a message distributor for a bus. Notifier creates .. autoclass:: can.Notifier :members: + +.. _errors: + Errors ------ From 853fbeb42c312fe34f24c854dfe0165c80334d2a Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Wed, 29 Jan 2020 14:21:26 -0600 Subject: [PATCH 0591/1235] adding absolute timestamp to ASC reader --- can/io/asc.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index d646a1924..b5a46bb77 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -31,8 +31,6 @@ class ASCReader(BaseIOHandler): """ Iterator of CAN messages from a ASC logging file. Meta data (comments, bus statistics, J1939 Transport Protocol messages) is ignored. - - TODO: turn relative timestamps back to absolute form """ def __init__( @@ -185,6 +183,13 @@ def __iter__(self) -> Generator[Message, None, None]: for line in self.file: temp = line.strip() + + #check for timestamp + if "begin triggerblock" in temp.lower(): + _, _, start_time = temp.split(None, 2) + start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() + continue + if not temp or not temp[0].isdigit(): # Could be a comment continue From 14c7a689a347024a03bf176f679a64c05053e48a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 18 Jun 2020 23:10:54 +1200 Subject: [PATCH 0592/1235] Error handling in ASC time parsing Co-authored-by: Alexander Bessman --- can/io/asc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index b5a46bb77..657a0ce7c 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -186,7 +186,11 @@ def __iter__(self) -> Generator[Message, None, None]: #check for timestamp if "begin triggerblock" in temp.lower(): - _, _, start_time = temp.split(None, 2) + try: + _, _, start_time = temp.split(None, 2) + start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() + except ValueError: + start_time = 0.0 start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() continue From d86020ee319ad166a9d7a2b7139dda405767cec8 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 18 Jun 2020 23:11:54 +1200 Subject: [PATCH 0593/1235] Update can/io/asc.py Co-authored-by: Alexander Bessman --- can/io/asc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index 657a0ce7c..ea1f7417e 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -191,7 +191,6 @@ def __iter__(self) -> Generator[Message, None, None]: start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() except ValueError: start_time = 0.0 - start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() continue if not temp or not temp[0].isdigit(): From 39541b1a48b27febf1f7ceb19a30d497fc0e38f2 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 12:40:55 -0500 Subject: [PATCH 0594/1235] rebase changes still need to update docs, create test, and add option flag --- can/io/asc.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index ea1f7417e..be773f86f 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -53,6 +53,7 @@ def __init__( self.base = base self._converted_base = self._check_base(base) self.date = None + # TODO - what is this used for? The ASC Writer only prints `absolute` self.timestamps_format = None self.internal_events_logged = None @@ -72,6 +73,14 @@ def _extract_header(self): self.timestamps_format = timestamp_format elif lower_case.endswith("internal events logged"): self.internal_events_logged = not lower_case.startswith("no") + # grab absolute timestamp + elif lower_case.startswith("begin triggerblock"): + try: + _, _, start_time = lower_case.split(None, 2) + start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() + except ValueError: + start_time = 0.0 + self.start_time = start_time # Currently the last line in the header which is parsed break else: @@ -183,23 +192,13 @@ def __iter__(self) -> Generator[Message, None, None]: for line in self.file: temp = line.strip() - - #check for timestamp - if "begin triggerblock" in temp.lower(): - try: - _, _, start_time = temp.split(None, 2) - start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() - except ValueError: - start_time = 0.0 - continue - if not temp or not temp[0].isdigit(): # Could be a comment continue msg_kwargs = {} try: timestamp, channel, rest_of_message = temp.split(None, 2) - timestamp = float(timestamp) + timestamp = float(timestamp) + self.start_time msg_kwargs["timestamp"] = timestamp if channel == "CANFD": msg_kwargs["is_fd"] = True From 3b5f0a776d328e49995c392e7cad90d0f72f8e11 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 17:45:44 +0000 Subject: [PATCH 0595/1235] Format code with black --- can/io/asc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index be773f86f..411daddfc 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -77,7 +77,9 @@ def _extract_header(self): elif lower_case.startswith("begin triggerblock"): try: _, _, start_time = lower_case.split(None, 2) - start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() + start_time = datetime.strptime( + start_time, "%a %b %m %I:%M:%S.%f %p %Y" + ).timestamp() except ValueError: start_time = 0.0 self.start_time = start_time From f941f2fb7a509dc2ca5c1c524874ba34b30fa682 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 12:59:51 -0500 Subject: [PATCH 0596/1235] adding option for relative or absolute --- can/io/asc.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index 411daddfc..d6d705ca2 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -37,6 +37,7 @@ def __init__( self, file: Union[typechecking.FileLike, typechecking.StringPathLike], base: str = "hex", + relative: bool = True, ) -> None: """ :param file: a path-like object or as file-like object to read from @@ -45,6 +46,9 @@ def __init__( :param base: Select the base(hex or dec) of id and data. If the header of the asc file contains base information, this value will be overwritten. Default "hex". + :param relative: Select whether the timestamps are `relative` (starting + at 0.0) of `absolute` (starting at the system time). + Default `True = relative`. """ super().__init__(file, mode="r") @@ -52,6 +56,7 @@ def __init__( raise ValueError("The given file cannot be None") self.base = base self._converted_base = self._check_base(base) + self.relative_timestamp = relative self.date = None # TODO - what is this used for? The ASC Writer only prints `absolute` self.timestamps_format = None @@ -82,7 +87,10 @@ def _extract_header(self): ).timestamp() except ValueError: start_time = 0.0 - self.start_time = start_time + if self.relative_timestamp: + self.start_time = 0.0 + else: + self.start_time = start_time # Currently the last line in the header which is parsed break else: From 65d5bd2c23ddcb7aadb42c9a852bef9d57edd235 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 21:29:46 +0200 Subject: [PATCH 0597/1235] clarify CanOperationError --- can/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index 3a279a449..51b8dcced 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -46,9 +46,9 @@ class CanOperationError(CanError): Example scenarios: - A call to a library function results in an unexpected return value - An invalid message was received - - Attempted to send an invalid message + - The driver rejected a message that was meant to be sent - Cyclic redundancy check (CRC) failed - - Message remained unacknowledged + - A message remained unacknowledged """ From 2111d7ce11f8f41fd5cc93915a7f0b375b431e83 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 14:45:14 -0500 Subject: [PATCH 0598/1235] simple refactor and add test --- can/io/asc.py | 17 +++++++++++------ test/data/test_CanMessage.asc | 2 +- test/logformats_test.py | 35 +++++++++++++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index d6d705ca2..b4e83df7d 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -33,11 +33,13 @@ class ASCReader(BaseIOHandler): bus statistics, J1939 Transport Protocol messages) is ignored. """ + FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" + def __init__( self, file: Union[typechecking.FileLike, typechecking.StringPathLike], base: str = "hex", - relative: bool = True, + relative_timestamp: bool = True, ) -> None: """ :param file: a path-like object or as file-like object to read from @@ -46,9 +48,9 @@ def __init__( :param base: Select the base(hex or dec) of id and data. If the header of the asc file contains base information, this value will be overwritten. Default "hex". - :param relative: Select whether the timestamps are `relative` (starting - at 0.0) of `absolute` (starting at the system time). - Default `True = relative`. + :param relative_timestamp: Select whether the timestamps are + `relative` (starting at 0.0) or `absolute` (starting at + the system time). Default `True = relative`. """ super().__init__(file, mode="r") @@ -56,7 +58,7 @@ def __init__( raise ValueError("The given file cannot be None") self.base = base self._converted_base = self._check_base(base) - self.relative_timestamp = relative + self.relative_timestamp = relative_timestamp self.date = None # TODO - what is this used for? The ASC Writer only prints `absolute` self.timestamps_format = None @@ -78,12 +80,15 @@ def _extract_header(self): self.timestamps_format = timestamp_format elif lower_case.endswith("internal events logged"): self.internal_events_logged = not lower_case.startswith("no") + elif lower_case.startswith("// version"): + # the test files include `// version 9.0.0` - not sure what this is + continue # grab absolute timestamp elif lower_case.startswith("begin triggerblock"): try: _, _, start_time = lower_case.split(None, 2) start_time = datetime.strptime( - start_time, "%a %b %m %I:%M:%S.%f %p %Y" + start_time, self.FORMAT_START_OF_FILE_DATE ).timestamp() except ValueError: start_time = 0.0 diff --git a/test/data/test_CanMessage.asc b/test/data/test_CanMessage.asc index 52dda34d9..881f16132 100644 --- a/test/data/test_CanMessage.asc +++ b/test/data/test_CanMessage.asc @@ -2,7 +2,7 @@ date Sam Sep 30 15:06:13.191 2017 base hex timestamps absolute internal events logged // version 9.0.0 -Begin Triggerblock Sam Sep 30 15:06:13.191 2017 +Begin Triggerblock Sat Sep 30 10:06:13.191 PM 2017 0.000000 Start of measurement 2.5010 2 C8 Tx d 8 09 08 07 06 05 04 03 02 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 diff --git a/test/logformats_test.py b/test/logformats_test.py index dfd6fa860..65a20097d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -20,6 +20,7 @@ import os from abc import abstractmethod, ABCMeta from itertools import zip_longest +from datetime import datetime import can @@ -364,6 +365,8 @@ def assertIncludesComments(self, filename): class TestAscFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" + FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" + def _setup_instance(self): super()._setup_instance_helper( can.ASCWriter, @@ -374,11 +377,39 @@ def _setup_instance(self): adds_default_channel=0, ) - def _read_log_file(self, filename): + def _read_log_file(self, filename, **kwargs): logfile = os.path.join(os.path.dirname(__file__), "data", filename) - with can.ASCReader(logfile) as reader: + with can.ASCReader(logfile, **kwargs) as reader: return list(reader) + def test_absolute_time(self): + time_from_file = "Sat Sep 30 10:06:13.191 PM 2017" + start_time = datetime.strptime( + time_from_file, self.FORMAT_START_OF_FILE_DATE + ).timestamp() + + expected_messages = [ + can.Message( + timestamp=2.5010 + start_time, + arbitration_id=0xC8, + is_extended_id=False, + is_rx=False, + channel=1, + dlc=8, + data=[9, 8, 7, 6, 5, 4, 3, 2], + ), + can.Message( + timestamp=17.876708 + start_time, + arbitration_id=0x6F9, + is_extended_id=False, + channel=0, + dlc=0x8, + data=[5, 0xC, 0, 0, 0, 0, 0, 0], + ), + ] + actual = self._read_log_file("test_CanMessage.asc", relative_timestamp=False) + self.assertMessagesEqual(actual, expected_messages) + def test_can_message(self): expected_messages = [ can.Message( From 7389b23f8dc2a53adba5d58568333d5e350fad42 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 19:47:15 +0000 Subject: [PATCH 0599/1235] Format code with black --- can/io/asc.py | 2 +- test/logformats_test.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index b4e83df7d..01d91d695 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -48,7 +48,7 @@ def __init__( :param base: Select the base(hex or dec) of id and data. If the header of the asc file contains base information, this value will be overwritten. Default "hex". - :param relative_timestamp: Select whether the timestamps are + :param relative_timestamp: Select whether the timestamps are `relative` (starting at 0.0) or `absolute` (starting at the system time). Default `True = relative`. """ diff --git a/test/logformats_test.py b/test/logformats_test.py index 65a20097d..5dfa48e5c 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -366,7 +366,7 @@ class TestAscFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" - + def _setup_instance(self): super()._setup_instance_helper( can.ASCWriter, @@ -385,8 +385,8 @@ def _read_log_file(self, filename, **kwargs): def test_absolute_time(self): time_from_file = "Sat Sep 30 10:06:13.191 PM 2017" start_time = datetime.strptime( - time_from_file, self.FORMAT_START_OF_FILE_DATE - ).timestamp() + time_from_file, self.FORMAT_START_OF_FILE_DATE + ).timestamp() expected_messages = [ can.Message( From bcf75cb5317ee8e5de2023b1946749f13f7b0963 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 21:47:38 +0200 Subject: [PATCH 0600/1235] add error code to CanError --- can/exceptions.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/can/exceptions.py b/can/exceptions.py index 51b8dcced..886635c44 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -15,9 +15,38 @@ :class:`ValueError`. This should be documented for the function at hand. """ +from typing import Optional + class CanError(Exception): - """Base class for all CAN related exceptions.""" + """Base class for all CAN related exceptions. + + If specified, the error code is automatically prepended to the message: + + >>> # With an error code: + >>> error = CanError(message="Failed to do the thing", error_code=42) + >>> str(error) + 'Failed to do the thing [Error Code 42]' + >>> + >>> # Missing the error code: + >>> plain_error = CanError(message="Something went wrong ...") + >>> str(plain_error) + 'Something went wrong ...' + + :param error_code: + An optional error code to narrow down the cause of the fault + + :arg error_code: + An optional error code to narrow down the cause of the fault + """ + + def __init__( + self, message: str = "", error_code: Optional[int] = None, *args, **kwargs + ) -> None: + self.error_code = error_code + super().__init__( + message if error_code is None else f"{message} [Error Code {error_code}]" + ) class CanBackEndError(CanError): From 4e0ae779a01197ed94f492c04c2f0ead4e95412e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 21:51:52 +0200 Subject: [PATCH 0601/1235] improve docs --- can/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index 886635c44..55c4b0d82 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -23,8 +23,8 @@ class CanError(Exception): If specified, the error code is automatically prepended to the message: - >>> # With an error code: - >>> error = CanError(message="Failed to do the thing", error_code=42) + >>> # With an error code (it also works with a specific error): + >>> error = CanOperationError(message="Failed to do the thing", error_code=42) >>> str(error) 'Failed to do the thing [Error Code 42]' >>> From 48b2acf7560ec280a8fdfc3fb3d318ceef0b4831 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 21:57:58 +0200 Subject: [PATCH 0602/1235] simplify IXXAT interface test --- test/test_interface_ixxat.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index f9bb135df..9e0cf550e 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -19,23 +19,20 @@ def setUp(self): bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: - raise (unittest.SkipTest()) - - def tearDown(self): - pass + raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): # channel must be >= 0 with self.assertRaises(ValueError): - bus = can.Bus(interface="ixxat", channel=-1) + can.Bus(interface="ixxat", channel=-1) # rxFifoSize must be > 0 with self.assertRaises(ValueError): - bus = can.Bus(interface="ixxat", channel=0, rxFifoSize=0) + can.Bus(interface="ixxat", channel=0, rxFifoSize=0) # txFifoSize must be > 0 with self.assertRaises(ValueError): - bus = can.Bus(interface="ixxat", channel=0, txFifoSize=0) + can.Bus(interface="ixxat", channel=0, txFifoSize=0) class HardwareTestCase(unittest.TestCase): @@ -48,22 +45,17 @@ def setUp(self): bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() except ImportError: - raise (unittest.SkipTest()) - - def tearDown(self): - pass + raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): # non-existent channel -> use arbitrary high value with self.assertRaises(can.CanInitializationError): - bus = can.Bus(interface="ixxat", channel=0xFFFF) + can.Bus(interface="ixxat", channel=0xFFFF) def test_send_after_shutdown(self): - bus = can.Bus(interface="ixxat", channel=0) - msg = can.Message(arbitration_id=0x3FF, dlc=0) - bus.shutdown() - with self.assertRaises(can.CanOperationError): - bus.send(msg) + with can.Bus(interface="ixxat", channel=0) as bus: + with self.assertRaises(can.CanOperationError): + bus.send(can.Message(arbitration_id=0x3FF, dlc=0)) if __name__ == "__main__": From 2581e43dadb6defc93da01f0a72f2dccb5cbfbc7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 22:55:09 +0200 Subject: [PATCH 0603/1235] bump version to 4.0.0-dev2 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index a7280250d..598fc6eca 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -9,7 +9,7 @@ from typing import Dict, Any -__version__ = "4.0.0-dev" +__version__ = "4.0.0-dev.2" log = logging.getLogger("can") From 1f2fe7a5ce53c5162173cc9ed918e7eaac35c552 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Apr 2021 23:47:12 +0200 Subject: [PATCH 0604/1235] fix size/layout of struct needed for timestamping --- can/interfaces/socketcan/socketcan.py | 6 +++++- can/interfaces/udp_multicast/bus.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 920109581..95387e776 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -549,6 +549,10 @@ def capture_message( ), "received control message type that was not requested" # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data) + if nanoseconds >= 1e9: + raise can.CanError( + f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9" + ) timestamp = seconds + nanoseconds * 1e-9 # EXT, RTR, ERR flags -> boolean attributes @@ -594,7 +598,7 @@ def capture_message( # Constants needed for precise handling of timestamps if CMSG_SPACE_available: - RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@II") + RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@ll") RECEIVED_ANCILLARY_BUFFER_SIZE = CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index f4f0e5fd8..ee6a6e1ee 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -181,7 +181,7 @@ def __init__( raise RuntimeError("could not connect to a multicast IP network") # used in recv() - self.received_timestamp_struct = "@II" + self.received_timestamp_struct = "@ll" ancillary_data_size = struct.calcsize(self.received_timestamp_struct) self.received_ancillary_buffer_size = socket.CMSG_SPACE(ancillary_data_size) @@ -306,6 +306,10 @@ def recv( seconds, nanoseconds = struct.unpack( self.received_timestamp_struct, cmsg_data ) + if nanoseconds >= 1e9: + raise can.CanError( + f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9" + ) timestamp = seconds + nanoseconds * 1.0e-9 return raw_message_data, sender_address, timestamp From 230e8072f9a649270d97d5e4ca6ef5a3e6df3512 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 00:29:10 +0200 Subject: [PATCH 0605/1235] add regression test --- test/back2back_test.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 1ceec073b..1950c5fa5 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -2,11 +2,11 @@ # coding: utf-8 """ -This module tests two virtual buses attached to each other. +This module tests two buses attached to each other. """ import unittest -from time import sleep +from time import sleep, time from multiprocessing.dummy import Pool as ThreadPool import random @@ -235,6 +235,34 @@ def test_fileno(self): self.assertIsNotNone(fileno) self.assertTrue(fileno == -1 or fileno > 0) + def test_timestamp_is_absolute(self): + """Tests that the timestamp that is returned is an absolute one.""" + self.bus2.send(can.Message()) + message = self.bus1.recv(self.TIMEOUT) + # The allowed delta is still quite large to make this work on the CI server + self.assertAlmostEqual(message.timestamp, time(), delta=self.TIMEOUT) + + def test_sub_second_timestamp_resolution(self): + """Tests that the timestamp that is returned has sufficient resolution. + + The property that the timestamp has resolution below seconds is + checked on two messages to reduce the probability of both having + a timestamp of exactly a full second by accident to a negligible + level. + + This is a regression test that was added for #1021. + """ + self.bus2.send(can.Message()) + sleep(0.01) + self.bus2.send(can.Message()) + + recv_msg_1 = self.bus1.recv(self.TIMEOUT) + recv_msg_2 = self.bus1.recv(self.TIMEOUT) + + sub_second_fraction_1 = recv_msg_1.timestamp % 1 + sub_second_fraction_2 = recv_msg_2.timestamp % 1 + self.assertGreater(sub_second_fraction_1 + sub_second_fraction_2, 0) + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class BasicTestSocketCan(Back2BackTestCase): From 2072d174132ee5c47d4506d466ac9e67c69c425d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 00:59:04 +0200 Subject: [PATCH 0606/1235] enable a tiny test case --- test/logformats_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 5dfa48e5c..10cd4e9f9 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -737,9 +737,11 @@ class TestPrinter(unittest.TestCase): TODO test append mode """ - # TODO add CAN FD messages messages = ( - TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + TEST_MESSAGES_ERROR_FRAMES + TEST_MESSAGES_BASE + + TEST_MESSAGES_REMOTE_FRAMES + + TEST_MESSAGES_ERROR_FRAMES + + TEST_MESSAGES_CAN_FD ) def test_not_crashes_with_stdout(self): From 4af470800284fda0908f92c3478e12f262999b71 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 01:44:51 +0200 Subject: [PATCH 0607/1235] simplify linter config --- .pylintrc | 3 ++- .travis.yml | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.pylintrc b/.pylintrc index 2389f7559..d1e3c5538 100644 --- a/.pylintrc +++ b/.pylintrc @@ -138,7 +138,8 @@ disable=print-statement, xreadlines-attribute, deprecated-sys-function, exception-escape, - comprehension-escape + comprehension-escape, + abstract-class-instantiated # needed when instantiating can.Bus # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/.travis.yml b/.travis.yml index 785aa2a6b..9006c3f1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,10 +79,8 @@ jobs: - pylint --rcfile=.pylintrc-wip can/**.py - pylint --rcfile=.pylintrc setup.py - pylint --rcfile=.pylintrc doc.conf - # check the scripts folder - - find scripts -type f -name "*.py" | xargs pylint --rcfile=.pylintrc - # check the examples - - find examples -type f -name "*.py" | xargs pylint --rcfile=.pylintrc-wip + - pylint --rcfile=.pylintrc scripts/**.py + - pylint --rcfile=.pylintrc examples/**.py # ------------- # mypy checking: - mypy From 3dbc92e68e1af318c552056317c394d8f6849ebe Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 01:49:26 +0200 Subject: [PATCH 0608/1235] simplify linter config --- .pylintrc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index d1e3c5538..602393f1f 100644 --- a/.pylintrc +++ b/.pylintrc @@ -139,7 +139,18 @@ disable=print-statement, deprecated-sys-function, exception-escape, comprehension-escape, - abstract-class-instantiated # needed when instantiating can.Bus + abstract-class-instantiated, # needed when instantiating can.Bus + wrong-import-position, + wrong-import-order, + missing-module-docstring, + missing-class-docstring, + missing-function-docstring, + too-many-arguments, + invalid-name, + abstract-method, + too-few-public-methods, + no-else-raise + # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From c7f0c58f46c4c45920d1b011b6a68ba8453b91d4 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 02:03:09 +0200 Subject: [PATCH 0609/1235] update linter rules --- .pylintrc | 10 +++++++++- can/broadcastmanager.py | 6 +++--- can/bus.py | 11 +++++++---- can/ctypesutil.py | 15 ++++++++------- can/interface.py | 6 +++--- can/message.py | 6 ++++-- can/notifier.py | 8 ++++---- can/thread_safe_bus.py | 6 +++--- can/viewer.py | 10 ++++++---- 9 files changed, 47 insertions(+), 31 deletions(-) diff --git a/.pylintrc b/.pylintrc index 602393f1f..717edc752 100644 --- a/.pylintrc +++ b/.pylintrc @@ -149,7 +149,15 @@ disable=print-statement, invalid-name, abstract-method, too-few-public-methods, - no-else-raise + no-else-raise, + no-else-return, + too-many-locals, + too-many-instance-attributes, + too-many-branches, + too-many-statements, + fixme, + duplicate-code, + cyclic-import # Enable the message, report, category or checker with the given id(s). You can diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 6dd4f3fd7..b884a758d 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -177,8 +177,8 @@ def __init__( self, channel: typechecking.Channel, messages: Union[Sequence[Message], Message], - count: int, - initial_period: float, + count: int, # pylint: disable=unused-argument + initial_period: float, # pylint: disable=unused-argument subsequent_period: float, ) -> None: """ @@ -265,7 +265,7 @@ def _run(self) -> None: started = time.perf_counter() try: self.bus.send(self.messages[msg_index]) - except Exception as exc: + except Exception as exc: # pylint: disable=broad-except log.exception(exc) if self.on_error: if not self.on_error(exc): diff --git a/can/bus.py b/can/bus.py index 246f45d19..6697a18c9 100644 --- a/can/bus.py +++ b/can/bus.py @@ -274,9 +274,9 @@ def _send_periodic_internal( """ if not hasattr(self, "_lock_send_periodic"): # Create a send lock for this bus, but not for buses which override this method - self._lock_send_periodic = ( + self._lock_send_periodic = ( # pylint: disable=attribute-defined-outside-init threading.Lock() - ) # pylint: disable=attribute-defined-outside-init + ) task = ThreadBasedCyclicSendTask( self, self._lock_send_periodic, msgs, period, duration ) @@ -446,7 +446,10 @@ def fileno(self) -> int: class _SelfRemovingCyclicTask(CyclicSendTaskABC, ABC): - """Removes itself from a bus. Only needed for typing :meth:`Bus._periodic_tasks`. Do not instantiate.""" + """Removes itself from a bus. - def stop(self, remove_task: bool = True) -> None: + Only needed for typing :meth:`Bus._periodic_tasks`. Do not instantiate. + """ + + def stop(self, remove_task: bool = True) -> None: # pylint: disable=arguments-differ raise NotImplementedError() diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 1063130d3..7631ac988 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -23,6 +23,8 @@ class CLibrary(_LibBase): # type: ignore def __init__(self, library_or_path: Union[str, ctypes.CDLL]) -> None: + self.func_name: Any + if isinstance(library_or_path, str): super().__init__(library_or_path) else: @@ -53,23 +55,22 @@ def map_symbol( else: prototype = _FUNCTION_TYPE(restype) try: - symbol: Any = prototype((func_name, self)) + self.func_name = prototype((func_name, self)) except AttributeError: raise ImportError( f'Could not map function "{func_name}" from library {self._name}' ) from None - symbol._name = func_name + self.func_name._name = func_name # pylint: disable=protected-access log.debug( - f'Wrapped function "{func_name}", result type: {type(restype)}, error_check {errcheck}' + 'Wrapped function "%s", result type: %s, error_check %s', + func_name, type(restype), errcheck ) if errcheck is not None: - symbol.errcheck = errcheck - - self.func_name = symbol + self.func_name.errcheck = errcheck - return symbol + return self.func_name if sys.platform == "win32": diff --git a/can/interface.py b/can/interface.py index 2d1ad0891..129843ce7 100644 --- a/can/interface.py +++ b/can/interface.py @@ -64,7 +64,7 @@ class Bus(BusABC): # pylint disable=abstract-method """ @staticmethod - def __new__(cls, channel=None, *args, **kwargs): + def __new__(cls, channel=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg """ Takes the same arguments as :class:`can.BusABC.__init__`. Some might have a special meaning, see below. @@ -158,8 +158,8 @@ def detect_available_configs(interfaces=None): # get available channels try: available = list( - bus_class._detect_available_configs() - ) # pylint: disable=protected-access + bus_class._detect_available_configs() # pylint: disable=protected-access + ) except NotImplementedError: log_autodetect.debug( 'interface "%s" does not support detection of available configurations', diff --git a/can/message.py b/can/message.py index 9ad9c2118..592b99af0 100644 --- a/can/message.py +++ b/can/message.py @@ -72,9 +72,11 @@ def __init__( 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`. + or creating a message with both `is_remote_frame` and `is_error_frame` set + to `True`. - :raises ValueError: if and only if `check` is set to `True` and one or more arguments were invalid + :raises ValueError: + if and only if `check` is set to `True` and one or more arguments were invalid """ self.timestamp = timestamp self.arbitration_id = arbitration_id diff --git a/can/notifier.py b/can/notifier.py index 2842cc130..ed41697e4 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -19,7 +19,7 @@ class Notifier: def __init__( self, - bus: BusABC, + bus: Union[BusABC, List[BusABC]], listeners: Iterable[Listener], timeout: float = 1.0, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -52,8 +52,8 @@ def __init__( 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) + for each_bus in buses: + self.add_bus(each_bus) def add_bus(self, bus: BusABC): """Add a bus for notification. @@ -117,7 +117,7 @@ def _rx_thread(self, bus: BusABC): else: self._on_message_received(msg) msg = bus.recv(self.timeout) - except Exception as exc: + except Exception as exc: # pylint: disable=broad-except self.exception = exc if self._loop is not None: self._loop.call_soon_threadsafe(self._on_error, exc) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index f36119751..c77943b13 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -66,11 +66,11 @@ def __init__(self, *args, **kwargs): self._lock_send = RLock() self._lock_recv = RLock() - def recv(self, timeout=None, *args, **kwargs): + def recv(self, timeout=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg with self._lock_recv: return self.__wrapped__.recv(timeout=timeout, *args, **kwargs) - def send(self, msg, timeout=None, *args, **kwargs): + def send(self, msg, timeout=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg with self._lock_send: return self.__wrapped__.send(msg, timeout=timeout, *args, **kwargs) @@ -87,7 +87,7 @@ def filters(self, filters): with self._lock_recv: self.__wrapped__.filters = filters - def set_filters(self, filters=None, *args, **kwargs): + def set_filters(self, filters=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg with self._lock_recv: return self.__wrapped__.set_filters(filters=filters, *args, **kwargs) diff --git a/can/viewer.py b/can/viewer.py index dde075f0e..6824767ff 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -60,7 +60,8 @@ def __init__(self, stdscr, bus, data_structs, testing=False): # Get the window dimensions - used for resizing the window self.y, self.x = self.stdscr.getmaxyx() - # Do not wait for key inputs, disable the cursor and choose the background color automatically + # Do not wait for key inputs, disable the cursor and choose the background color + # automatically self.stdscr.nodelay(True) curses.curs_set(0) curses.use_default_colors() @@ -165,8 +166,8 @@ def unpack_data( values = list(as_struct_t.unpack(data)) return values - else: - raise ValueError("Unknown command: 0x{:02X}".format(cmd)) + + raise ValueError("Unknown command: 0x{:02X}".format(cmd)) def draw_can_bus_message(self, msg, sorting=False): # Use the CAN-Bus ID as the key in the dict @@ -498,7 +499,8 @@ def parse_args(args): # f = float (32-bits), d = double (64-bits) # # An optional conversion from real units to integers can be given as additional arguments. - # In order to convert from raw integer value the real units are multiplied with the values and similarly the values + # In order to convert from raw integer value the real units are multiplied with the values and + # similarly the values # are divided by the value in order to convert from real units to raw integer values. data_structs = ( {} From 179f4f11d654abc282893122eb5c911154a3642a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 02:04:55 +0200 Subject: [PATCH 0610/1235] simplify linting --- .pylintrc | 129 +++++++++++++++------------------------------------- .travis.yml | 2 +- 2 files changed, 38 insertions(+), 93 deletions(-) diff --git a/.pylintrc b/.pylintrc index 717edc752..37651ce95 100644 --- a/.pylintrc +++ b/.pylintrc @@ -60,104 +60,49 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, - abstract-class-instantiated, # needed when instantiating can.Bus - wrong-import-position, +disable=invalid-name, + missing-docstring, + empty-docstring, + line-too-long, + too-many-lines, + bad-whitespace, + bad-continuation, wrong-import-order, - missing-module-docstring, - missing-class-docstring, - missing-function-docstring, - too-many-arguments, - invalid-name, - abstract-method, - too-few-public-methods, - no-else-raise, - no-else-return, - too-many-locals, + ungrouped-imports, + wrong-import-position, + too-many-function-args, + locally-disabled, too-many-instance-attributes, + too-few-public-methods, + too-many-public-methods, too-many-branches, + too-many-arguments, + too-many-locals, too-many-statements, + too-many-nested-blocks, + no-else-raise, + bad-indentation, + wildcard-import, fixme, + broad-except, + redefined-builtin, + no-else-return, + redefined-argument-from-local, + abstract-class-instantiated, + multiple-statements, + cyclic-import, + useless-else-on-loop, duplicate-code, - cyclic-import + inconsistent-return-statements, + arguments-differ, + unused-wildcard-import, + logging-fstring-interpolation, + logging-format-interpolation, + unused-argument, + abstract-method, + attribute-defined-outside-init, + protected-access, + keyword-arg-before-vararg # Enable the message, report, category or checker with the given id(s). You can diff --git a/.travis.yml b/.travis.yml index 9006c3f1a..a44449a99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -76,7 +76,7 @@ jobs: # warnings to the .pylintrc-wip file to prevent them from being # re-introduced # check the entire main codebase - - pylint --rcfile=.pylintrc-wip can/**.py + - pylint --rcfile=.pylintrc can/**.py - pylint --rcfile=.pylintrc setup.py - pylint --rcfile=.pylintrc doc.conf - pylint --rcfile=.pylintrc scripts/**.py From 7a76463175a8480c3eeea49719cd9abe6ca8a68c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 00:05:57 +0000 Subject: [PATCH 0611/1235] Format code with black --- can/bus.py | 4 +++- can/ctypesutil.py | 4 +++- can/interface.py | 4 +++- can/thread_safe_bus.py | 12 +++++++++--- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/can/bus.py b/can/bus.py index 6697a18c9..9cd6d2cc1 100644 --- a/can/bus.py +++ b/can/bus.py @@ -451,5 +451,7 @@ class _SelfRemovingCyclicTask(CyclicSendTaskABC, ABC): Only needed for typing :meth:`Bus._periodic_tasks`. Do not instantiate. """ - def stop(self, remove_task: bool = True) -> None: # pylint: disable=arguments-differ + def stop( + self, remove_task: bool = True + ) -> None: # pylint: disable=arguments-differ raise NotImplementedError() diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 7631ac988..cfcaa6e08 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -64,7 +64,9 @@ def map_symbol( self.func_name._name = func_name # pylint: disable=protected-access log.debug( 'Wrapped function "%s", result type: %s, error_check %s', - func_name, type(restype), errcheck + func_name, + type(restype), + errcheck, ) if errcheck is not None: diff --git a/can/interface.py b/can/interface.py index 129843ce7..0f7dd9896 100644 --- a/can/interface.py +++ b/can/interface.py @@ -64,7 +64,9 @@ class Bus(BusABC): # pylint disable=abstract-method """ @staticmethod - def __new__(cls, channel=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg + def __new__( + cls, channel=None, *args, **kwargs + ): # pylint: disable=keyword-arg-before-vararg """ Takes the same arguments as :class:`can.BusABC.__init__`. Some might have a special meaning, see below. diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index c77943b13..330ba78e7 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -66,11 +66,15 @@ def __init__(self, *args, **kwargs): self._lock_send = RLock() self._lock_recv = RLock() - def recv(self, timeout=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg + def recv( + self, timeout=None, *args, **kwargs + ): # pylint: disable=keyword-arg-before-vararg with self._lock_recv: return self.__wrapped__.recv(timeout=timeout, *args, **kwargs) - def send(self, msg, timeout=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg + def send( + self, msg, timeout=None, *args, **kwargs + ): # pylint: disable=keyword-arg-before-vararg with self._lock_send: return self.__wrapped__.send(msg, timeout=timeout, *args, **kwargs) @@ -87,7 +91,9 @@ def filters(self, filters): with self._lock_recv: self.__wrapped__.filters = filters - def set_filters(self, filters=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg + def set_filters( + self, filters=None, *args, **kwargs + ): # pylint: disable=keyword-arg-before-vararg with self._lock_recv: return self.__wrapped__.set_filters(filters=filters, *args, **kwargs) From 1693e034858bb6f94578aee2cb037b8c6444a1b0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 02:07:17 +0200 Subject: [PATCH 0612/1235] simplify --- .pylintrc | 7 - .pylintrc-wip | 534 -------------------------------------------------- 2 files changed, 541 deletions(-) delete mode 100644 .pylintrc-wip diff --git a/.pylintrc b/.pylintrc index 37651ce95..e3d766a7b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -81,7 +81,6 @@ disable=invalid-name, too-many-statements, too-many-nested-blocks, no-else-raise, - bad-indentation, wildcard-import, fixme, broad-except, @@ -89,21 +88,15 @@ disable=invalid-name, no-else-return, redefined-argument-from-local, abstract-class-instantiated, - multiple-statements, cyclic-import, - useless-else-on-loop, duplicate-code, inconsistent-return-statements, arguments-differ, unused-wildcard-import, - logging-fstring-interpolation, logging-format-interpolation, unused-argument, abstract-method, - attribute-defined-outside-init, protected-access, - keyword-arg-before-vararg - # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/.pylintrc-wip b/.pylintrc-wip deleted file mode 100644 index 67ad614ab..000000000 --- a/.pylintrc-wip +++ /dev/null @@ -1,534 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-whitelist= - -# Add files or directories to be ignored. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to to be ignored. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=invalid-name, - missing-docstring, - empty-docstring, - line-too-long, - too-many-lines, - bad-whitespace, - bad-continuation, - wrong-import-order, - ungrouped-imports, - wrong-import-position, - too-many-function-args, - locally-disabled, - too-many-instance-attributes, - too-few-public-methods, - too-many-public-methods, - too-many-branches, - too-many-arguments, - too-many-locals, - too-many-statements, - too-many-nested-blocks, - no-else-raise, - bad-indentation, - wildcard-import, - fixme, - broad-except, - redefined-builtin, - no-else-return, - redefined-argument-from-local, - abstract-class-instantiated, - multiple-statements, - cyclic-import, - useless-else-on-loop, - duplicate-code, - inconsistent-return-statements, - arguments-differ, - unused-wildcard-import, - logging-fstring-interpolation, - logging-format-interpolation, - unused-argument, - abstract-method, - attribute-defined-outside-init, - protected-access, - keyword-arg-before-vararg - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[LOGGING] - -# Format style used to check logging format string. `old` means using % -# formatting, while `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[STRING] - -# This flag controls whether the implicit-str-concat-in-sequence should -# generate a warning on implicit string concatenation in sequences defined over -# several lines. -check-str-concat-over-line-jumps=no - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -#variable-rgx= - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package.. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[DESIGN] - -# Maximum number of arguments for function / method. -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement. -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception From c767b99e0d91d9019501d385bc3430df22f792f4 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 02:15:39 +0200 Subject: [PATCH 0613/1235] remove unnessesary disables --- .pylintrc | 14 +------------- can/interface.py | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/.pylintrc b/.pylintrc index e3d766a7b..629b134be 100644 --- a/.pylintrc +++ b/.pylintrc @@ -65,13 +65,10 @@ disable=invalid-name, empty-docstring, line-too-long, too-many-lines, - bad-whitespace, - bad-continuation, wrong-import-order, ungrouped-imports, wrong-import-position, too-many-function-args, - locally-disabled, too-many-instance-attributes, too-few-public-methods, too-many-public-methods, @@ -83,20 +80,11 @@ disable=invalid-name, no-else-raise, wildcard-import, fixme, - broad-except, - redefined-builtin, no-else-return, - redefined-argument-from-local, + abstract-method, abstract-class-instantiated, cyclic-import, duplicate-code, - inconsistent-return-statements, - arguments-differ, - unused-wildcard-import, - logging-format-interpolation, - unused-argument, - abstract-method, - protected-access, # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/can/interface.py b/can/interface.py index 129843ce7..7126ef01e 100644 --- a/can/interface.py +++ b/can/interface.py @@ -56,7 +56,7 @@ def _get_class_for_interface(interface): return bus_class -class Bus(BusABC): # pylint disable=abstract-method +class Bus(BusABC): # pylint: disable=abstract-method """Bus wrapper with configuration loading. Instantiates a CAN Bus of the given ``interface``, falls back to reading a From 5c28e21c895486ea19874036f6e0ad9effd1a0b6 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 02:17:35 +0200 Subject: [PATCH 0614/1235] simplify invocation --- .travis.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index a44449a99..781219e25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,15 +72,8 @@ jobs: script: # ------------- # pylint checking: - # Slowly enable all pylint warnings by adding addressed classes of - # warnings to the .pylintrc-wip file to prevent them from being - # re-introduced - # check the entire main codebase - - pylint --rcfile=.pylintrc can/**.py - - pylint --rcfile=.pylintrc setup.py - - pylint --rcfile=.pylintrc doc.conf - - pylint --rcfile=.pylintrc scripts/**.py - - pylint --rcfile=.pylintrc examples/**.py + # check the entire main codebase (except the tests) + - pylint --rcfile=.pylintrc can/**.py setup.py doc.conf scripts/**.py examples/**.py # ------------- # mypy checking: - mypy From 2177a0effa33a804b498c75c2bea019ccb563017 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 02:25:57 +0200 Subject: [PATCH 0615/1235] simplify --- .pylintrc | 3 --- can/bus.py | 4 ++-- can/message.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.pylintrc b/.pylintrc index 629b134be..a5a3d2ae3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -63,12 +63,10 @@ confidence= disable=invalid-name, missing-docstring, empty-docstring, - line-too-long, too-many-lines, wrong-import-order, ungrouped-imports, wrong-import-position, - too-many-function-args, too-many-instance-attributes, too-few-public-methods, too-many-public-methods, @@ -76,7 +74,6 @@ disable=invalid-name, too-many-arguments, too-many-locals, too-many-statements, - too-many-nested-blocks, no-else-raise, wildcard-import, fixme, diff --git a/can/bus.py b/can/bus.py index 9cd6d2cc1..6360dd98e 100644 --- a/can/bus.py +++ b/can/bus.py @@ -451,7 +451,7 @@ class _SelfRemovingCyclicTask(CyclicSendTaskABC, ABC): Only needed for typing :meth:`Bus._periodic_tasks`. Do not instantiate. """ - def stop( + def stop( # pylint: disable=arguments-differ self, remove_task: bool = True - ) -> None: # pylint: disable=arguments-differ + ) -> None: raise NotImplementedError() diff --git a/can/message.py b/can/message.py index 592b99af0..0d3798f15 100644 --- a/can/message.py +++ b/can/message.py @@ -48,7 +48,7 @@ class Message: "__weakref__", # support weak references to messages ) - def __init__( + def __init__( # pylint: disable=too-many-locals self, timestamp: float = 0.0, arbitration_id: int = 0, From 6f104181e0f09f8bcd57d103bf54be5464a7afc5 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 02:31:06 +0200 Subject: [PATCH 0616/1235] fix some issues --- test/network_test.py | 1 - test/test_cantact.py | 5 ----- test/test_kvaser.py | 4 +--- test/test_message_class.py | 2 +- test/test_rotating_loggers.py | 4 ++-- 5 files changed, 4 insertions(+), 12 deletions(-) diff --git a/test/network_test.py b/test/network_test.py index 2ee4795fb..ad02fae41 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -4,7 +4,6 @@ import unittest import threading -import queue import random import logging diff --git a/test/test_cantact.py b/test/test_cantact.py index dc3b6a385..51bf569bb 100644 --- a/test/test_cantact.py +++ b/test/test_cantact.py @@ -5,12 +5,7 @@ Tests for CANtact interfaces """ -import time -import logging import unittest -from unittest.mock import Mock, patch - -import pytest import can from can.interfaces import cantact diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 733bbc367..3acc2731d 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -4,11 +4,9 @@ """ """ -import ctypes import time -import logging import unittest -from unittest.mock import Mock, patch +from unittest.mock import Mock import pytest diff --git a/test/test_message_class.py b/test/test_message_class.py index 2654f79ef..03884c3e9 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -79,7 +79,7 @@ def test_methods(self, **kwargs): self.assertGreater(len("{}".format(message)), 0) _ = "{}".format(message) with self.assertRaises(Exception): - _ = "{somespec}".format(message) + _ = "{somespec}".format(message) # pylint: disable=missing-format-argument-key if sys.version_info.major > 2: self.assertEqual(bytearray(bytes(message)), kwargs["data"]) diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index 6f9121584..0fff84677 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -162,7 +162,7 @@ def test_on_message_received(self): with tempfile.TemporaryDirectory() as temp_dir: logger_instance.get_new_writer(os.path.join(temp_dir, "file.ASC")) - """Test without rollover""" + # Test without rollover should_rollover = Mock(return_value=False) do_rollover = Mock() writers_on_message_received = Mock() @@ -178,7 +178,7 @@ def test_on_message_received(self): do_rollover.assert_not_called() writers_on_message_received.assert_called_with(msg) - """Test with rollover""" + # Test with rollover should_rollover = Mock(return_value=True) do_rollover = Mock() writers_on_message_received = Mock() From c71e5eb5d7f787e8fcb40240c723f08225dc3852 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 00:31:44 +0000 Subject: [PATCH 0617/1235] Format code with black --- test/test_message_class.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_message_class.py b/test/test_message_class.py index 03884c3e9..f65cecb09 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -79,7 +79,9 @@ def test_methods(self, **kwargs): self.assertGreater(len("{}".format(message)), 0) _ = "{}".format(message) with self.assertRaises(Exception): - _ = "{somespec}".format(message) # pylint: disable=missing-format-argument-key + _ = "{somespec}".format( + message + ) # pylint: disable=missing-format-argument-key if sys.version_info.major > 2: self.assertEqual(bytearray(bytes(message)), kwargs["data"]) From e69296763e94dd414fa5376e44953e7e6c545385 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 22:33:00 -0500 Subject: [PATCH 0618/1235] adding OSError catch to prevent nonPOSIX timestamp from breaking things --- can/io/asc.py | 15 ++++++++------- tox.ini | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 01d91d695..f5cea85cb 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -85,16 +85,17 @@ def _extract_header(self): continue # grab absolute timestamp elif lower_case.startswith("begin triggerblock"): - try: - _, _, start_time = lower_case.split(None, 2) - start_time = datetime.strptime( - start_time, self.FORMAT_START_OF_FILE_DATE - ).timestamp() - except ValueError: - start_time = 0.0 if self.relative_timestamp: self.start_time = 0.0 else: + try: + _, _, start_time = lower_case.split(None, 2) + start_time = datetime.strptime( + start_time, self.FORMAT_START_OF_FILE_DATE + ).timestamp() + except (ValueError, OSError): + # `OSError` to handle non-POSIX capable timestamps + start_time = 0.0 self.start_time = start_time # Currently the last line in the header which is parsed break diff --git a/tox.ini b/tox.ini index a29c3cafe..63bccaa02 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ deps = pyserial~=3.0 commands = - pytest + pytest {posargs} recreate = True From 3dd632ce11d430d1b881333f5b5bf06669da08aa Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 10:13:23 +0200 Subject: [PATCH 0619/1235] move mypy config to separate file --- .travis.yml | 3 --- setup.cfg | 10 ++++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 785aa2a6b..8358806d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -86,9 +86,6 @@ jobs: # ------------- # mypy checking: - mypy - --python-version=3.7 - --ignore-missing-imports - --no-implicit-optional can/*.py can/io/**.py scripts/**.py diff --git a/setup.cfg b/setup.cfg index 498ec14ac..84d734573 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,12 @@ [metadata] license_file = LICENSE.txt + +[mypy] +python_version = 3.7 +warn_return_any = True +warn_unused_configs = True +ignore_missing_imports = True +no_implicit_optional = True +disallow_incomplete_defs = True +warn_redundant_casts = True +warn_unused_ignores = True From 740738c1a8e7470fa79e2fc9dddf037e3ffcd60c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 11:01:22 +0200 Subject: [PATCH 0620/1235] add lots of typing annotations --- can/bus.py | 4 +-- can/interface.py | 20 ++++++++++----- can/io/blf.py | 15 +++++++---- can/io/generic.py | 5 ++-- can/io/logger.py | 50 ++++++++++++++++++++---------------- can/io/player.py | 14 +++++++--- can/listener.py | 28 ++++++++++---------- can/notifier.py | 24 ++++++++--------- can/thread_safe_bus.py | 2 +- examples/virtual_can_demo.py | 2 +- 10 files changed, 96 insertions(+), 68 deletions(-) diff --git a/can/bus.py b/can/bus.py index 246f45d19..c330eb8a7 100644 --- a/can/bus.py +++ b/can/bus.py @@ -323,7 +323,7 @@ def filters(self) -> Optional[can.typechecking.CanFilters]: return self._filters @filters.setter - def filters(self, filters: Optional[can.typechecking.CanFilters]): + def filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: self.set_filters(filters) def set_filters( @@ -421,7 +421,7 @@ def state(self) -> BusState: return BusState.ACTIVE @state.setter - def state(self, new_state: BusState): + def state(self, new_state: BusState) -> None: """ Set the new state of the hardware """ diff --git a/can/interface.py b/can/interface.py index 2d1ad0891..23b350bd9 100644 --- a/can/interface.py +++ b/can/interface.py @@ -6,16 +6,18 @@ import importlib import logging +from typing import Any, cast, Iterable, Type, Optional, Union, List from .bus import BusABC from .util import load_config from .interfaces import BACKENDS +from .typechecking import AutoDetectedConfig, Channel log = logging.getLogger("can.interface") log_autodetect = log.getChild("detect_available_configs") -def _get_class_for_interface(interface): +def _get_class_for_interface(interface: str) -> Type[BusABC]: """ Returns the main bus class for the given interface. @@ -53,7 +55,7 @@ def _get_class_for_interface(interface): ) ) from None - return bus_class + return cast(Type[BusABC], bus_class) class Bus(BusABC): # pylint disable=abstract-method @@ -64,7 +66,9 @@ class Bus(BusABC): # pylint disable=abstract-method """ @staticmethod - def __new__(cls, channel=None, *args, **kwargs): + def __new__( # type: ignore + cls: Any, channel: Optional[Channel] = None, *args: Any, **kwargs: Any + ) -> BusABC: """ Takes the same arguments as :class:`can.BusABC.__init__`. Some might have a special meaning, see below. @@ -111,12 +115,16 @@ def __new__(cls, channel=None, *args, **kwargs): if channel is None: # Use the default channel for the backend - return cls(*args, **kwargs) + bus = cls(*args, **kwargs) else: - return cls(channel, *args, **kwargs) + bus = cls(channel, *args, **kwargs) + return cast(BusABC, bus) -def detect_available_configs(interfaces=None): + +def detect_available_configs( + interfaces: Union[None, str, Iterable[str]] = None +) -> List[AutoDetectedConfig]: """Detect all configurations/channels that the interfaces could currently connect with. diff --git a/can/io/blf.py b/can/io/blf.py index eb6c69210..515c375e2 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -19,9 +19,10 @@ import logging from typing import List -from can.message import Message -from can.listener import Listener -from can.util import len2dlc, dlc2len, channel2int +from ..message import Message +from ..listener import Listener +from ..util import len2dlc, dlc2len, channel2int +from ..typechecking import AcceptedIOType from .generic import BaseIOHandler @@ -342,8 +343,12 @@ class BLFWriter(BaseIOHandler, Listener): application_id = 5 def __init__( - self, file, append: bool = False, channel: int = 1, compression_level: int = -1 - ): + self, + file: AcceptedIOType, + append: bool = False, + channel: int = 1, + compression_level: int = -1, + ) -> None: """ :param file: a path-like object or as file-like object to write to If this is a file-like object, is has to opened in mode "wb+". diff --git a/can/io/generic.py b/can/io/generic.py index 2a45e0983..b3ac93a2a 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -3,7 +3,7 @@ """ from abc import ABCMeta -from typing import Optional, cast, Union, TextIO, BinaryIO +from typing import Any, Optional, cast, Union, TextIO, BinaryIO, Type import can import can.typechecking @@ -39,8 +39,9 @@ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> N def __enter__(self) -> "BaseIOHandler": return self - def __exit__(self, *args) -> None: + def __exit__(self, exc_type: Type, exc_val: Any, exc_tb: Any) -> Any: self.stop() + return False def stop(self) -> None: """Closes the undelying file-like object and flushes it, if it was opened in write mode.""" diff --git a/can/io/logger.py b/can/io/logger.py index b5282cfa6..91e3066b4 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -1,14 +1,14 @@ """ See the :class:`Logger` class. """ + import os import pathlib from abc import ABC, abstractmethod from datetime import datetime -from typing import Optional, Callable +from typing import Any, cast, Callable, Optional from pkg_resources import iter_entry_points -from can.typechecking import StringPathLike from ..message import Message from ..listener import Listener @@ -19,6 +19,7 @@ from .csv import CSVWriter from .sqlite import SqliteWriter from .printer import Printer +from ..typechecking import StringPathLike class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method @@ -53,7 +54,9 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method } @staticmethod - def __new__(cls, filename: Optional[StringPathLike], *args, **kwargs): + def __new__( # type: ignore + cls: Any, filename: Optional[StringPathLike], *args: Any, **kwargs: Any + ) -> Listener: """ :param filename: the filename/path of the file to write to, may be a path-like object or None to @@ -74,7 +77,9 @@ def __new__(cls, filename: Optional[StringPathLike], *args, **kwargs): suffix = pathlib.PurePath(filename).suffix.lower() try: - return Logger.message_writers[suffix](filename, *args, **kwargs) + return cast( + Listener, Logger.message_writers[suffix](filename, *args, **kwargs) + ) except KeyError: raise ValueError( f'No write support for this unknown log format "{suffix}"' @@ -116,12 +121,12 @@ class BaseRotatingLogger(Listener, ABC): ".log": CanutilsLogWriter, ".txt": Printer, } - namer: Optional[Callable] = None - rotator: Optional[Callable] = None + namer: Optional[Callable[[StringPathLike], StringPathLike]] = None + rotator: Optional[Callable[[StringPathLike, StringPathLike], None]] = None rollover_count: int = 0 _writer: Optional[FileIOMessageWriter] = None - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: self.writer_args = args self.writer_kwargs = kwargs @@ -132,7 +137,7 @@ def writer(self) -> FileIOMessageWriter: return self._writer - def rotation_filename(self, default_name: StringPathLike): + def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: """Modify the filename of a log file when rotating. This is provided so that a custom filename can be provided. @@ -145,12 +150,11 @@ def rotation_filename(self, default_name: StringPathLike): The default name for the log file. """ if not callable(self.namer): - result = default_name - else: - result = self.namer(default_name) - return result + return default_name + + return self.namer(default_name) - def rotate(self, source: StringPathLike, dest: StringPathLike): + def rotate(self, source: StringPathLike, dest: StringPathLike) -> None: """When rotating, rotate the current log. The default implementation calls the 'rotator' attribute of the @@ -171,7 +175,7 @@ def rotate(self, source: StringPathLike, dest: StringPathLike): else: self.rotator(source, dest) - def on_message_received(self, msg: Message): + def on_message_received(self, msg: Message) -> None: """This method is called to handle the given message. :param msg: @@ -183,7 +187,7 @@ def on_message_received(self, msg: Message): self.writer.on_message_received(msg) - def get_new_writer(self, filename: StringPathLike): + def get_new_writer(self, filename: StringPathLike) -> None: """Instantiate a new writer. :param filename: @@ -205,7 +209,7 @@ def get_new_writer(self, filename: StringPathLike): filename, *self.writer_args, **self.writer_kwargs ) - def stop(self): + def stop(self) -> None: """Stop handling new messages. Carry out any final tasks to ensure @@ -216,12 +220,10 @@ def stop(self): @abstractmethod def should_rollover(self, msg: Message) -> bool: """Determine if the rollover conditions are met.""" - ... @abstractmethod - def do_rollover(self): + def do_rollover(self) -> None: """Perform rollover.""" - ... class SizedRotatingLogger(BaseRotatingLogger): @@ -261,8 +263,12 @@ class SizedRotatingLogger(BaseRotatingLogger): """ def __init__( - self, base_filename: StringPathLike, max_bytes: int = 0, *args, **kwargs - ): + self, + base_filename: StringPathLike, + max_bytes: int = 0, + *args: Any, + **kwargs: Any, + ) -> None: """ :param base_filename: A path-like object for the base filename. The log file format is defined by @@ -287,7 +293,7 @@ def should_rollover(self, msg: Message) -> bool: return False - def do_rollover(self): + def do_rollover(self) -> None: if self.writer: self.writer.stop() diff --git a/can/io/player.py b/can/io/player.py index 21e716250..e39d52c5e 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -13,7 +13,7 @@ if typing.TYPE_CHECKING: import can -from .generic import BaseIOHandler +from .generic import BaseIOHandler, MessageReader from .asc import ASCReader from .blf import BLFReader from .canutils import CanutilsLogReader @@ -56,7 +56,12 @@ class LogReader(BaseIOHandler): } @staticmethod - def __new__(cls, filename: "can.typechecking.StringPathLike", *args, **kwargs): + def __new__( # type: ignore + cls: typing.Any, + filename: "can.typechecking.StringPathLike", + *args: typing.Any, + **kwargs: typing.Any, + ) -> MessageReader: """ :param filename: the filename/path of the file to read from :raises ValueError: if the filename's suffix is of an unknown file type @@ -72,7 +77,10 @@ def __new__(cls, filename: "can.typechecking.StringPathLike", *args, **kwargs): suffix = pathlib.PurePath(filename).suffix.lower() try: - return LogReader.message_readers[suffix](filename, *args, **kwargs) + return typing.cast( + MessageReader, + LogReader.message_readers[suffix](filename, *args, **kwargs), + ) except KeyError: raise ValueError( f'No read support for this unknown log format "{suffix}"' diff --git a/can/listener.py b/can/listener.py index 2695e80c5..41cfa4b5e 100644 --- a/can/listener.py +++ b/can/listener.py @@ -1,9 +1,10 @@ """ This module contains the implementation of `can.Listener` and some readers. """ + import sys import warnings -from typing import AsyncIterator, Awaitable, Optional +from typing import Any, AsyncIterator, Awaitable, Callable, Optional from can.message import Message from can.bus import BusABC @@ -37,23 +38,22 @@ class Listener(metaclass=ABCMeta): """ @abstractmethod - def on_message_received(self, msg: Message): + def on_message_received(self, msg: Message) -> None: """This method is called to handle the given message. :param msg: the delivered message - """ - def __call__(self, msg: Message): + def __call__(self, msg: Message) -> None: self.on_message_received(msg) - def on_error(self, exc: Exception): + def on_error(self, exc: Exception) -> None: """This method is called to handle any exception in the receive thread. :param exc: The exception causing the thread to stop """ - def stop(self): + def stop(self) -> None: """ Stop handling new messages, carry out any final tasks to ensure data is persisted and cleanup any open resources. @@ -68,10 +68,10 @@ class RedirectReader(Listener): """ - def __init__(self, bus: BusABC): + def __init__(self, bus: BusABC) -> None: self.bus = bus - def on_message_received(self, msg: Message): + def on_message_received(self, msg: Message) -> None: self.bus.send(msg) @@ -89,12 +89,12 @@ class BufferedReader(Listener): :attr bool is_stopped: ``True`` if the reader has been stopped """ - def __init__(self): + def __init__(self) -> None: # set to "infinite" size - self.buffer = SimpleQueue() + self.buffer: SimpleQueue[Message] = SimpleQueue() self.is_stopped = False - def on_message_received(self, msg: Message): + def on_message_received(self, msg: Message) -> None: """Append a message to the buffer. :raises: BufferError @@ -120,7 +120,7 @@ def get_message(self, timeout: float = 0.5) -> Optional[Message]: except Empty: return None - def stop(self): + def stop(self) -> None: """Prohibits any more additions to this reader.""" self.is_stopped = True @@ -136,7 +136,7 @@ class AsyncBufferedReader(Listener): print(msg) """ - def __init__(self, **kwargs): + def __init__(self, **kwargs: Any) -> None: self.buffer: "asyncio.Queue[Message]" if "loop" in kwargs.keys(): @@ -151,7 +151,7 @@ def __init__(self, **kwargs): self.buffer = asyncio.Queue() - def on_message_received(self, msg: Message): + def on_message_received(self, msg: Message) -> None: """Append a message to the buffer. Must only be called inside an event loop! diff --git a/can/notifier.py b/can/notifier.py index 2842cc130..58bb30a08 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -2,7 +2,7 @@ This module contains the implementation of :class:`~can.Notifier`. """ -from typing import Iterable, List, Optional, Union +from typing import Any, cast, Iterable, List, Optional, Union, Awaitable from can.bus import BusABC from can.listener import Listener @@ -23,7 +23,7 @@ def __init__( listeners: Iterable[Listener], timeout: float = 1.0, loop: Optional[asyncio.AbstractEventLoop] = None, - ): + ) -> None: """Manages the distribution of :class:`can.Message` instances to listeners. Supports multiple buses and listeners. @@ -39,7 +39,7 @@ def __init__( :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 = list(listeners) + self.listeners: List[Listener] = list(listeners) self.bus = bus self.timeout = timeout self._loop = loop @@ -55,7 +55,7 @@ def __init__( for bus in buses: self.add_bus(bus) - def add_bus(self, bus: BusABC): + def add_bus(self, bus: BusABC) -> None: """Add a bus for notification. :param bus: @@ -82,7 +82,7 @@ def add_bus(self, bus: BusABC): reader_thread.start() self._readers.append(reader_thread) - def stop(self, timeout: float = 5): + def stop(self, timeout: float = 5) -> None: """Stop notifying Listeners when new :class:`~can.Message` objects arrive and call :meth:`~can.Listener.stop` on each Listener. @@ -104,7 +104,7 @@ def stop(self, timeout: float = 5): if hasattr(listener, "stop"): listener.stop() - def _rx_thread(self, bus: BusABC): + def _rx_thread(self, bus: BusABC) -> None: msg = None try: while self._running: @@ -125,15 +125,15 @@ def _rx_thread(self, bus: BusABC): elif not self._on_error(exc): raise - def _on_message_available(self, bus: BusABC): + def _on_message_available(self, bus: BusABC) -> None: msg = bus.recv(0) if msg is not None: self._on_message_received(msg) - def _on_message_received(self, msg: Message): + def _on_message_received(self, msg: Message) -> None: for callback in self.listeners: - res = callback(msg) - if self._loop is not None and asyncio.iscoroutine(res): + res = cast(Optional[Awaitable[Any]], callback(msg)) + if res is not None and self._loop is not None and asyncio.iscoroutine(res): # Schedule coroutine self._loop.create_task(res) @@ -147,7 +147,7 @@ def _on_error(self, exc: Exception) -> bool: return bool(listeners_with_on_error) - def add_listener(self, listener: Listener): + def add_listener(self, listener: Listener) -> None: """Add new Listener to the notification list. If it is already present, it will be called two times each time a message arrives. @@ -156,7 +156,7 @@ def add_listener(self, listener: Listener): """ self.listeners.append(listener) - def remove_listener(self, listener: Listener): + def remove_listener(self, listener: Listener) -> None: """Remove a listener from the notification list. This method throws an exception if the given listener is not part of the stored listeners. diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index f36119751..8c81e4d4f 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -14,7 +14,7 @@ try: - from contextlib import nullcontext # type: ignore + from contextlib import nullcontext except ImportError: diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index b3fdefc09..d0d6a4a6a 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -10,7 +10,7 @@ import can -def producer(thread_id: int, message_count: int = 16): +def producer(thread_id: int, message_count: int = 16) -> None: """Spam the bus with messages including the data id. :param thread_id: the id of the thread/process From 63a4de212dcf47553d53d2a5ea9c6acaf1811d18 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Apr 2021 11:09:43 +0200 Subject: [PATCH 0621/1235] remove unised import --- can/listener.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/listener.py b/can/listener.py index 41cfa4b5e..d79a6bc73 100644 --- a/can/listener.py +++ b/can/listener.py @@ -4,7 +4,7 @@ import sys import warnings -from typing import Any, AsyncIterator, Awaitable, Callable, Optional +from typing import Any, AsyncIterator, Awaitable, Optional from can.message import Message from can.bus import BusABC From dc1216865e39b9ef40290c7771e633f05b1614d9 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 25 Apr 2021 09:26:12 +0200 Subject: [PATCH 0622/1235] Update can/notifier.py --- can/notifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/notifier.py b/can/notifier.py index 58bb30a08..b0ce8fb98 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -132,7 +132,7 @@ def _on_message_available(self, bus: BusABC) -> None: def _on_message_received(self, msg: Message) -> None: for callback in self.listeners: - res = cast(Optional[Awaitable[Any]], callback(msg)) + res = cast(Union[None, Optional[Awaitable[Any]]], callback(msg)) if res is not None and self._loop is not None and asyncio.iscoroutine(res): # Schedule coroutine self._loop.create_task(res) From ba1b9e926a4b13750d54bc9bf6986796ad6e562b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 09:39:56 +0200 Subject: [PATCH 0623/1235] rename to CanInterfaceNotImplementedError --- can/__init__.py | 2 +- can/exceptions.py | 19 ++++++++++++------- can/interface.py | 10 +++++----- can/util.py | 6 ++++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index d8bf17a5f..62fdf51fa 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -18,7 +18,7 @@ from .exceptions import ( CanError, - CanBackEndError, + CanInterfaceNotImplementedError, CanInitializationError, CanOperationError, CanTimeoutError, diff --git a/can/exceptions.py b/can/exceptions.py index 55c4b0d82..49ec19544 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -6,13 +6,13 @@ +-- ... +-- CanError (python-can) +-- CanBackendError - +-- CanInitializationError + +-- CanInterfaceNotImplementedError +-- CanOperationError +-- CanTimeoutError Keep in mind that some functions and methods may raise different exceptions. For example, validating typical arguments and parameters might result in a -:class:`ValueError`. This should be documented for the function at hand. +:class:`ValueError`. This should always be documented for the function at hand. """ from typing import Optional @@ -49,19 +49,24 @@ def __init__( ) -class CanBackEndError(CanError): - """Indicates an error related to the backend (e.g. driver/OS/library). +class CanInterfaceNotImplementedError(CanError, NotImplementedError): + """Indicates that the interface is not supported on the current platform. Example scenarios: - - The interface does not exist - - The interface is unsupported on the current platform - - The driver is not present or has the wrong version + - No interface with that name exists + - The interface is unsupported on the current operating system or interpreter + - The driver could not be found or has the wrong version """ class CanInitializationError(CanError): """Indicates an error the occurred while initializing a :class:`can.BusABC`. + If initialization fails due to a driver or platform missing/being unsupported, + a :class:`can.CanInterfaceNotImplementedError` is raised instead. + If initialization fails due to a value being out of range, a :class:`ValueError` + is raised. + Example scenarios: - Try to open a non-existent device and/or channel - Try to use an invalid setting, which is ok by value, but not ok for the interface diff --git a/can/interface.py b/can/interface.py index 5a4d7caa7..7026b37e2 100644 --- a/can/interface.py +++ b/can/interface.py @@ -10,7 +10,7 @@ from .bus import BusABC from .util import load_config from .interfaces import BACKENDS -from .exceptions import CanBackEndError +from .exceptions import CanInterfaceNotImplementedError log = logging.getLogger("can.interface") log_autodetect = log.getChild("detect_available_configs") @@ -38,7 +38,7 @@ def _get_class_for_interface(interface): try: module = importlib.import_module(module_name) except Exception as e: - raise CanBackEndError( + raise CanInterfaceNotImplementedError( "Cannot import module {} for CAN interface '{}': {}".format( module_name, interface, e ) @@ -48,7 +48,7 @@ def _get_class_for_interface(interface): try: bus_class = getattr(module, class_name) except Exception as e: - raise CanBackEndError( + raise CanInterfaceNotImplementedError( "Cannot import class {} from module {} for CAN interface '{}': {}".format( class_name, module_name, interface, e ) @@ -152,9 +152,9 @@ def detect_available_configs(interfaces=None): try: bus_class = _get_class_for_interface(interface) - except CanBackEndError: + except CanInterfaceNotImplementedError: log_autodetect.debug( - 'interface "%s" can not be loaded for detection of available configurations', + 'interface "%s" cannot be loaded for detection of available configurations', interface, ) continue diff --git a/can/util.py b/can/util.py index 56b1ca22a..c2b71cf4a 100644 --- a/can/util.py +++ b/can/util.py @@ -17,7 +17,7 @@ import can from .interfaces import VALID_INTERFACES from . import typechecking -from .exceptions import CanBackEndError +from .exceptions import CanInterfaceNotImplementedError log = logging.getLogger("can.util") @@ -191,7 +191,9 @@ def load_config( config[key] = None if config["interface"] not in VALID_INTERFACES: - raise CanBackEndError(f'Invalid CAN Bus Type "{config["interface"]}"') + raise CanInterfaceNotImplementedError( + f'Unknown interface type "{config["interface"]}"' + ) if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) From 3657e492b321cf14d6b506655d4cd316486ecadc Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 09:43:00 +0200 Subject: [PATCH 0624/1235] simplify config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 781219e25..f29c0bc41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,7 +73,7 @@ jobs: # ------------- # pylint checking: # check the entire main codebase (except the tests) - - pylint --rcfile=.pylintrc can/**.py setup.py doc.conf scripts/**.py examples/**.py + - pylint can/**.py setup.py doc.conf scripts/**.py examples/**.py # ------------- # mypy checking: - mypy From c55b6998f718b4ea3a88fc594d1f2b48afbe1ff2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 09:43:41 +0200 Subject: [PATCH 0625/1235] speed up plyint --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index a5a3d2ae3..2ec82d2d2 100644 --- a/.pylintrc +++ b/.pylintrc @@ -19,7 +19,7 @@ ignore-patterns= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use. -jobs=1 +jobs=0 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or From a5b250edb46ee71948094dab0e78ee6e82bd980e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 09:46:30 +0200 Subject: [PATCH 0626/1235] remove 'abstract-method' --- .pylintrc | 1 - can/broadcastmanager.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index 2ec82d2d2..fe699f804 100644 --- a/.pylintrc +++ b/.pylintrc @@ -78,7 +78,6 @@ disable=invalid-name, wildcard-import, fixme, no-else-return, - abstract-method, abstract-class-instantiated, cyclic-import, duplicate-code, diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index b884a758d..ed8161e91 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -30,7 +30,7 @@ log = logging.getLogger("can.bcm") -class CyclicTask: +class CyclicTask(abc.ABC): """ Abstract Base for all cyclic tasks. """ From e174c138a23e18a990907b2266afc8e193126304 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 10:11:45 +0200 Subject: [PATCH 0627/1235] improve code --- can/message.py | 18 +++++++----------- can/util.py | 22 ++++++++++++++++------ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/can/message.py b/can/message.py index 0d3798f15..3203f822e 100644 --- a/can/message.py +++ b/can/message.py @@ -6,7 +6,7 @@ starting with Python 3.7. """ -from typing import Optional, Union +from typing import Optional from . import typechecking @@ -48,7 +48,7 @@ class Message: "__weakref__", # support weak references to messages ) - def __init__( # pylint: disable=too-many-locals + def __init__( # pylint: disable=too-many-locals, too-many-arguments self, timestamp: float = 0.0, arbitration_id: int = 0, @@ -230,11 +230,11 @@ def __deepcopy__(self, memo: dict) -> "Message": ) return new - def _check(self): + def _check(self): # pylint: disable=too-many-branches; it's still simple code """Checks if the message parameters are valid. Assumes that the types are already correct. - :raises ValueError: iff one or more attributes are invalid + :raises ValueError: if and only if one or more attributes are invalid """ if self.timestamp < 0.0: @@ -263,15 +263,11 @@ def _check(self): if self.is_fd: if self.dlc > 64: raise ValueError( - "DLC was {} but it should be <= 64 for CAN FD frames".format( - self.dlc - ) + f"DLC was {self.dlc} but it should be <= 64 for CAN FD frames" ) elif self.dlc > 8: raise ValueError( - "DLC was {} but it should be <= 8 for normal CAN frames".format( - self.dlc - ) + f"DLC was {self.dlc} but it should be <= 8 for normal CAN frames" ) if self.is_remote_frame: @@ -293,7 +289,7 @@ def _check(self): def equals( self, other: "Message", - timestamp_delta: Optional[Union[float, int]] = 1.0e-6, + timestamp_delta: Optional[float] = 1.0e-6, check_direction: bool = True, ) -> bool: """ diff --git a/can/util.py b/can/util.py index 07fa1986a..27fd80b60 100644 --- a/can/util.py +++ b/can/util.py @@ -147,15 +147,14 @@ def load_config( All unused values are passed from ``config`` over to this. - :raises: - NotImplementedError if the ``interface`` isn't recognized + :raises NotImplementedError: if the ``interface`` name is unknown """ - # start with an empty dict to apply filtering to all sources + # Start with an empty dict to apply filtering to all sources given_config = config or {} config = {} - # use the given dict for default values + # Use the given dict for default values config_sources = cast( Iterable[Union[Dict[str, Any], Callable[[Any], Dict[str, Any]]]], [ @@ -184,6 +183,19 @@ def load_config( if key not in config: config[key] = cfg[key] + bus_config = _create_bus_config(config) + can.log.debug("can config: %s", bus_config) + return bus_config + + +def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: + """Validates some config values, performs compatibility mappings and creates specific + structures (e.g. for bit timings). + + :param config: The raw config as specified by the user + :return: A config that can be used by a :class:`~can.BusABC` + :raises NotImplementedError: if the ``interface`` is unknown + """ # substitute None for all values not found for key in REQUIRED_KEYS: if key not in config: @@ -218,8 +230,6 @@ def load_config( timing_conf["bitrate"] = config["bitrate"] config["timing"] = can.BitTiming(**timing_conf) - can.log.debug("can config: %s", config) - return cast(typechecking.BusConfig, config) From cbdd5642423836691f3138d31b549314279ba68d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 10:12:00 +0200 Subject: [PATCH 0628/1235] icnlude pylint file again --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f29c0bc41..781219e25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,7 +73,7 @@ jobs: # ------------- # pylint checking: # check the entire main codebase (except the tests) - - pylint can/**.py setup.py doc.conf scripts/**.py examples/**.py + - pylint --rcfile=.pylintrc can/**.py setup.py doc.conf scripts/**.py examples/**.py # ------------- # mypy checking: - mypy From 51a854cadd53d020465a1c0bd15c71aaa7d8c4eb Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 10:12:13 +0200 Subject: [PATCH 0629/1235] cleanup viewer.py --- can/viewer.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/can/viewer.py b/can/viewer.py index 6824767ff..d450576e9 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -142,8 +142,8 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod def unpack_data( - cmd, cmd_to_struct, data - ): # type: (int, Dict, bytes) -> List[Union[float, int]] + cmd: int, cmd_to_struct: Dict, data: bytes + ) -> List[float]: if not cmd_to_struct or not data: # These messages do not contain a data package return [] @@ -259,7 +259,7 @@ def draw_can_bus_message(self, msg, sorting=False): def draw_line(self, row, col, txt, *args): if row - self.scroll < 0: - # Skip if we have scrolled passed the line + # Skip if we have scrolled past the line return try: self.stdscr.addstr(row - self.scroll, col, txt, *args) @@ -286,7 +286,6 @@ def redraw_screen(self): self.draw_can_bus_message(self.ids[key]["msg"]) -# noinspection PyProtectedMember class SmartFormatter(argparse.HelpFormatter): def _get_default_metavar_for_optional(self, action): return action.dest.upper() @@ -327,18 +326,12 @@ def _split_lines(self, text, width): def _fill_text(self, text, width, indent): if text.startswith("R|"): - # noinspection PyTypeChecker return "".join(indent + line + "\n" for line in text[2:].splitlines()) else: return super()._fill_text(text, width, indent) def parse_args(args): - # Python versions >= 3.5 - kwargs = {} - if sys.version_info[0] * 10 + sys.version_info[1] >= 35: # pragma: no cover - kwargs = {"allow_abbrev": False} - # Parse command line arguments parser = argparse.ArgumentParser( "python -m can.viewer", @@ -355,7 +348,7 @@ def parse_args(args): "\n +---------+-------------------------+", formatter_class=SmartFormatter, add_help=False, - **kwargs + allow_abbrev=False, ) optional = parser.add_argument_group("Optional arguments") @@ -371,7 +364,7 @@ def parse_args(args): version="%(prog)s (version {version})".format(version=__version__), ) - # Copied from: https://github.com/hardbyte/python-can/blob/develop/can/logger.py + # Copied from: can/logger.py optional.add_argument( "-b", "--bitrate", @@ -469,9 +462,7 @@ def parse_args(args): can_filters = [] if parsed_args.filter: - # print('Adding filter/s', parsed_args.filter) for flt in parsed_args.filter: - # print(filter) if ":" in flt: _ = flt.split(":") can_id, can_mask = int(_[0], base=16), int(_[1], base=16) @@ -502,9 +493,7 @@ def parse_args(args): # In order to convert from raw integer value the real units are multiplied with the values and # similarly the values # are divided by the value in order to convert from real units to raw integer values. - data_structs = ( - {} - ) # type: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] + data_structs: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] = {} if parsed_args.decode: if os.path.isfile(parsed_args.decode[0]): with open(parsed_args.decode[0], "r") as f: @@ -519,7 +508,7 @@ def parse_args(args): key, fmt = int(tmp[0], base=16), tmp[1] # The scaling - scaling = [] # type: list + scaling: List[float] = [] for t in tmp[2:]: # First try to convert to int, if that fails, then convert to a float try: @@ -531,12 +520,11 @@ def parse_args(args): data_structs[key] = (struct.Struct(fmt),) + tuple(scaling) else: data_structs[key] = struct.Struct(fmt) - # print(data_structs[key]) return parsed_args, can_filters, data_structs -def main(): # pragma: no cover +def main(): parsed_args, can_filters, data_structs = parse_args(sys.argv[1:]) config = {"single_handle": True} From 47dee13bab94ebf6da91663390683891c4dce7d7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 10:27:04 +0200 Subject: [PATCH 0630/1235] imporve code --- .pylintrc | 12 +++--------- can/message.py | 2 +- can/viewer.py | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.pylintrc b/.pylintrc index fe699f804..d11bde862 100644 --- a/.pylintrc +++ b/.pylintrc @@ -63,24 +63,18 @@ confidence= disable=invalid-name, missing-docstring, empty-docstring, - too-many-lines, wrong-import-order, - ungrouped-imports, wrong-import-position, - too-many-instance-attributes, too-few-public-methods, too-many-public-methods, too-many-branches, - too-many-arguments, too-many-locals, too-many-statements, no-else-raise, wildcard-import, fixme, no-else-return, - abstract-class-instantiated, - cyclic-import, - duplicate-code, + abstract-class-instantiated, # Needed for can.Bus # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -474,10 +468,10 @@ valid-metaclass-classmethod-first-arg=cls [DESIGN] # Maximum number of arguments for function / method. -max-args=5 +max-args=10 # Maximum number of attributes for a class (see R0902). -max-attributes=7 +max-attributes=10 # Maximum number of boolean expressions in an if statement. max-bool-expr=5 diff --git a/can/message.py b/can/message.py index 3203f822e..d1fb8414a 100644 --- a/can/message.py +++ b/can/message.py @@ -14,7 +14,7 @@ from math import isinf, isnan -class Message: +class Message: # pylint: disable=too-many-instance-attributes; OK for a dataclass """ The :class:`~can.Message` object is used to represent CAN messages for sending, receiving and other purposes like converting between different diff --git a/can/viewer.py b/can/viewer.py index d450576e9..aface9d00 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -524,7 +524,7 @@ def parse_args(args): return parsed_args, can_filters, data_structs -def main(): +def main() -> None: parsed_args, can_filters, data_structs = parse_args(sys.argv[1:]) config = {"single_handle": True} From 30202b7156c9778ef45210a23cb435f77e0e587c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 10:27:25 +0200 Subject: [PATCH 0631/1235] factor out some common parts --- can/logger.py | 72 ++++++++++++++++++++++++++++----------------------- can/player.py | 42 +++++++----------------------- 2 files changed, 48 insertions(+), 66 deletions(-) diff --git a/can/logger.py b/can/logger.py index 72977e143..27c68b679 100644 --- a/can/logger.py +++ b/can/logger.py @@ -24,17 +24,52 @@ from can import Bus, BusState, Logger, SizedRotatingLogger -def main(): +def _create_base_argument_parser(parser: argparse.ArgumentParser): + """Adds common options to an argument parser.""" + + parser.add_argument( + "-c", + "--channel", + help='''Most backend interfaces require some sort of channel. + For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" + With the socketcan interfaces valid channel examples include: "can0", "vcan0"''', + ) + + parser.add_argument( + "-i", + "--interface", + dest="interface", + help="""Specify the backend CAN interface to use. If left blank, + fall back to reading from configuration files.""", + choices=can.VALID_INTERFACES, + ) + + parser.add_argument( + "-b", "--bitrate", type=int, help="Bitrate to use for the CAN bus." + ) + + parser.add_argument("--fd", help="Activate CAN-FD support", action="store_true") + + parser.add_argument( + "--data_bitrate", + type=int, + help="Bitrate to use for the data phase in case of CAN-FD.", + ) + + +def main() -> None: parser = argparse.ArgumentParser( "python -m can.logger", description="Log CAN traffic, printing messages to stdout or to a given file.", ) + _create_base_argument_parser(parser) + parser.add_argument( "-f", "--file_name", dest="log_file", - help="""Path and base log filename, for supported types see can.Logger.""", + help="Path and base log filename, for supported types see can.Logger.", default=None, ) @@ -43,7 +78,7 @@ def main(): "--file_size", dest="file_size", type=int, - help="""Maximum file size in bytes. Rotate log file when size threshold is reached.""", + help="Maximum file size in bytes. Rotate log file when size threshold is reached.", default=None, ) @@ -56,23 +91,6 @@ def main(): default=2, ) - parser.add_argument( - "-c", - "--channel", - help='''Most backend interfaces require some sort of channel. - For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" - With the socketcan interfaces valid channel examples include: "can0", "vcan0"''', - ) - - parser.add_argument( - "-i", - "--interface", - dest="interface", - help="""Specify the backend CAN interface to use. If left blank, - fall back to reading from configuration files.""", - choices=can.VALID_INTERFACES, - ) - parser.add_argument( "--filter", help="""Comma separated filters can be specified for the given CAN interface: @@ -83,18 +101,6 @@ def main(): default="", ) - parser.add_argument( - "-b", "--bitrate", type=int, help="""Bitrate to use for the CAN bus.""" - ) - - parser.add_argument("--fd", help="Activate CAN-FD support", action="store_true") - - parser.add_argument( - "--data_bitrate", - type=int, - help="""Bitrate to use for the data phase in case of CAN-FD.""", - ) - state_group = parser.add_mutually_exclusive_group(required=False) state_group.add_argument( "--active", @@ -105,7 +111,7 @@ def main(): "--passive", help="Start the bus as passive.", action="store_true" ) - # print help message when no arguments wre given + # print help message when no arguments were given if len(sys.argv) < 2: parser.print_help(sys.stderr) raise SystemExit(errno.EINVAL) diff --git a/can/player.py b/can/player.py index ebd03f2f7..939116f5e 100644 --- a/can/player.py +++ b/can/player.py @@ -14,16 +14,21 @@ from can import Bus, LogReader, MessageSync -def main(): +from .logger import _create_base_argument_parser + + +def main() -> None: parser = argparse.ArgumentParser( "python -m can.player", description="Replay CAN traffic." ) + _create_base_argument_parser(parser) + parser.add_argument( "-f", "--file_name", dest="log_file", - help="""Path and base log filename, for supported types see can.LogReader.""", + help="Path and base log filename, for supported types see can.LogReader.", default=None, ) @@ -36,35 +41,6 @@ def main(): default=2, ) - parser.add_argument( - "-c", - "--channel", - help='''Most backend interfaces require some sort of channel. - For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" - With the socketcan interfaces valid channel examples include: "can0", "vcan0"''', - ) - - parser.add_argument( - "-i", - "--interface", - dest="interface", - help="""Specify the backend CAN interface to use. If left blank, - fall back to reading from configuration files.""", - choices=can.VALID_INTERFACES, - ) - - parser.add_argument( - "-b", "--bitrate", type=int, help="""Bitrate to use for the CAN bus.""" - ) - - parser.add_argument("--fd", help="Activate CAN-FD support", action="store_true") - - parser.add_argument( - "--data_bitrate", - type=int, - help="""Bitrate to use for the data phase in case of CAN-FD.""", - ) - parser.add_argument( "--ignore-timestamps", dest="timestamps", @@ -82,7 +58,7 @@ def main(): "-g", "--gap", type=float, - help=""" minimum time between replayed frames""", + help=" minimum time between replayed frames", default=0.0001, ) parser.add_argument( @@ -90,7 +66,7 @@ def main(): "--skip", type=float, default=60 * 60 * 24, - help=""" skip gaps greater than 's' seconds""", + help=" skip gaps greater than 's' seconds", ) parser.add_argument( From ed9ed3f298178b874270ac87f7de34b127bee98d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 10:40:13 +0200 Subject: [PATCH 0632/1235] factor out common code --- can/logger.py | 38 ++++++++++++++++++++------------------ can/player.py | 21 +++------------------ can/viewer.py | 2 +- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/can/logger.py b/can/logger.py index 27c68b679..fa8016a94 100644 --- a/can/logger.py +++ b/can/logger.py @@ -19,6 +19,7 @@ import socket from datetime import datetime import errno +from typing import Any, Dict import can from can import Bus, BusState, Logger, SizedRotatingLogger @@ -57,11 +58,28 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser): ) +def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus: + logging_level_names = ["critical", "error", "warning", "info", "debug", "subdebug"] + can.set_logging_level(logging_level_names[min(5, parsed_args.verbosity)]) + + config: Dict[str, Any] = {"single_handle": True, **kwargs} + if parsed_args.interface: + config["interface"] = parsed_args.interface + if parsed_args.bitrate: + config["bitrate"] = parsed_args.bitrate + if parsed_args.fd: + config["fd"] = True + if parsed_args.data_bitrate: + config["data_bitrate"] = parsed_args.data_bitrate + + return Bus(parsed_args.channel, **config) + + def main() -> None: parser = argparse.ArgumentParser( "python -m can.logger", description="Log CAN traffic, printing messages to stdout or to a given file.", - ) + ) # pylint: disable=R0801; the following lines are not duplicates _create_base_argument_parser(parser) @@ -118,13 +136,6 @@ def main() -> None: results = parser.parse_args() - verbosity = results.verbosity - - logging_level_name = ["critical", "error", "warning", "info", "debug", "subdebug"][ - min(5, verbosity) - ] - can.set_logging_level(logging_level_name) - can_filters = [] if results.filter: print(f"Adding filter(s): {results.filter}") @@ -138,16 +149,7 @@ def main() -> None: can_mask = int(can_mask, base=16) & socket.CAN_ERR_FLAG can_filters.append({"can_id": can_id, "can_mask": can_mask}) - config = {"can_filters": can_filters, "single_handle": True} - if results.interface: - config["interface"] = results.interface - if results.bitrate: - config["bitrate"] = results.bitrate - if results.fd: - config["fd"] = True - if results.data_bitrate: - config["data_bitrate"] = results.data_bitrate - bus = Bus(results.channel, **config) + bus = _create_bus(results, can_filters=can_filters) if results.active: bus.state = BusState.ACTIVE diff --git a/can/player.py b/can/player.py index 939116f5e..dd1a07fec 100644 --- a/can/player.py +++ b/can/player.py @@ -10,11 +10,10 @@ from datetime import datetime import errno -import can -from can import Bus, LogReader, MessageSync +from can import LogReader, MessageSync -from .logger import _create_base_argument_parser +from .logger import _create_base_argument_parser, _create_bus def main() -> None: @@ -85,23 +84,9 @@ def main() -> None: verbosity = results.verbosity - logging_level_name = ["critical", "error", "warning", "info", "debug", "subdebug"][ - min(5, verbosity) - ] - can.set_logging_level(logging_level_name) - error_frames = results.error_frames - config = {"single_handle": True} - if results.interface: - config["interface"] = results.interface - if results.bitrate: - config["bitrate"] = results.bitrate - if results.fd: - config["fd"] = True - if results.data_bitrate: - config["data_bitrate"] = results.data_bitrate - bus = Bus(results.channel, **config) + bus = _create_bus(results) reader = LogReader(results.infile) diff --git a/can/viewer.py b/can/viewer.py index aface9d00..5c717c0ee 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -377,7 +377,7 @@ def parse_args(args): optional.add_argument( "--data_bitrate", type=int, - help="""Bitrate to use for the data phase in case of CAN-FD.""", + help="Bitrate to use for the data phase in case of CAN-FD.", ) optional.add_argument( From 49a42b695723643e0cf5a9df2751118460523583 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 11:11:59 +0200 Subject: [PATCH 0633/1235] factor out _parse_filters; fix tpying --- .pylintrc | 3 ++- can/logger.py | 47 ++++++++++++++++++++++++++++------------------- can/player.py | 5 +++-- can/viewer.py | 43 +++++++++++++++---------------------------- 4 files changed, 48 insertions(+), 50 deletions(-) diff --git a/.pylintrc b/.pylintrc index d11bde862..8144d5d8f 100644 --- a/.pylintrc +++ b/.pylintrc @@ -72,9 +72,10 @@ disable=invalid-name, too-many-statements, no-else-raise, wildcard-import, - fixme, no-else-return, + fixme, # We deliberately use TODO/FIXME inline abstract-class-instantiated, # Needed for can.Bus + duplicate-code, # Needed due to https://github.com/PyCQA/pylint/issues/214 # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/can/logger.py b/can/logger.py index fa8016a94..b9fcae587 100644 --- a/can/logger.py +++ b/can/logger.py @@ -19,10 +19,11 @@ import socket from datetime import datetime import errno -from typing import Any, Dict +from typing import Any, Dict, List import can -from can import Bus, BusState, Logger, SizedRotatingLogger +from . import Bus, BusState, Logger, SizedRotatingLogger +from .typechecking import CanFilter, CanFilters def _create_base_argument_parser(parser: argparse.ArgumentParser): @@ -72,14 +73,35 @@ def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus: if parsed_args.data_bitrate: config["data_bitrate"] = parsed_args.data_bitrate - return Bus(parsed_args.channel, **config) + return Bus(parsed_args.channel, **config) # type: ignore + + +def _parse_filters(parsed_args: Any) -> CanFilters: + can_filters: List[CanFilter] = [] + + if parsed_args.filter: + print(f"Adding filter(s): {parsed_args.filter}") + for filt in parsed_args.filter: + if ":" in filt: + parts = filt.split(":") + can_id = int(parts[0], base=16) + can_mask = int(parts[1], base=16) + elif "~" in filt: + parts = filt.split("~") + can_id = int(parts[0], base=16) | 0x20000000 # CAN_INV_FILTER + can_mask = int(parts[1], base=16) & socket.CAN_ERR_FLAG + else: + raise argparse.ArgumentError(None, "Invalid filter argument") + can_filters.append({"can_id": can_id, "can_mask": can_mask}) + + return can_filters def main() -> None: parser = argparse.ArgumentParser( "python -m can.logger", description="Log CAN traffic, printing messages to stdout or to a given file.", - ) # pylint: disable=R0801; the following lines are not duplicates + ) _create_base_argument_parser(parser) @@ -136,20 +158,7 @@ def main() -> None: results = parser.parse_args() - can_filters = [] - if results.filter: - print(f"Adding filter(s): {results.filter}") - for filt in results.filter: - if ":" in filt: - _ = filt.split(":") - can_id, can_mask = int(_[0], base=16), int(_[1], base=16) - elif "~" in filt: - can_id, can_mask = filt.split("~") - can_id = int(can_id, base=16) | 0x20000000 # CAN_INV_FILTER - can_mask = int(can_mask, base=16) & socket.CAN_ERR_FLAG - can_filters.append({"can_id": can_id, "can_mask": can_mask}) - - bus = _create_bus(results, can_filters=can_filters) + bus = _create_bus(results, can_filters=_parse_filters(results)) if results.active: bus.state = BusState.ACTIVE @@ -164,7 +173,7 @@ def main() -> None: base_filename=results.log_file, max_bytes=results.file_size ) else: - logger = Logger(filename=results.log_file) + logger = Logger(filename=results.log_file) # type: ignore try: while True: diff --git a/can/player.py b/can/player.py index dd1a07fec..db0a6b0e0 100644 --- a/can/player.py +++ b/can/player.py @@ -9,8 +9,9 @@ import argparse from datetime import datetime import errno +from typing import cast, Iterable -from can import LogReader, MessageSync +from can import LogReader, Message, MessageSync from .logger import _create_base_argument_parser, _create_bus @@ -91,7 +92,7 @@ def main() -> None: reader = LogReader(results.infile) in_sync = MessageSync( - reader, timestamps=results.timestamps, gap=results.gap, skip=results.skip + cast(Iterable[Message], reader), timestamps=results.timestamps, gap=results.gap, skip=results.skip ) print(f"Can LogReader (Started on {datetime.now()})") diff --git a/can/viewer.py b/can/viewer.py index 5c717c0ee..c57b47ca0 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -31,6 +31,8 @@ import can from can import __version__ +from .logger import _create_bus, _parse_filters + logger = logging.getLogger("can.serial") @@ -453,6 +455,15 @@ def parse_args(args): choices=sorted(can.VALID_INTERFACES), ) + parser.add_argument( + "-v", + action="count", + dest="verbosity", + help="""How much information do you want to see at the command line? + You can add several of these e.g., -vv is DEBUG""", + default=2, + ) + # Print help message when no arguments are given if not args: parser.print_help(sys.stderr) @@ -460,19 +471,7 @@ def parse_args(args): parsed_args = parser.parse_args(args) - can_filters = [] - if parsed_args.filter: - for flt in parsed_args.filter: - if ":" in flt: - _ = flt.split(":") - can_id, can_mask = int(_[0], base=16), int(_[1], base=16) - elif "~" in flt: - can_id, can_mask = flt.split("~") - can_id = int(can_id, base=16) | 0x20000000 # CAN_INV_FILTER - can_mask = int(can_mask, base=16) & 0x20000000 # socket.CAN_ERR_FLAG - else: - raise argparse.ArgumentError(None, "Invalid filter argument") - can_filters.append({"can_id": can_id, "can_mask": can_mask}) + can_filters = _parse_filters(parse_args) # Dictionary used to convert between Python values and C structs represented as Python strings. # If the value is 'None' then the message does not contain any data package. @@ -527,21 +526,9 @@ def parse_args(args): def main() -> None: parsed_args, can_filters, data_structs = parse_args(sys.argv[1:]) - config = {"single_handle": True} - if can_filters: - config["can_filters"] = can_filters - if parsed_args.interface: - config["interface"] = parsed_args.interface - if parsed_args.bitrate: - config["bitrate"] = parsed_args.bitrate - if parsed_args.fd: - config["fd"] = True - if parsed_args.data_bitrate: - config["data_bitrate"] = parsed_args.data_bitrate - - # Create a CAN-Bus interface - bus = can.Bus(parsed_args.channel, **config) - # print('Connected to {}: {}'.format(bus.__class__.__name__, bus.channel_info)) + additional_config = {"can_filters": can_filters} if can_filters else {} + bus = _create_bus(parsed_args.channel, **additional_config) + # print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") curses.wrapper(CanViewer, bus, data_structs) From e0c60d7227701a8f3f605132dd963105dff6dcc9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 25 Apr 2021 09:12:47 +0000 Subject: [PATCH 0634/1235] Format code with black --- can/player.py | 5 ++++- can/viewer.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/can/player.py b/can/player.py index db0a6b0e0..a703739f7 100644 --- a/can/player.py +++ b/can/player.py @@ -92,7 +92,10 @@ def main() -> None: reader = LogReader(results.infile) in_sync = MessageSync( - cast(Iterable[Message], reader), timestamps=results.timestamps, gap=results.gap, skip=results.skip + cast(Iterable[Message], reader), + timestamps=results.timestamps, + gap=results.gap, + skip=results.skip, ) print(f"Can LogReader (Started on {datetime.now()})") diff --git a/can/viewer.py b/can/viewer.py index c57b47ca0..e6b823be7 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -143,9 +143,7 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod - def unpack_data( - cmd: int, cmd_to_struct: Dict, data: bytes - ) -> List[float]: + def unpack_data(cmd: int, cmd_to_struct: Dict, data: bytes) -> List[float]: if not cmd_to_struct or not data: # These messages do not contain a data package return [] @@ -492,7 +490,9 @@ def parse_args(args): # In order to convert from raw integer value the real units are multiplied with the values and # similarly the values # are divided by the value in order to convert from real units to raw integer values. - data_structs: Dict[Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None]] = {} + data_structs: Dict[ + Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None] + ] = {} if parsed_args.decode: if os.path.isfile(parsed_args.decode[0]): with open(parsed_args.decode[0], "r") as f: From c2ab1087cdb44cf255bbb672da2324eade705424 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:04:36 +0200 Subject: [PATCH 0635/1235] factor out filter argument, fix bug --- can/logger.py | 32 +++++++++++++++++++++----------- can/viewer.py | 18 +++--------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/can/logger.py b/can/logger.py index b9fcae587..90db066fc 100644 --- a/can/logger.py +++ b/can/logger.py @@ -19,14 +19,14 @@ import socket from datetime import datetime import errno -from typing import Any, Dict, List +from typing import Any, Dict, List, Union import can from . import Bus, BusState, Logger, SizedRotatingLogger from .typechecking import CanFilter, CanFilters -def _create_base_argument_parser(parser: argparse.ArgumentParser): +def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: """Adds common options to an argument parser.""" parser.add_argument( @@ -59,6 +59,24 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser): ) +def _append_filter_argument(parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup]) -> None: + """Adds the ``filter`` option to an argument parser.""" + + parser.add_argument( + "-f", + "--filter", + help="R|Space separated CAN filters for the given CAN interface:" + "\n : (matches when & mask == can_id & mask)" + "\n ~ (matches when & mask != can_id & mask)" + "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" + "\n python -m can.viewer -f 100:7FC 200:7F0" + "\nNote that the ID and mask are alway interpreted as hex values", + metavar="{:,~}", + nargs=argparse.ONE_OR_MORE, + default="", + ) + + def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus: logging_level_names = ["critical", "error", "warning", "info", "debug", "subdebug"] can.set_logging_level(logging_level_names[min(5, parsed_args.verbosity)]) @@ -131,15 +149,7 @@ def main() -> None: default=2, ) - parser.add_argument( - "--filter", - help="""Comma separated filters can be specified for the given CAN interface: - : (matches when & mask == can_id & mask) - ~ (matches when & mask != can_id & mask) - """, - nargs=argparse.REMAINDER, - default="", - ) + _append_filter_argument(parser) state_group = parser.add_mutually_exclusive_group(required=False) state_group.add_argument( diff --git a/can/viewer.py b/can/viewer.py index c57b47ca0..0c31f0127 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -31,7 +31,7 @@ import can from can import __version__ -from .logger import _create_bus, _parse_filters +from .logger import _create_bus, _parse_filters, _append_filter_argument logger = logging.getLogger("can.serial") @@ -433,19 +433,7 @@ def parse_args(args): default="", ) - optional.add_argument( - "-f", - "--filter", - help="R|Space separated CAN filters for the given CAN interface:" - "\n : (matches when & mask == can_id & mask)" - "\n ~ (matches when & mask != can_id & mask)" - "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" - "\n python -m can.viewer -f 100:7FC 200:7F0" - "\nNote that the ID and mask are alway interpreted as hex values", - metavar="{:,~}", - nargs=argparse.ONE_OR_MORE, - default="", - ) + _append_filter_argument(optional) optional.add_argument( "-i", @@ -471,7 +459,7 @@ def parse_args(args): parsed_args = parser.parse_args(args) - can_filters = _parse_filters(parse_args) + can_filters = _parse_filters(parsed_args) # Dictionary used to convert between Python values and C structs represented as Python strings. # If the value is 'None' then the message does not contain any data package. From 649c8f9f49adcb2368e4f51ef1d646e98aa46407 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 08:05:43 +0000 Subject: [PATCH 0636/1235] Format code with black --- can/logger.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/can/logger.py b/can/logger.py index 90db066fc..5df70c9e0 100644 --- a/can/logger.py +++ b/can/logger.py @@ -59,18 +59,20 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: ) -def _append_filter_argument(parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup]) -> None: +def _append_filter_argument( + parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup] +) -> None: """Adds the ``filter`` option to an argument parser.""" parser.add_argument( "-f", "--filter", help="R|Space separated CAN filters for the given CAN interface:" - "\n : (matches when & mask == can_id & mask)" - "\n ~ (matches when & mask != can_id & mask)" - "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" - "\n python -m can.viewer -f 100:7FC 200:7F0" - "\nNote that the ID and mask are alway interpreted as hex values", + "\n : (matches when & mask == can_id & mask)" + "\n ~ (matches when & mask != can_id & mask)" + "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" + "\n python -m can.viewer -f 100:7FC 200:7F0" + "\nNote that the ID and mask are alway interpreted as hex values", metavar="{:,~}", nargs=argparse.ONE_OR_MORE, default="", From 7d853c0402b1c987bc699a09dff0f6a942cb439b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:11:35 +0200 Subject: [PATCH 0637/1235] fix conflicting arguments bug --- can/logger.py | 5 +++-- can/viewer.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/can/logger.py b/can/logger.py index 90db066fc..4197548d0 100644 --- a/can/logger.py +++ b/can/logger.py @@ -59,11 +59,11 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: ) -def _append_filter_argument(parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup]) -> None: +def _append_filter_argument(parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup], *args, **kwargs) -> None: """Adds the ``filter`` option to an argument parser.""" parser.add_argument( - "-f", + *args, "--filter", help="R|Space separated CAN filters for the given CAN interface:" "\n : (matches when & mask == can_id & mask)" @@ -74,6 +74,7 @@ def _append_filter_argument(parser: Union[argparse.ArgumentParser, argparse._Arg metavar="{:,~}", nargs=argparse.ONE_OR_MORE, default="", + **kwargs, ) diff --git a/can/viewer.py b/can/viewer.py index abc4d2c25..7d27a2fba 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -431,7 +431,7 @@ def parse_args(args): default="", ) - _append_filter_argument(optional) + _append_filter_argument(optional, "-f") optional.add_argument( "-i", From 322bc0786010ccedb1a996abbb242c16204f93f9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:17:46 +0200 Subject: [PATCH 0638/1235] fix linter complaint --- can/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interface.py b/can/interface.py index ea2463992..e73c5dfce 100644 --- a/can/interface.py +++ b/can/interface.py @@ -66,9 +66,9 @@ class Bus(BusABC): # pylint: disable=abstract-method """ @staticmethod - def __new__( # type: ignore + def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg cls: Any, channel: Optional[Channel] = None, *args: Any, **kwargs: Any - ) -> BusABC: # pylint: disable=keyword-arg-before-vararg + ) -> BusABC: """ Takes the same arguments as :class:`can.BusABC.__init__`. Some might have a special meaning, see below. From d193bf24e20754fe4d3b69fc30f30097991d788e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:21:03 +0200 Subject: [PATCH 0639/1235] cleanup rename to CanInterfaceNotImplementedError --- can/bus.py | 2 +- can/exceptions.py | 2 +- can/interface.py | 7 +++---- can/util.py | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/can/bus.py b/can/bus.py index 945838e54..c13ba2da5 100644 --- a/can/bus.py +++ b/can/bus.py @@ -66,7 +66,7 @@ def __init__( Any backend dependent configurations are passed in this dictionary :raises ValueError: If parameters are out of range - :raises can.CanBackEndError: If the driver cannot be accessed + :raises can.CanInterfaceNotImplementedError: If the driver cannot be accessed :raises can.CanInitializationError: If the bus cannot be initialized """ self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] diff --git a/can/exceptions.py b/can/exceptions.py index 49ec19544..146fc4f96 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -5,7 +5,7 @@ Exception (Python standard library) +-- ... +-- CanError (python-can) - +-- CanBackendError + +-- CanInterfaceNotImplementedError +-- CanInterfaceNotImplementedError +-- CanOperationError +-- CanTimeoutError diff --git a/can/interface.py b/can/interface.py index 8e6d86c7a..ce113b18e 100644 --- a/can/interface.py +++ b/can/interface.py @@ -22,9 +22,8 @@ def _get_class_for_interface(interface): :raises: NotImplementedError if the interface is not known - :raises: - CanBackEndError if there was a problem while importing the - interface or the bus class within that + :raises CanInterfaceNotImplementedError: + if there was a problem while importing the interface or the bus class within that """ # Find the correct backend try: @@ -82,7 +81,7 @@ def __new__( Should contain an ``interface`` key with a valid interface name. If not, it is completed using :meth:`can.util.load_config`. - :raises: can.CanBackEndError + :raises: can.CanInterfaceNotImplementedError if the ``interface`` isn't recognized or cannot be loaded :raises: can.CanInitializationError diff --git a/can/util.py b/can/util.py index c2b71cf4a..476130560 100644 --- a/can/util.py +++ b/can/util.py @@ -149,7 +149,7 @@ def load_config( All unused values are passed from ``config`` over to this. :raises: - CanBackEndError if the ``interface`` isn't recognized + CanInterfaceNotImplementedError if the ``interface`` isn't recognized """ # start with an empty dict to apply filtering to all sources From 50e1507c7ddf7d44c759d0613e8c1427f53481c2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:26:47 +0200 Subject: [PATCH 0640/1235] fix IXXAT test --- test/test_interface_ixxat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 9e0cf550e..55618c769 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -18,7 +18,7 @@ def setUp(self): try: bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() - except ImportError: + except can.CanInterfaceNotImplementedError: raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): @@ -44,7 +44,7 @@ def setUp(self): try: bus = can.Bus(interface="ixxat", channel=0) bus.shutdown() - except ImportError: + except can.CanInterfaceNotImplementedError: raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): From cbe3fb23e43142ad38d385123b57d3236f110f7c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:31:40 +0200 Subject: [PATCH 0641/1235] defer adjusting test_vector_error_pickle --- test/test_vector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_vector.py b/test/test_vector.py index c00bdb7e7..69edd6d38 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -281,9 +281,10 @@ 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 + # do not set the _testing argument, since it suppresses the exception can.Bus(channel=0, bustype="vector") + @unittest.skip("Fixing this is deferred until Vector is adjusted after #1025") def test_vector_error_pickle(self) -> None: error_code = 118 error_string = "XL_ERROR" From 14282203ab37f64de8b736f76cc61b3c1aad80fd Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Apr 2021 10:42:23 +0200 Subject: [PATCH 0642/1235] fix bad import --- can/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/logger.py b/can/logger.py index 7ad261d9f..72293ed91 100644 --- a/can/logger.py +++ b/can/logger.py @@ -74,7 +74,7 @@ def _append_filter_argument( "\n ~ (matches when & mask != can_id & mask)" "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" "\n python -m can.viewer -f 100:7FC 200:7F0" - "\nNote that the ID and mask are alway interpreted as hex values", + "\nNote that the ID and mask are always interpreted as hex values", metavar="{:,~}", nargs=argparse.ONE_OR_MORE, default="", @@ -112,7 +112,7 @@ def _parse_filters(parsed_args: Any) -> CanFilters: elif "~" in filt: parts = filt.split("~") can_id = int(parts[0], base=16) | 0x20000000 # CAN_INV_FILTER - can_mask = int(parts[1], base=16) & socket.CAN_ERR_FLAG + can_mask = int(parts[1], base=16) & 0x20000000 # socket.CAN_ERR_FLAG else: raise argparse.ArgumentError(None, "Invalid filter argument") can_filters.append({"can_id": can_id, "can_mask": can_mask}) From f68567859b3c40675c0b9dc991808713dca73591 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 27 Apr 2021 13:19:00 +0200 Subject: [PATCH 0643/1235] update IXXAT to new exceptions --- can/interfaces/ixxat/canlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 9abef1210..13709c71c 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -16,7 +16,7 @@ from typing import Optional from can import BusABC, Message -from can.exceptions import * +from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC, @@ -426,7 +426,7 @@ def __init__(self, channel, can_filters=None, **kwargs): Channel bitrate in bit/s """ if _canlib is None: - raise ImportError( + raise CanInterfaceNotImplementedError( "The IXXAT VCI library has not been initialized. Check the logs for more details." ) log.info("CAN Filters: %s", can_filters) From 7c2228d565fb7a5e7e8a80679d7c723726cc2122 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 27 Apr 2021 13:34:16 +0200 Subject: [PATCH 0644/1235] fix linter issue --- can/exceptions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/exceptions.py b/can/exceptions.py index 146fc4f96..f37b95e4c 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -41,7 +41,9 @@ class CanError(Exception): """ def __init__( - self, message: str = "", error_code: Optional[int] = None, *args, **kwargs + self, + message: str = "", + error_code: Optional[int] = None, ) -> None: self.error_code = error_code super().__init__( From b9410e994314a0bd9b1eaf126a94cfdc21930cbf Mon Sep 17 00:00:00 2001 From: Stephane Dorre Date: Tue, 9 Mar 2021 14:27:39 +0100 Subject: [PATCH 0645/1235] Disable command PCAN_ALLOW_ERROR_FRAMES on MAC As this command is not yet supported by PCANUsb lib from MACCAN See: https://www.uv-software.de/files/downloads/MacCAN/PCANUSB/Library/OS_X_Library_for_PCANUSB_v0.10.readme Signed-off-by: Stephane Dorre --- can/interfaces/pcan/pcan.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index fcaa12412..bda677bd6 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -281,13 +281,19 @@ def __init__( if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) - if platform.system() != "Darwin": - result = self.m_objPCANBasic.SetValue( - self.m_PcanHandle, PCAN_ALLOW_ERROR_FRAMES, PCAN_PARAMETER_ON - ) + result = self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_ALLOW_ERROR_FRAMES, PCAN_PARAMETER_ON + ) - if result != PCAN_ERROR_OK: + if result != PCAN_ERROR_OK: + if platform.system() != "Darwin": raise PcanError(self._get_formatted_error(result)) + else: + # TODO Remove Filter when MACCan actually supports it: + # https://github.com/mac-can/PCBUSB-Library/ + log.debug( + "Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.10" + ) if HAS_EVENTS: self._recv_event = CreateEvent(None, 0, 0, None) From dc8ba044161e6e3f5b034ada087cdab7b3720b90 Mon Sep 17 00:00:00 2001 From: Simon Tegelid Date: Thu, 6 May 2021 13:38:45 +0200 Subject: [PATCH 0646/1235] Refactor canutils writer --- can/io/canutils.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/can/io/canutils.py b/can/io/canutils.py index 5c08e9050..7aa2a76ac 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -131,26 +131,16 @@ def on_message_received(self, msg): "(%f) %s %08X#0000000000000000\n" % (timestamp, channel, CAN_ERR_FLAG | CAN_ERR_BUSERROR) ) - - elif msg.is_remote_frame: - if msg.is_extended_id: - self.file.write( - "(%f) %s %08X#R\n" % (timestamp, channel, msg.arbitration_id) - ) - else: - self.file.write( - "(%f) %s %03X#R\n" % (timestamp, channel, msg.arbitration_id) - ) - else: - data = ["{:02X}".format(byte) for byte in msg.data] - if msg.is_extended_id: + arbitration_id_str = ("%08X" if msg.is_extended_id else "%03X") % msg.arbitration_id + + if msg.is_remote_frame: self.file.write( - "(%f) %s %08X#%s\n" - % (timestamp, channel, msg.arbitration_id, "".join(data)) + "(%f) %s %s#R\n" % (timestamp, channel, arbitration_id_str) ) else: + data = ["{:02X}".format(byte) for byte in msg.data] self.file.write( - "(%f) %s %03X#%s\n" - % (timestamp, channel, msg.arbitration_id, "".join(data)) + "(%f) %s %s#%s\n" + % (timestamp, channel, arbitration_id_str, "".join(data)) ) From ef4709183ba1e6c97b0520786fd8c18576121961 Mon Sep 17 00:00:00 2001 From: Simon Tegelid Date: Thu, 6 May 2021 13:58:28 +0200 Subject: [PATCH 0647/1235] Use bytes.hex instead of string format in canutils --- can/io/canutils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/can/io/canutils.py b/can/io/canutils.py index 7aa2a76ac..a5b90b83b 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -139,8 +139,7 @@ def on_message_received(self, msg): "(%f) %s %s#R\n" % (timestamp, channel, arbitration_id_str) ) else: - data = ["{:02X}".format(byte) for byte in msg.data] self.file.write( "(%f) %s %s#%s\n" - % (timestamp, channel, arbitration_id_str, "".join(data)) + % (timestamp, channel, arbitration_id_str, msg.data.hex().upper()) ) From 4b300a64ef0bc3db94f2b207149e0683a38db6ec Mon Sep 17 00:00:00 2001 From: Simon Tegelid Date: Thu, 6 May 2021 14:17:56 +0200 Subject: [PATCH 0648/1235] Add FD support to canutils Remove RTR tests for FD frames since they doesn't exist and thus cannot be represented in the canutils log format. See "CAN with Flexible Data-Rate, version 1.0, Bosch, April 17th, 2012, pp.11": "The REMOTE TRANSMISSION REQUEST (RTR) bit only exists in CAN format frames. [...] There are no REMOTE FRAMES in CAN FD format." --- can/io/canutils.py | 51 +++++++++++++++++++++++++++------------ test/data/example_data.py | 2 -- test/logformats_test.py | 2 +- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/can/io/canutils.py b/can/io/canutils.py index a5b90b83b..9f289d6c5 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -18,6 +18,8 @@ CAN_ERR_BUSERROR = 0x00000080 CAN_ERR_DLC = 8 +CANFD_BRS = 0x01 +CANFD_ESI = 0x02 class CanutilsLogReader(BaseIOHandler): """ @@ -47,13 +49,24 @@ def __iter__(self): timestamp, channel, frame = temp.split() timestamp = float(timestamp[1:-1]) - canId, data = frame.split("#") + canId, data = frame.split("#", maxsplit=1) if channel.isdigit(): channel = int(channel) isExtended = len(canId) > 3 canId = int(canId, 16) + is_fd = False + brs = False + esi = False + + if data and data[0] == "#": + is_fd = True + fd_flags = int(data[1]) + brs = bool(fd_flags & CANFD_BRS) + esi = bool(fd_flags & CANFD_ESI) + data = data[2:] + if data and data[0].lower() == "r": isRemoteFrame = True @@ -79,6 +92,9 @@ def __iter__(self): arbitration_id=canId & 0x1FFFFFFF, is_extended_id=isExtended, is_remote_frame=isRemoteFrame, + is_fd=is_fd, + bitrate_switch=brs, + error_state_indicator=esi, dlc=dlc, data=dataBin, channel=channel, @@ -126,20 +142,25 @@ def on_message_received(self, msg): channel = msg.channel if msg.channel is not None else self.channel + framestr = "(%f) %s" % (timestamp, channel) + if msg.is_error_frame: - self.file.write( - "(%f) %s %08X#0000000000000000\n" - % (timestamp, channel, CAN_ERR_FLAG | CAN_ERR_BUSERROR) - ) + framestr += " %08X#" % (CAN_ERR_FLAG | CAN_ERR_BUSERROR) + elif msg.is_extended_id: + framestr += " %08X#" % (msg.arbitration_id) else: - arbitration_id_str = ("%08X" if msg.is_extended_id else "%03X") % msg.arbitration_id + framestr += " %03X#" % (msg.arbitration_id) - if msg.is_remote_frame: - self.file.write( - "(%f) %s %s#R\n" % (timestamp, channel, arbitration_id_str) - ) - else: - self.file.write( - "(%f) %s %s#%s\n" - % (timestamp, channel, arbitration_id_str, msg.data.hex().upper()) - ) + if msg.is_remote_frame: + framestr += "R\n" + else: + if msg.is_fd: + fd_flags = 0 + if msg.bitrate_switch: + fd_flags |= CANFD_BRS + if msg.error_state_indicator: + fd_flags |= CANFD_ESI + framestr += "#%X" % fd_flags + framestr += "%s\n" % (msg.data.hex().upper()) + + self.file.write(framestr) diff --git a/test/data/example_data.py b/test/data/example_data.py index c682d35f5..d642887cf 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -112,8 +112,6 @@ def sort_messages(messages): [ Message(is_fd=True, data=range(64)), Message(is_fd=True, data=range(8)), - Message(is_fd=True, bitrate_switch=True, is_remote_frame=True), - Message(is_fd=True, error_state_indicator=True, is_remote_frame=True), Message(is_fd=True, data=range(8), bitrate_switch=True), Message(is_fd=True, data=range(8), error_state_indicator=True), ] diff --git a/test/logformats_test.py b/test/logformats_test.py index 10cd4e9f9..8bdb4c215 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -662,7 +662,7 @@ def _setup_instance(self): super()._setup_instance_helper( can.CanutilsLogWriter, can.CanutilsLogReader, - check_fd=False, + check_fd=True, test_append=True, check_comments=False, preserves_channel=False, From 140cbda731cecdbb4893890a0102904bdce5be4d Mon Sep 17 00:00:00 2001 From: Patrick Kanzler <4189642+patkan@users.noreply.github.com> Date: Mon, 10 May 2021 23:30:41 +0200 Subject: [PATCH 0649/1235] Fix minor typos in printer io (#1044) --- can/io/printer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/io/printer.py b/can/io/printer.py index ed3006de2..27fc2caf2 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -16,15 +16,15 @@ class Printer(BaseIOHandler, Listener): any messages it receives to the terminal (stdout). A message is turned into a string using :meth:`~can.Message.__str__`. - :attr bool write_to_file: `True` iff this instance prints to a file instead of + :attr bool write_to_file: `True` if this instance prints to a file instead of standard out """ 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 + :param file: An optional path-like object or a file-like object to "print" + to instead of writing to standard out (stdout). + If this is a file-like object, is has to be 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 From d8ea244eb5241d7aa7fefa29fa0dfd0ee9cda2de Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Mon, 10 May 2021 23:31:28 +0200 Subject: [PATCH 0650/1235] Fix typo introduced in 1025 (#1045) --- can/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/exceptions.py b/can/exceptions.py index f37b95e4c..0ffacefd9 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -21,7 +21,7 @@ class CanError(Exception): """Base class for all CAN related exceptions. - If specified, the error code is automatically prepended to the message: + If specified, the error code is automatically appended to the message: >>> # With an error code (it also works with a specific error): >>> error = CanOperationError(message="Failed to do the thing", error_code=42) From 59a12c6e029783c569481b4a2db71b015a818f57 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 11 May 2021 10:27:34 +0200 Subject: [PATCH 0651/1235] fix linter complaints --- can/logger.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/can/logger.py b/can/logger.py index 72293ed91..b59c6ad9f 100644 --- a/can/logger.py +++ b/can/logger.py @@ -16,7 +16,6 @@ import sys import argparse -import socket from datetime import datetime import errno from typing import Any, Dict, List, Union @@ -60,7 +59,10 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: def _append_filter_argument( - parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup], + parser: Union[ + argparse.ArgumentParser, + argparse._ArgumentGroup, # pylint: disable=protected-access + ], *args, **kwargs, ) -> None: From 356ea647bf722007ec83623d4b9b98974585007a Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 11 May 2021 11:02:43 +0200 Subject: [PATCH 0652/1235] fix another linter cpmplaint --- can/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/logger.py b/can/logger.py index b59c6ad9f..652d47d51 100644 --- a/can/logger.py +++ b/can/logger.py @@ -63,8 +63,8 @@ def _append_filter_argument( argparse.ArgumentParser, argparse._ArgumentGroup, # pylint: disable=protected-access ], - *args, - **kwargs, + *args: str, + **kwargs: Any, ) -> None: """Adds the ``filter`` option to an argument parser.""" From 52892e5344c85090d0c06e0aa0e32ec8360fbd65 Mon Sep 17 00:00:00 2001 From: Simon Tegelid Date: Tue, 11 May 2021 13:46:52 +0200 Subject: [PATCH 0653/1235] Fix formatting in canutils --- can/io/canutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/io/canutils.py b/can/io/canutils.py index 9f289d6c5..b60b333c6 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -21,6 +21,7 @@ CANFD_BRS = 0x01 CANFD_ESI = 0x02 + class CanutilsLogReader(BaseIOHandler): """ Iterator over CAN messages from a .log Logging File (candump -L). From 800c846276382369f36ec64fddc21d893fb92a5b Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 11 May 2021 10:31:13 +0200 Subject: [PATCH 0654/1235] Remove Python 2 leftover from setup.py --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7a9a62d37..d8b76b250 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python + """ Setup script for the `can` package. Learn more at https://github.com/hardbyte/python-can/ @@ -6,8 +7,6 @@ # pylint: disable=invalid-name -from __future__ import absolute_import - from os import listdir from os.path import isfile, join import re From aacd643bcc4f2df551a3eab2c31356bc8d28150a Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 1 Jun 2021 23:34:06 +0200 Subject: [PATCH 0655/1235] constrain versions so pip resolver does not backtrack endlessly --- tox.ini | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 63bccaa02..bf4d604bb 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,8 @@ deps = pytest~=5.3 pytest-timeout~=1.3 pytest-cov~=2.8 - coverage<5 - codecov~=2.0 + coverage==4.5.* + codecov==2.1.10 hypothesis~=4.56 pyserial~=3.0 @@ -19,6 +19,7 @@ recreate = True passenv = CI GITHUB_* + CODECOV_* PY_COLORS commands_post = @@ -29,6 +30,7 @@ passenv = CI TRAVIS TRAVIS_* + CODECOV_* TEST_SOCKETCAN commands_post = From 32f53b49a332c66aa37036cd8d5d9ffcdc77eba7 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sat, 5 Jun 2021 22:27:27 +0200 Subject: [PATCH 0656/1235] Fix single typo --- can/io/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/generic.py b/can/io/generic.py index b3ac93a2a..de8e91700 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -44,7 +44,7 @@ def __exit__(self, exc_type: Type, exc_val: Any, exc_tb: Any) -> Any: return False def stop(self) -> None: - """Closes the undelying file-like object and flushes it, if it was opened in write mode.""" + """Closes the underlying 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() From d6a365000e4e9812a1ae23597cb1d7427be86003 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 11 May 2021 10:47:26 +0200 Subject: [PATCH 0657/1235] patch Message class after comment in 4b300a64ef0bc3db94f2b207149e0683a38db6ec --- can/message.py | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/can/message.py b/can/message.py index 0d3798f15..48b60b185 100644 --- a/can/message.py +++ b/can/message.py @@ -76,7 +76,7 @@ def __init__( # pylint: disable=too-many-locals to `True`. :raises ValueError: - if and only if `check` is set to `True` and one or more arguments were invalid + If and only if `check` is set to `True` and one or more arguments were invalid """ self.timestamp = timestamp self.arbitration_id = arbitration_id @@ -191,13 +191,13 @@ 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") + raise ValueError("non-empty format_specs are not supported") def __bytes__(self) -> bytes: return bytes(self.data) def __copy__(self) -> "Message": - new = Message( + return Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, is_extended_id=self.is_extended_id, @@ -211,10 +211,9 @@ def __copy__(self) -> "Message": bitrate_switch=self.bitrate_switch, error_state_indicator=self.error_state_indicator, ) - return new def __deepcopy__(self, memo: dict) -> "Message": - new = Message( + return Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, is_extended_id=self.is_extended_id, @@ -228,13 +227,13 @@ def __deepcopy__(self, memo: dict) -> "Message": bitrate_switch=self.bitrate_switch, error_state_indicator=self.error_state_indicator, ) - return new - def _check(self): + def _check(self) -> None: """Checks if the message parameters are valid. - Assumes that the types are already correct. - :raises ValueError: iff one or more attributes are invalid + Assumes that the attribute types are already correct. + + :raises ValueError: If and only if one or more attributes are invalid """ if self.timestamp < 0.0: @@ -244,10 +243,13 @@ def _check(self): if isnan(self.timestamp): raise ValueError("the timestamp may not be NaN") - if self.is_remote_frame and self.is_error_frame: - raise ValueError( - "a message cannot be a remote and an error frame at the sane time" - ) + if self.is_remote_frame: + if self.is_error_frame: + raise ValueError( + "a message cannot be a remote and an error frame at the sane time" + ) + if self.is_fd: + raise ValueError("CAN FD does not support remote frames") if self.arbitration_id < 0: raise ValueError("arbitration IDs may not be negative") @@ -263,15 +265,11 @@ def _check(self): if self.is_fd: if self.dlc > 64: raise ValueError( - "DLC was {} but it should be <= 64 for CAN FD frames".format( - self.dlc - ) + f"DLC was {self.dlc} but it should be <= 64 for CAN FD frames" ) elif self.dlc > 8: raise ValueError( - "DLC was {} but it should be <= 8 for normal CAN frames".format( - self.dlc - ) + f"DLC was {self.dlc} but it should be <= 8 for normal CAN frames" ) if self.is_remote_frame: @@ -301,12 +299,12 @@ def equals( :param other: the message to compare with - :param timestamp_delta: the maximum difference at which two timestamps are - still considered equal or None to not compare timestamps + :param timestamp_delta: the maximum difference in seconds at which two timestamps are + still considered equal or `None` to not compare timestamps - :param check_direction: do we compare the messages' directions (Tx/Rx) + :param check_direction: whether to compare the messages' directions (Tx/Rx) - :return: True iff the given message equals this one + :return: True if and only if the given message equals this one """ # see https://github.com/hardbyte/python-can/pull/413 for a discussion # on why a delta of 1.0e-6 was chosen From 9f1591d449265d57c1b8ca57826722a7a1510bb0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 2 Jun 2021 11:48:24 +0200 Subject: [PATCH 0658/1235] reformat --- can/message.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/can/message.py b/can/message.py index 48b60b185..013874c15 100644 --- a/can/message.py +++ b/can/message.py @@ -298,10 +298,8 @@ def equals( Compares a given message with this one. :param other: the message to compare with - :param timestamp_delta: the maximum difference in seconds at which two timestamps are still considered equal or `None` to not compare timestamps - :param check_direction: whether to compare the messages' directions (Tx/Rx) :return: True if and only if the given message equals this one From d826fe6dd33201bd3bb0d169f158119fb49191cf Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 2 Jun 2021 20:13:53 +0200 Subject: [PATCH 0659/1235] fix test case to match new validation --- test/test_message_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_message_class.py b/test/test_message_class.py index f65cecb09..e82581fa9 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -51,7 +51,7 @@ def test_methods(self, **kwargs): or (kwargs["arbitration_id"] >= 0x800 and not kwargs["is_extended_id"]) or kwargs["arbitration_id"] >= 0x20000000 or kwargs["arbitration_id"] < 0 - or (kwargs["is_remote_frame"] and kwargs["is_error_frame"]) + or (kwargs["is_remote_frame"] and (kwargs["is_fd"] or kwargs["is_error_frame"])) or (kwargs["is_remote_frame"] and len(kwargs["data"] or []) > 0) or ( (kwargs["bitrate_switch"] or kwargs["error_state_indicator"]) From dcd771b780f8894a3cb0af441401197f3c9e0947 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 3 Jun 2021 10:04:16 +0000 Subject: [PATCH 0660/1235] Format code with black --- test/test_message_class.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_message_class.py b/test/test_message_class.py index e82581fa9..997f6df8d 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -51,7 +51,10 @@ def test_methods(self, **kwargs): or (kwargs["arbitration_id"] >= 0x800 and not kwargs["is_extended_id"]) or kwargs["arbitration_id"] >= 0x20000000 or kwargs["arbitration_id"] < 0 - or (kwargs["is_remote_frame"] and (kwargs["is_fd"] or kwargs["is_error_frame"])) + or ( + kwargs["is_remote_frame"] + and (kwargs["is_fd"] or kwargs["is_error_frame"]) + ) or (kwargs["is_remote_frame"] and len(kwargs["data"] or []) > 0) or ( (kwargs["bitrate_switch"] or kwargs["error_state_indicator"]) From 0d20d673f038800eb7ff3cd19bfb5b425559ccea Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 17 May 2021 21:57:05 +0200 Subject: [PATCH 0661/1235] fix VectorError bug --- can/interfaces/vector/exceptions.py | 5 +++-- test/test_vector.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 95f8b579d..739060188 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -6,8 +6,9 @@ class VectorError(CanError): def __init__(self, error_code, error_string, function): - self.error_code = error_code - super().__init__(f"{function} failed ({error_string})") + super().__init__( + message=f"{function} failed ({error_string})", error_code=error_code + ) # keep reference to args for pickling self._args = error_code, error_string, function diff --git a/test/test_vector.py b/test/test_vector.py index 69edd6d38..09ad7c9c3 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -284,7 +284,6 @@ def test_called_without_testing_argument(self) -> None: # do not set the _testing argument, since it suppresses the exception can.Bus(channel=0, bustype="vector") - @unittest.skip("Fixing this is deferred until Vector is adjusted after #1025") def test_vector_error_pickle(self) -> None: error_code = 118 error_string = "XL_ERROR" From 62e2282fb7a9ff551e6450ef1f807ebffb0ef16a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 2 Jun 2021 12:32:15 +0200 Subject: [PATCH 0662/1235] preserve capitalization when reading config files --- can/util.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/can/util.py b/can/util.py index 476130560..262f149d4 100644 --- a/can/util.py +++ b/can/util.py @@ -52,6 +52,10 @@ def load_file_config( name of the section to read configuration from. """ config = ConfigParser() + + # make sure to not transform the entries such that capitalization is preserved + config.optionxform = lambda entry: entry # type: ignore + if path is None: config.read([os.path.expanduser(path) for path in CONFIG_FILES]) else: From dbdbe0306c21324d1e71c0555dc6cc443942ac80 Mon Sep 17 00:00:00 2001 From: Dawid Rosinski Date: Sun, 6 Jun 2021 10:28:02 +0100 Subject: [PATCH 0663/1235] add tests for PCAN (#1056) * add tests for PCAN * Format code with black * Update test/test_pcan.py Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- test/test_pcan.py | 307 ++++++++++++++++++++++++++++++++++++++++++++++ tox.ini | 1 + 2 files changed, 308 insertions(+) create mode 100644 test/test_pcan.py diff --git a/test/test_pcan.py b/test/test_pcan.py new file mode 100644 index 000000000..32579d894 --- /dev/null +++ b/test/test_pcan.py @@ -0,0 +1,307 @@ +""" +Test for PCAN Interface +""" + +import ctypes +import unittest +from unittest import mock +from unittest.mock import Mock +from can.bus import BusState +from can.interfaces.pcan.basic import * +from can.interfaces.pcan import PcanBus +from parameterized import parameterized + +import pytest + +import can +from can.interfaces.pcan import pcan + + +class TestPCANBus(unittest.TestCase): + def setUp(self) -> None: + + patcher = mock.patch("can.interfaces.pcan.pcan.PCANBasic", spec=True) + self.MockPCANBasic = patcher.start() + self.addCleanup(patcher.stop) + self.mock_pcan = self.MockPCANBasic.return_value + self.mock_pcan.Initialize.return_value = PCAN_ERROR_OK + self.mock_pcan.InitializeFD = Mock(return_value=PCAN_ERROR_OK) + self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_OK) + + 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(bustype="pcan") + self.assertIsInstance(self.bus, pcan.PcanBus) + self.MockPCANBasic.assert_called_once() + self.mock_pcan.Initialize.assert_called_once() + self.mock_pcan.InitializeFD.assert_not_called() + + def test_bus_creation_state_error(self) -> None: + with self.assertRaises(ctypes.ArgumentError): + self.bus = can.Bus(bustype="pcan", state=BusState.ERROR) + self.assertIsInstance(self.bus, pcan.PcanBus) + + def test_bus_creation_fd(self) -> None: + self.bus = can.Bus(bustype="pcan", fd=True) + self.assertIsInstance(self.bus, pcan.PcanBus) + self.MockPCANBasic.assert_called_once() + self.mock_pcan.Initialize.assert_not_called() + self.mock_pcan.InitializeFD.assert_called_once() + + @parameterized.expand( + [ + ("no_error", PCAN_ERROR_OK, PCAN_ERROR_OK, "some ok text 1"), + ("one_error", PCAN_ERROR_UNKNOWN, PCAN_ERROR_OK, "some ok text 2"), + ( + "both_errors", + PCAN_ERROR_UNKNOWN, + PCAN_ERROR_UNKNOWN, + "An error occurred. Error-code's text (8h) couldn't be retrieved", + ), + ] + ) + def test_get_formatted_error(self, name, status1, status2, expected_result: str): + + self.bus = can.Bus(bustype="pcan") + self.mock_pcan.GetErrorText = Mock( + side_effect=[ + (status1, expected_result.encode("utf-8", errors="replace")), + (status2, expected_result.encode("utf-8", errors="replace")), + ] + ) + + complete_text = self.bus._get_formatted_error(PCAN_ERROR_BUSHEAVY) + + self.assertEqual(complete_text, expected_result) + + def test_status(self) -> None: + self.bus = can.Bus(bustype="pcan") + self.bus.status() + self.mock_pcan.GetStatus.assert_called_once_with(PCAN_USBBUS1) + + @parameterized.expand( + [("no_error", PCAN_ERROR_OK, True), ("error", PCAN_ERROR_UNKNOWN, False)] + ) + def test_status_is_ok(self, name, status, expected_result) -> None: + self.mock_pcan.GetStatus = Mock(return_value=status) + self.bus = can.Bus(bustype="pcan") + self.assertEqual(self.bus.status_is_ok(), expected_result) + self.mock_pcan.GetStatus.assert_called_once_with(PCAN_USBBUS1) + + @parameterized.expand( + [("no_error", PCAN_ERROR_OK, True), ("error", PCAN_ERROR_UNKNOWN, False)] + ) + def test_reset(self, name, status, expected_result) -> None: + self.mock_pcan.Reset = Mock(return_value=status) + self.bus = can.Bus(bustype="pcan", fd=True) + self.assertEqual(self.bus.reset(), expected_result) + self.mock_pcan.Reset.assert_called_once_with(PCAN_USBBUS1) + + @parameterized.expand( + [("no_error", PCAN_ERROR_OK, 1), ("error", PCAN_ERROR_UNKNOWN, None)] + ) + def test_get_device_number(self, name, status, expected_result) -> None: + self.mock_pcan.GetValue = Mock(return_value=(status, 1)) + self.bus = can.Bus(bustype="pcan", fd=True) + self.assertEqual(self.bus.get_device_number(), expected_result) + self.mock_pcan.GetValue.assert_called_once_with( + PCAN_USBBUS1, PCAN_DEVICE_NUMBER + ) + + @parameterized.expand( + [("no_error", PCAN_ERROR_OK, True), ("error", PCAN_ERROR_UNKNOWN, False)] + ) + def test_set_device_number(self, name, status, expected_result) -> None: + self.bus = can.Bus(bustype="pcan") + self.mock_pcan.SetValue = Mock(return_value=status) + self.assertEqual(self.bus.set_device_number(3), expected_result) + # check last SetValue call + self.assertEqual( + self.mock_pcan.SetValue.call_args_list[-1][0], + (PCAN_USBBUS1, PCAN_DEVICE_NUMBER, 3), + ) + + def test_recv(self): + data = (ctypes.c_ubyte * 8)(*[x for x in range(8)]) + msg = TPCANMsg(ID=0xC0FFEF, LEN=8, MSGTYPE=PCAN_MESSAGE_EXTENDED, DATA=data) + + timestamp = TPCANTimestamp() + self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_OK, msg, timestamp)) + self.bus = can.Bus(bustype="pcan") + + recv_msg = self.bus.recv() + self.assertEqual(recv_msg.arbitration_id, msg.ID) + self.assertEqual(recv_msg.dlc, msg.LEN) + self.assertEqual(recv_msg.is_extended_id, True) + self.assertEqual(recv_msg.is_fd, False) + self.assertSequenceEqual(recv_msg.data, msg.DATA) + self.assertEqual(recv_msg.timestamp, 0) + + def test_recv_fd(self): + data = (ctypes.c_ubyte * 64)(*[x for x in range(64)]) + msg = TPCANMsgFD( + ID=0xC0FFEF, + DLC=64, + MSGTYPE=(PCAN_MESSAGE_EXTENDED.value | PCAN_MESSAGE_FD.value), + DATA=data, + ) + + timestamp = TPCANTimestampFD() + + self.mock_pcan.ReadFD = Mock(return_value=(PCAN_ERROR_OK, msg, timestamp)) + + self.bus = can.Bus(bustype="pcan", fd=True) + + recv_msg = self.bus.recv() + self.assertEqual(recv_msg.arbitration_id, msg.ID) + self.assertEqual(recv_msg.dlc, msg.DLC) + self.assertEqual(recv_msg.is_extended_id, True) + self.assertEqual(recv_msg.is_fd, True) + self.assertSequenceEqual(recv_msg.data, msg.DATA) + self.assertEqual(recv_msg.timestamp, 0) + + @pytest.mark.timeout(3.0) + def test_recv_no_message(self): + self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_QRCVEMPTY, None, None)) + self.bus = can.Bus(bustype="pcan") + self.assertEqual(self.bus.recv(timeout=0.5), None) + + def test_send(self) -> None: + self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_OK) + self.bus = can.Bus(bustype="pcan") + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + self.bus.send(msg) + self.mock_pcan.Write.assert_called_once() + self.mock_pcan.WriteFD.assert_not_called() + + def test_send_fd(self) -> None: + self.mock_pcan.WriteFD = Mock(return_value=PCAN_ERROR_OK) + self.bus = can.Bus(bustype="pcan", fd=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + self.bus.send(msg) + self.mock_pcan.Write.assert_not_called() + self.mock_pcan.WriteFD.assert_called_once() + + @parameterized.expand( + [ + ( + "standart", + (False, False, False, False, False, False), + PCAN_MESSAGE_STANDARD, + ), + ( + "extended", + (True, False, False, False, False, False), + PCAN_MESSAGE_EXTENDED, + ), + ("remote", (False, True, False, False, False, False), PCAN_MESSAGE_RTR), + ("error", (False, False, True, False, False, False), PCAN_MESSAGE_ERRFRAME), + ("fd", (False, False, False, True, False, False), PCAN_MESSAGE_FD), + ( + "bitrate_switch", + (False, False, False, False, True, False), + PCAN_MESSAGE_BRS, + ), + ( + "error_state_indicator", + (False, False, False, False, False, True), + PCAN_MESSAGE_ESI, + ), + ] + ) + def test_send_type(self, name, msg_type, expected_value) -> None: + ( + is_extended_id, + is_remote_frame, + is_error_frame, + is_fd, + bitrate_switch, + error_state_indicator, + ) = msg_type + + self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_OK) + + self.bus = can.Bus(bustype="pcan") + msg = can.Message( + arbitration_id=0xC0FFEF, + data=[1, 2, 3, 4, 5, 6, 7, 8], + is_extended_id=is_extended_id, + is_remote_frame=is_remote_frame, + is_error_frame=is_error_frame, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator, + is_fd=is_fd, + ) + self.bus.send(msg) + # self.mock_m_objPCANBasic.Write.assert_called_once() + CANMsg = self.mock_pcan.Write.call_args_list[0][0][1] + self.assertEqual(CANMsg.MSGTYPE, expected_value.value) + + def test_send_error(self) -> None: + self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_BUSHEAVY) + self.bus = can.Bus(bustype="pcan") + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + + with self.assertRaises(pcan.PcanError): + self.bus.send(msg) + + @parameterized.expand([("on", True), ("off", False)]) + def test_flash(self, name, flash) -> None: + self.bus = can.Bus(bustype="pcan") + self.bus.flash(flash) + call_list = self.mock_pcan.SetValue.call_args_list + last_call_args_list = call_list[-1][0] + self.assertEqual( + last_call_args_list, (PCAN_USBBUS1, PCAN_CHANNEL_IDENTIFYING, flash) + ) + + def test_shutdown(self) -> None: + self.bus = can.Bus(bustype="pcan") + self.bus.shutdown() + self.mock_pcan.Uninitialize.assert_called_once_with(PCAN_USBBUS1) + + @parameterized.expand( + [ + ("active", BusState.ACTIVE, PCAN_PARAMETER_OFF), + ("passive", BusState.PASSIVE, PCAN_PARAMETER_ON), + ] + ) + def test_state(self, name, bus_state: BusState, expected_parameter) -> None: + self.bus = can.Bus(bustype="pcan") + + self.bus.state = bus_state + call_list = self.mock_pcan.SetValue.call_args_list + last_call_args_list = call_list[-1][0] + self.assertEqual( + last_call_args_list, (PCAN_USBBUS1, PCAN_LISTEN_ONLY, expected_parameter) + ) + + def test_detect_available_configs(self) -> None: + self.mock_pcan.GetValue = Mock( + return_value=(PCAN_ERROR_OK, PCAN_CHANNEL_AVAILABLE) + ) + configs = PcanBus._detect_available_configs() + self.assertEqual(len(configs), 50) + + @parameterized.expand([("valid", PCAN_ERROR_OK, "OK"), ("invalid", 0x00005, None)]) + def test_status_string(self, name, status, expected_result) -> None: + self.bus = can.Bus(bustype="pcan") + self.mock_pcan.GetStatus = Mock(return_value=status) + self.assertEqual(self.bus.status_string(), expected_result) + self.mock_pcan.GetStatus.assert_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/tox.ini b/tox.ini index bf4d604bb..164dfdd8e 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ deps = codecov==2.1.10 hypothesis~=4.56 pyserial~=3.0 + parameterized~=0.8 commands = pytest {posargs} From 6d77050bc51f26e2732ee887c2e02100839a08c4 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Mon, 7 Jun 2021 00:23:16 +0200 Subject: [PATCH 0664/1235] Improve IO module typing (#1068) * improve IO module typing * Add missing typing_extensions dependency * Remove the now unnesesary mypy_extensions * Format code with black * fix ContextManager signature in BaseIOHandler * move type annotation of BaseIOHandler::file to actual annotation and out of the docstring * ignore pylint false positive --- can/io/generic.py | 34 +++++++++++++++++++++++--------- can/typechecking.py | 8 ++++---- examples/simple_log_converter.py | 2 +- setup.py | 2 +- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/can/io/generic.py b/can/io/generic.py index de8e91700..daead706e 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,24 +1,35 @@ -""" -Contains a generic class for file IO. -""" +"""Contains generic base classes for file IO.""" from abc import ABCMeta -from typing import Any, Optional, cast, Union, TextIO, BinaryIO, Type +from typing import ( + Optional, + cast, + Iterable, + Union, + TextIO, + BinaryIO, + Type, + ContextManager, +) +from typing_extensions import Literal +from types import TracebackType import can import can.typechecking -class BaseIOHandler(metaclass=ABCMeta): +class BaseIOHandler(ContextManager, metaclass=ABCMeta): """A generic file handler that can be used for reading and writing. Can be used as a context manager. - :attr Optional[FileLike] file: - the file-like object that is kept internally, or None if none + :attr file: + the file-like object that is kept internally, or `None` if none was opened """ + file: Optional[can.typechecking.FileLike] + 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 @@ -39,7 +50,12 @@ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> N def __enter__(self) -> "BaseIOHandler": return self - def __exit__(self, exc_type: Type, exc_val: Any, exc_tb: Any) -> Any: + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Literal[False]: self.stop() return False @@ -63,5 +79,5 @@ class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): # pylint: disable=too-few-public-methods -class MessageReader(BaseIOHandler, metaclass=ABCMeta): +class MessageReader(BaseIOHandler, Iterable, metaclass=ABCMeta): """The base class for all readers.""" diff --git a/can/typechecking.py b/can/typechecking.py index d29f1ccaf..1aaf03ac4 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -6,10 +6,10 @@ if typing.TYPE_CHECKING: import os -import mypy_extensions +import typing_extensions -CanFilter = mypy_extensions.TypedDict("CanFilter", {"can_id": int, "can_mask": int}) -CanFilterExtended = mypy_extensions.TypedDict( +CanFilter = typing_extensions.TypedDict("CanFilter", {"can_id": int, "can_mask": int}) +CanFilterExtended = typing_extensions.TypedDict( "CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool} ) CanFilters = typing.Sequence[typing.Union[CanFilter, CanFilterExtended]] @@ -33,7 +33,7 @@ BusConfig = typing.NewType("BusConfig", typing.Dict[str, typing.Any]) -AutoDetectedConfig = mypy_extensions.TypedDict( +AutoDetectedConfig = typing_extensions.TypedDict( "AutoDetectedConfig", {"interface": str, "channel": Channel} ) diff --git a/examples/simple_log_converter.py b/examples/simple_log_converter.py index f01546375..e82669f54 100755 --- a/examples/simple_log_converter.py +++ b/examples/simple_log_converter.py @@ -18,7 +18,7 @@ def main(): with can.LogReader(sys.argv[1]) as reader: with can.Logger(sys.argv[2]) as writer: - for msg in reader: + for msg in reader: # pylint: disable=not-an-iterable writer.on_message_received(msg) diff --git a/setup.py b/setup.py index d8b76b250..1b56379cf 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,7 @@ # "setuptools", "wrapt~=1.10", 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"', - "mypy_extensions>=0.4.0,<0.5.0", + "typing_extensions>=3.10.0.0", 'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"', 'msgpack~=1.0.0;platform_system!="Windows"', ], From 906f523816add2d61e040a30c88cd3f9fc63d657 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 6 Jun 2021 11:57:37 +0200 Subject: [PATCH 0665/1235] Add bdist_wheel distribution to automated Travis CI deployment --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a7aafd5e7..48e3fd0f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -86,6 +86,7 @@ jobs: python: "3.9" deploy: provider: pypi + distributions: "sdist bdist_wheel" user: hardbyte password: secure: oQ9XpEkcilkZgKp+rKvPb2J1GrZe2ZvtOq/IjzCpiA8NeWixl/ai3BkPrLbd8t1wNIFoGwx7IQ7zxWL79aPYeG6XrljEomv3g45NR6dkQewUH+dQFlnT75Rm96Ycxvme0w1+71vM4PqxIuzyXUrF2n7JjC0XCCxHdTuYmPGbxVO1fOsE5R5b9inAbpEUtJuWz5AIrDEZ0OgoQpLSC8fLwbymTThX3JZ5GBLpRScVvLazjIYfRkZxvCqQ4mp1UNTdoMzekxsvxOOcEW6+j3fQO+Q/8uvMksKP0RgT8HE69oeYOeVic4Q4wGqORw+ur4A56NvBqVKtizVLCzzEG9ZfoSDy7ryvGWGZykkh8HX0PFQAEykC3iYihHK8ZFz5bEqRMegTmuRYZwPsel61wVd5posxnQkGm0syIoJNKuuRc5sUK+E3GviYcT8NntdR+4WBrvpQAYa1ZHpVrfnQXyaDmGzOjwCRGPoIDJweEqGVmLycEC5aT8rX3/W9tie9iPnjmFJh4CwNMxDgVQRo80m6Gtlf/DQpA3mH39IvWGqd5fHdTPxYPs32EQSCsaYLJV5pM8xBNv6M2S/KriGnGZU0xT7MEr46da0LstKsK/U8O0yamjyugMvQoC3zQcKLrDzWFSBsT7/vG+AuV5SK8yzfEHugo7jkPQQ+NTw29xzk4dY= From 868911a1a39a0b942a163f9eb5e2e4d743e0c214 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 16:09:44 +0200 Subject: [PATCH 0666/1235] simplify CANLIBError --- can/interfaces/kvaser/canlib.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 201a4ec82..4ba492b43 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -67,20 +67,15 @@ class CANLIBError(CanError): """ def __init__(self, function, error_code, arguments): - super().__init__() - self.error_code = error_code + message = CANLIBError._get_error_message(error_code) + super().__init__(f"Function {function.__name__} failed - {message}", error_code) self.function = function self.arguments = arguments - def __str__(self): - return "Function %s failed - %s" % ( - self.function.__name__, - self.__get_error_message(), - ) - - def __get_error_message(self): + @staticmethod + def _get_error_message(error_code: int) -> str: errmsg = ctypes.create_string_buffer(128) - canGetErrorText(self.error_code, errmsg, len(errmsg)) + canGetErrorText(error_code, errmsg, len(errmsg)) return errmsg.value.decode("ascii") From 8b5f64d301182fa4ea3acefc98740daf65aecab7 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 16:41:25 +0200 Subject: [PATCH 0667/1235] Adjust kvaser interface --- can/interfaces/kvaser/canlib.py | 98 +++++++++++++++++++-------------- test/test_kvaser.py | 1 + 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 4ba492b43..59ca187a1 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -11,7 +11,8 @@ import logging import ctypes -from can import CanError, BusABC +from can import BusABC +from ...exceptions import CanError, CanInitializationError, CanOperationError from can import Message from can.util import time_perfcounter_correlation from . import constants as canstat @@ -79,27 +80,39 @@ def _get_error_message(error_code: int) -> str: return errmsg.value.decode("ascii") +class CANLIBInitializationError(CANLIBError, CanInitializationError): + pass + + +class CANLIBOperationError(CANLIBError, CanOperationError): + pass + + def __convert_can_status_to_int(result): - # log.debug("converting can status to int {} ({})".format(result, type(result))) if isinstance(result, int): return result else: return result.value -def __check_status(result, function, arguments): +def __check_status_operation(result, function, arguments): result = __convert_can_status_to_int(result) if not canstat.CANSTATUS_SUCCESS(result): - # log.debug('Detected error while checking CAN status') - raise CANLIBError(function, result, arguments) + raise CANLIBOperationError(function, result, arguments) + return result + + +def __check_status_initialization(result, function, arguments): + result = __convert_can_status_to_int(result) + if not canstat.CANSTATUS_SUCCESS(result): + raise CANLIBInitializationError(function, result, arguments) return result def __check_status_read(result, function, arguments): result = __convert_can_status_to_int(result) if not canstat.CANSTATUS_SUCCESS(result) and result != canstat.canERR_NOMSG: - # log.debug('Detected error in which checking status read') - raise CANLIBError(function, result, arguments) + raise CANLIBOperationError(function, result, arguments) return result @@ -110,16 +123,12 @@ class c_canHandle(ctypes.c_int): canINVALID_HANDLE = -1 -def __handle_is_valid(handle): - return handle.value > canINVALID_HANDLE - - def __check_bus_handle_validity(handle, function, arguments): - if not __handle_is_valid(handle): - result = __convert_can_status_to_int(handle) - raise CANLIBError(function, result, arguments) - else: - return handle + if handle.value > canINVALID_HANDLE: + return handle # is valid + + result = __convert_can_status_to_int(handle) + raise CANLIBInitializationError(function, result, arguments) if __canlib is not None: @@ -129,7 +138,7 @@ def __check_bus_handle_validity(handle, function, arguments): "canGetErrorText", argtypes=[canstat.c_canStatus, ctypes.c_char_p, ctypes.c_uint], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, ) # TODO wrap this type of function to provide a more Pythonic API @@ -137,35 +146,35 @@ def __check_bus_handle_validity(handle, function, arguments): "canGetNumberOfChannels", argtypes=[ctypes.c_void_p], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_initialization, ) kvReadTimer = __get_canlib_function( "kvReadTimer", argtypes=[c_canHandle, ctypes.POINTER(ctypes.c_uint)], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_initialization, ) canBusOff = __get_canlib_function( "canBusOff", argtypes=[c_canHandle], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, ) canBusOn = __get_canlib_function( "canBusOn", argtypes=[c_canHandle], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_initialization, ) canClose = __get_canlib_function( "canClose", argtypes=[c_canHandle], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, ) canOpenChannel = __get_canlib_function( @@ -187,7 +196,7 @@ def __check_bus_handle_validity(handle, function, arguments): ctypes.c_uint, ], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_initialization, ) canSetBusParamsFd = __get_canlib_function( @@ -200,21 +209,21 @@ def __check_bus_handle_validity(handle, function, arguments): ctypes.c_uint, ], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_initialization, ) canSetBusOutputControl = __get_canlib_function( "canSetBusOutputControl", argtypes=[c_canHandle, ctypes.c_uint], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_initialization, ) canSetAcceptanceFilter = __get_canlib_function( "canSetAcceptanceFilter", argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_uint, ctypes.c_int], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, ) canReadWait = __get_canlib_function( @@ -242,32 +251,39 @@ def __check_bus_handle_validity(handle, function, arguments): ctypes.c_uint, ], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, ) canWriteSync = __get_canlib_function( "canWriteSync", argtypes=[c_canHandle, ctypes.c_ulong], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, + ) + + canIoCtlInit = __get_canlib_function( + "canIoCtl", + argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint], + restype=canstat.c_canStatus, + errcheck=__check_status_initialization, ) canIoCtl = __get_canlib_function( "canIoCtl", argtypes=[c_canHandle, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, ) canGetVersion = __get_canlib_function( - "canGetVersion", restype=ctypes.c_short, errcheck=__check_status + "canGetVersion", restype=ctypes.c_short, errcheck=__check_status_operation ) kvFlashLeds = __get_canlib_function( "kvFlashLeds", argtypes=[c_canHandle, ctypes.c_int, ctypes.c_int], restype=ctypes.c_short, - errcheck=__check_status, + errcheck=__check_status_operation, ) if sys.platform == "win32": @@ -275,21 +291,21 @@ def __check_bus_handle_validity(handle, function, arguments): "canGetVersionEx", argtypes=[ctypes.c_uint], restype=ctypes.c_uint, - errcheck=__check_status, + errcheck=__check_status_operation, ) canGetChannelData = __get_canlib_function( "canGetChannelData", argtypes=[ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_initialization, ) canRequestBusStatistics = __get_canlib_function( "canRequestBusStatistics", argtypes=[c_canHandle], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, ) canGetBusStatistics = __get_canlib_function( @@ -300,7 +316,7 @@ def __check_bus_handle_validity(handle, function, arguments): ctypes.c_size_t, ], restype=canstat.c_canStatus, - errcheck=__check_status, + errcheck=__check_status_operation, ) @@ -435,7 +451,7 @@ def __init__(self, channel, can_filters=None, **kwargs): log.debug("Creating read handle to bus channel: %s", channel) self._read_handle = canOpenChannel(channel, flags) - canIoCtl( + canIoCtlInit( self._read_handle, canstat.canIOCTL_SET_TIMER_SCALE, ctypes.byref(ctypes.c_long(TIMESTAMP_RESOLUTION)), @@ -462,7 +478,7 @@ def __init__(self, channel, can_filters=None, **kwargs): local_echo = single_handle or receive_own_messages if receive_own_messages and single_handle: log.warning("receive_own_messages only works if single_handle is False") - canIoCtl( + canIoCtlInit( self._read_handle, canstat.canIOCTL_SET_LOCAL_TXECHO, ctypes.byref(ctypes.c_byte(local_echo)), @@ -528,9 +544,8 @@ def _apply_filters(self, filters): for handle in (self._read_handle, self._write_handle): for extended in (0, 1): canSetAcceptanceFilter(handle, 0, 0, extended) - except (NotImplementedError, CANLIBError): - # TODO add logging? - pass + except (NotImplementedError, CANLIBError) as e: + log.error("An error occured while disabling filtering: %s", e) def flush_tx_buffer(self): """Wipeout the transmit buffer on the Kvaser.""" @@ -565,7 +580,6 @@ def _recv_internal(self, timeout=None): ) if status == canstat.canOK: - # log.debug('read complete -> status OK') data_array = data.raw flags = flags.value is_extended = bool(flags & canstat.canMSG_EXT) @@ -666,7 +680,7 @@ def _detect_available_configs(): num_channels = ctypes.c_int(0) try: canGetNumberOfChannels(ctypes.byref(num_channels)) - except Exception: + except CANLIBError: pass return [ {"interface": "kvaser", "channel": channel} diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 3acc2731d..6d67c3915 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -20,6 +20,7 @@ def setUp(self): canlib.canGetNumberOfChannels = KvaserTest.canGetNumberOfChannels canlib.canOpenChannel = Mock(return_value=0) canlib.canIoCtl = Mock(return_value=0) + canlib.canIoCtlInit = Mock(return_value=0) canlib.kvReadTimer = Mock() canlib.canSetBusParams = Mock() canlib.canSetBusParamsFd = Mock() From 51569b0fba75352f02203f9a0875df9d86095dd4 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 17:10:43 +0200 Subject: [PATCH 0668/1235] improve error handling in neousys; fix bugs --- can/interfaces/neousys/neousys.py | 112 +++++++++++++----------------- 1 file changed, 48 insertions(+), 64 deletions(-) diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index a0037981b..1950bb1d0 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -13,10 +13,7 @@ # pylint: disable=too-few-public-methods # pylint: disable=too-many-instance-attributes # pylint: disable=wrong-import-position -# pylint: disable=method-hidden -# pylint: disable=unused-import -import warnings import queue import logging import platform @@ -33,11 +30,13 @@ Structure, ) -if platform.system() == "Windows": +try: from ctypes import WinDLL -else: +except ImportError: from ctypes import CDLL + from can import BusABC, Message +from ...exceptions import CanInitializationError, CanOperationError, CanInterfaceNotImplementedError logger = logging.getLogger(__name__) @@ -126,14 +125,13 @@ class NeousysCanBitClk(Structure): NEOUSYS_CANLIB = WinDLL("./WDT_DIO.dll") else: NEOUSYS_CANLIB = CDLL("libwdt_dio.so") - logger.info("Loaded Neousys WDT_DIO Can driver") + logger.info("Loaded Neousys WDT_DIO Can driver") except OSError as error: - logger.info("Cannot Neousys CAN bus dll or share object: %d", format(error)) - # NEOUSYS_CANLIB = None + logger.info("Cannot load Neousys CAN bus dll or shared object: %d", format(error)) class NeousysBus(BusABC): - """ Neousys CAN bus Class""" + """Neousys CAN bus Class""" def __init__(self, channel, device=0, bitrate=500000, **kwargs): """ @@ -143,43 +141,41 @@ def __init__(self, channel, device=0, bitrate=500000, **kwargs): """ super().__init__(channel, **kwargs) - if NEOUSYS_CANLIB is not None: - self.channel = channel - - self.device = device + if NEOUSYS_CANLIB is None: + raise CanInterfaceNotImplementedError("Neousys WDT_DIO Can driver missing") - self.channel_info = "Neousys Can: device {}, channel {}".format( - self.device, self.channel - ) + self.channel = channel + self.device = device + self.channel_info = f"Neousys Can: device {self.device}, channel {self.channel}" - self.queue = queue.Queue() + self.queue = queue.Queue() - # Init with accept all and wanted bitrate - self.init_config = NeousysCanSetup( - bitrate, NEOUSYS_CAN_MSG_USE_ID_FILTER, 0, 0 - ) + # Init with accept all and wanted bitrate + self.init_config = NeousysCanSetup( + bitrate, NEOUSYS_CAN_MSG_USE_ID_FILTER, 0, 0 + ) - self._neousys_recv_cb = NEOUSYS_CAN_MSG_CALLBACK(self._neousys_recv_cb) - self._neousys_status_cb = NEOUSYS_CAN_STATUS_CALLBACK( - self._neousys_status_cb - ) + self._neousys_recv_cb = NEOUSYS_CAN_MSG_CALLBACK(self._neousys_recv_cb) + self._neousys_status_cb = NEOUSYS_CAN_STATUS_CALLBACK( + self._neousys_status_cb + ) - if NEOUSYS_CANLIB.CAN_RegisterReceived(0, self._neousys_recv_cb) == 0: - logger.error("Neousys CAN bus Setup receive callback") + if NEOUSYS_CANLIB.CAN_RegisterReceived(0, self._neousys_recv_cb) == 0: + raise CanInitializationError("Neousys CAN bus Setup receive callback") - if NEOUSYS_CANLIB.CAN_RegisterStatus(0, self._neousys_status_cb) == 0: - logger.error("Neousys CAN bus Setup status callback") + if NEOUSYS_CANLIB.CAN_RegisterStatus(0, self._neousys_status_cb) == 0: + raise CanInitializationError("Neousys CAN bus Setup status callback") - if ( - NEOUSYS_CANLIB.CAN_Setup( - channel, byref(self.init_config), sizeof(self.init_config) - ) - == 0 - ): - logger.error("Neousys CAN bus Setup Error") + if ( + NEOUSYS_CANLIB.CAN_Setup( + channel, byref(self.init_config), sizeof(self.init_config) + ) + == 0 + ): + raise CanInitializationError("Neousys CAN bus Setup Error") - if NEOUSYS_CANLIB.CAN_Start(channel) == 0: - logger.error("Neousys CAN bus Start Error") + if NEOUSYS_CANLIB.CAN_Start(channel) == 0: + raise CanInitializationError("Neousys CAN bus Start Error") def send(self, msg, timeout=None): """ @@ -188,26 +184,18 @@ def send(self, msg, timeout=None): :return: """ - if NEOUSYS_CANLIB is None: - logger.error("Can't send msg as Neousys DLL/SO is not loaded") - else: - tx_msg = NeousysCanMsg( - msg.arbitration_id, 0, 0, msg.dlc, (c_ubyte * 8)(*msg.data) - ) + tx_msg = NeousysCanMsg( + msg.arbitration_id, 0, 0, msg.dlc, (c_ubyte * 8)(*msg.data) + ) - if ( - NEOUSYS_CANLIB.CAN_Send(self.channel, byref(tx_msg), sizeof(tx_msg)) - == 0 - ): - logger.error("Neousys Can can't send message") + if NEOUSYS_CANLIB.CAN_Send(self.channel, byref(tx_msg), sizeof(tx_msg)) == 0: + raise CanOperationError("Neousys Can can't send message") def _recv_internal(self, timeout): - msg = None - - if not self.queue.empty(): - msg = self.queue.get() - - return msg, False + try: + return self.queue.get(), False + except queue.Empty: + return None, False def _neousys_recv_cb(self, msg, sizeof_msg): """ @@ -242,10 +230,10 @@ def _neousys_recv_cb(self, msg, sizeof_msg): # Reading happens in Callback function and # with Python-CAN it happens polling # so cache stuff in array to for poll - if not self.queue.full(): + try: self.queue.put(msg) - else: - logger.error("Neousys message Queue is full") + except queue.Full: + raise CanOperationError("Neousys message Queue is full") from None def _neousys_status_cb(self, status): """ @@ -255,8 +243,7 @@ def _neousys_status_cb(self, status): logger.info("%s _neousys_status_cb: %d", self.init_config, status) def shutdown(self): - if NEOUSYS_CANLIB is not None: - NEOUSYS_CANLIB.CAN_Stop(self.channel) + NEOUSYS_CANLIB.CAN_Stop(self.channel) def fileno(self): # Return an invalid file descriptor as not used @@ -264,8 +251,5 @@ def fileno(self): @staticmethod def _detect_available_configs(): - channels = [] - # There is only one channel - channels.append({"interface": "neousys", "channel": 0}) - return channels + return [{"interface": "neousys", "channel": 0}] From d94bad0cceb534f5926b7ca758fe730307f38c86 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 17:11:33 +0200 Subject: [PATCH 0669/1235] remove unused code from neousys --- can/interfaces/neousys/neousys.py | 34 +++++++++---------------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index 1950bb1d0..d358a6cc3 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -17,7 +17,7 @@ import queue import logging import platform -import time +from time import time from ctypes import ( byref, @@ -177,11 +177,10 @@ def __init__(self, channel, device=0, bitrate=500000, **kwargs): if NEOUSYS_CANLIB.CAN_Start(channel) == 0: raise CanInitializationError("Neousys CAN bus Start Error") - def send(self, msg, timeout=None): + def send(self, msg, timeout=None) -> None: """ :param msg: message to send :param timeout: timeout is not used here - :return: """ tx_msg = NeousysCanMsg( @@ -197,28 +196,20 @@ def _recv_internal(self, timeout): except queue.Empty: return None, False - def _neousys_recv_cb(self, msg, sizeof_msg): + def _neousys_recv_cb(self, msg, sizeof_msg) -> None: """ - :param msg struct CAN_MSG - :param sizeof_msg message number - :return: + :param msg: struct CAN_MSG + :param sizeof_msg: message number """ - remote_frame = False - extended_frame = False - msg_bytes = bytearray(msg.contents.data) - - if msg.contents.flags & NEOUSYS_CAN_MSG_REMOTE_FRAME: - remote_frame = True - - if msg.contents.flags & NEOUSYS_CAN_MSG_EXTENDED_ID: - extended_frame = True + remote_frame = bool(msg.contents.flags & NEOUSYS_CAN_MSG_REMOTE_FRAME) + extended_frame = bool(msg.contents.flags & NEOUSYS_CAN_MSG_EXTENDED_ID) if msg.contents.flags & NEOUSYS_CAN_MSG_DATA_LOST: logger.error("_neousys_recv_cb flag CAN_MSG_DATA_LOST") msg = Message( - timestamp=time.time(), + timestamp=time(), arbitration_id=msg.contents.id, is_remote_frame=remote_frame, is_extended_id=extended_frame, @@ -235,20 +226,15 @@ def _neousys_recv_cb(self, msg, sizeof_msg): except queue.Full: raise CanOperationError("Neousys message Queue is full") from None - def _neousys_status_cb(self, status): + def _neousys_status_cb(self, status) -> None: """ - :param status BUS Status - :return: + :param status: BUS Status """ logger.info("%s _neousys_status_cb: %d", self.init_config, status) def shutdown(self): NEOUSYS_CANLIB.CAN_Stop(self.channel) - def fileno(self): - # Return an invalid file descriptor as not used - return -1 - @staticmethod def _detect_available_configs(): # There is only one channel From 1f5b03a4b54e91a43fee6aafdc77ebbbaba2bf13 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 15:19:44 +0000 Subject: [PATCH 0670/1235] Format code with black --- can/interfaces/neousys/neousys.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index d358a6cc3..3b8e2737d 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -36,7 +36,11 @@ from ctypes import CDLL from can import BusABC, Message -from ...exceptions import CanInitializationError, CanOperationError, CanInterfaceNotImplementedError +from ...exceptions import ( + CanInitializationError, + CanOperationError, + CanInterfaceNotImplementedError, +) logger = logging.getLogger(__name__) @@ -151,14 +155,10 @@ def __init__(self, channel, device=0, bitrate=500000, **kwargs): self.queue = queue.Queue() # Init with accept all and wanted bitrate - self.init_config = NeousysCanSetup( - bitrate, NEOUSYS_CAN_MSG_USE_ID_FILTER, 0, 0 - ) + self.init_config = NeousysCanSetup(bitrate, NEOUSYS_CAN_MSG_USE_ID_FILTER, 0, 0) self._neousys_recv_cb = NEOUSYS_CAN_MSG_CALLBACK(self._neousys_recv_cb) - self._neousys_status_cb = NEOUSYS_CAN_STATUS_CALLBACK( - self._neousys_status_cb - ) + self._neousys_status_cb = NEOUSYS_CAN_STATUS_CALLBACK(self._neousys_status_cb) if NEOUSYS_CANLIB.CAN_RegisterReceived(0, self._neousys_recv_cb) == 0: raise CanInitializationError("Neousys CAN bus Setup receive callback") From cf2b19f2a8be8c02c171bd77b405391e51eb7931 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 17:32:07 +0200 Subject: [PATCH 0671/1235] fix detect_available_configs() --- can/interfaces/kvaser/canlib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 59ca187a1..9d6d6b64a 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -680,8 +680,9 @@ def _detect_available_configs(): num_channels = ctypes.c_int(0) try: canGetNumberOfChannels(ctypes.byref(num_channels)) - except CANLIBError: + except (CANLIBError, NameError): pass + return [ {"interface": "kvaser", "channel": channel} for channel in range(num_channels.value) From 8c669336bc854f19bffc5efd0fe1fda8f90df484 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 17:56:50 +0200 Subject: [PATCH 0672/1235] remove unused test flag --- test/test_neousys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_neousys.py b/test/test_neousys.py index d5a59ed0e..c2ae535f5 100644 --- a/test/test_neousys.py +++ b/test/test_neousys.py @@ -34,7 +34,7 @@ def setUp(self) -> None: can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Start = Mock(return_value=1) can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Send = Mock(return_value=1) can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Stop = Mock(return_value=1) - self.bus = can.Bus(channel=0, bustype="neousys", _testing=True) + self.bus = can.Bus(channel=0, bustype="neousys") def tearDown(self) -> None: if self.bus: @@ -67,7 +67,7 @@ def test_bus_creation(self) -> None: ) def test_bus_creation_bitrate(self) -> None: - self.bus = can.Bus(channel=0, bustype="neousys", bitrate=200000, _testing=True) + self.bus = can.Bus(channel=0, bustype="neousys", bitrate=200000) self.assertIsInstance(self.bus, neousys.NeousysBus) CAN_Start_args = ( can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup.call_args[0] From 34488616ef7ea69901aeca7aef2f4b84238fdc00 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 17:57:18 +0200 Subject: [PATCH 0673/1235] support timeouts while receiving --- can/interfaces/neousys/neousys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index 3b8e2737d..d79a0a572 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -192,7 +192,7 @@ def send(self, msg, timeout=None) -> None: def _recv_internal(self, timeout): try: - return self.queue.get(), False + return self.queue.get(block=True, timeout=timeout), False except queue.Empty: return None, False From 16edfad90bc5632f5f49991c0e32be42b3d32e76 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 17:58:54 +0200 Subject: [PATCH 0674/1235] remove confusing parameter documentation --- can/interfaces/neousys/neousys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index d79a0a572..c996c8298 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -141,7 +141,7 @@ def __init__(self, channel, device=0, bitrate=500000, **kwargs): """ :param channel: channel number :param device: device number - :param bitrate: bit rate. Renamed to bitrate in next release. + :param bitrate: bit rate. """ super().__init__(channel, **kwargs) From 6e078fc4154744a0d7f91ae2604bd843770126e1 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 18:12:17 +0200 Subject: [PATCH 0675/1235] Adapt virtual interface --- can/interfaces/virtual.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index a00e60d71..1f36f1f07 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -15,16 +15,17 @@ from threading import RLock from random import randint -from can import CanError +from can import CanOperationError from can.bus import BusABC from can.message import Message +from can.typechecking import AutoDetectedConfig logger = logging.getLogger(__name__) # Channels are lists of queues, one for each connection if TYPE_CHECKING: - # https://mypy.readthedocs.io/en/stable/common_issues.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime + # https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime channels: Dict[Optional[Any], List[queue.Queue[Message]]] = {} else: channels = {} @@ -81,12 +82,12 @@ def __init__( self.channel.append(self.queue) def _check_if_open(self) -> None: - """Raises CanError if the bus is not open. + """Raises :class:`~can.CanOperationError` if the bus is not open. Has to be called in every method that accesses the bus. """ if not self._open: - raise CanError("Operation on closed bus") + raise CanOperationError("Cannot operate on a closed bus") def _recv_internal( self, timeout: Optional[float] @@ -116,8 +117,9 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: bus_queue.put(msg_copy, block=True, timeout=timeout) except queue.Full: all_sent = False + if not all_sent: - raise CanError("Could not send message to one or more recipients") + raise CanOperationError("Could not send message to one or more recipients") def shutdown(self) -> None: if self._open: @@ -131,7 +133,7 @@ def shutdown(self) -> None: del channels[self.channel_id] @staticmethod - def _detect_available_configs(): + def _detect_available_configs() -> List[AutoDetectedConfig]: """ Returns all currently used channels as well as one other currently unused channel. @@ -146,7 +148,9 @@ def _detect_available_configs(): available_channels = list(channels.keys()) # find a currently unused channel - get_extra = lambda: "channel-{}".format(randint(0, 9999)) + def get_extra(): + return f"channel-{randint(0, 9999)}" + extra = get_extra() while extra in available_channels: extra = get_extra() From 9c0325babb63a0c610d5fce16b982e3248990f2e Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 16:13:11 +0000 Subject: [PATCH 0676/1235] Format code with black --- can/interfaces/virtual.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 1f36f1f07..f6d071723 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -59,7 +59,7 @@ def __init__( channel: Any = None, receive_own_messages: bool = False, rx_queue_size: int = 0, - **kwargs: Any + **kwargs: Any, ) -> None: super().__init__( channel=channel, receive_own_messages=receive_own_messages, **kwargs From 759b4b2c3081ec5edceebe0ac53077f3b8575556 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 15:12:54 +0200 Subject: [PATCH 0677/1235] fix documentation copy-pasta error in exceptions.py --- can/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/exceptions.py b/can/exceptions.py index 0ffacefd9..0f41dfa36 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -6,7 +6,7 @@ +-- ... +-- CanError (python-can) +-- CanInterfaceNotImplementedError - +-- CanInterfaceNotImplementedError + +-- CanInitializationError +-- CanOperationError +-- CanTimeoutError From c191b6e36087d4901afa21204fab6a99f7b1f557 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 15:52:36 +0200 Subject: [PATCH 0678/1235] Adjust ics_neovi interface for #1046 --- can/interfaces/ics_neovi/__init__.py | 5 +- can/interfaces/ics_neovi/neovi_bus.py | 70 ++++++++++++++++++--------- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py index 1ac666b6c..548e3ea3f 100644 --- a/can/interfaces/ics_neovi/__init__.py +++ b/can/interfaces/ics_neovi/__init__.py @@ -1,4 +1,7 @@ """ """ -from can.interfaces.ics_neovi.neovi_bus import NeoViBus +from .neovi_bus import NeoViBus +from .neovi_bus import ICSApiError +from .neovi_bus import ICSInitializationError +from .neovi_bus import ICSOperationError diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index defe9850d..2a49e29d9 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -14,8 +14,10 @@ from collections import deque, defaultdict from itertools import cycle from threading import Event +from warnings import warn -from can import Message, CanError, BusABC +from can import Message, BusABC +from ...exceptions import CanError, CanTimeoutError, CanOperationError, CanInitializationError logger = logging.getLogger(__name__) @@ -76,27 +78,37 @@ class ICSApiError(CanError): def __init__( self, - error_number, - description_short, - description_long, - severity, - restart_needed, + error_code: int, + description_short: str, + description_long: str, + severity: int, + restart_needed: int, ): - super().__init__(description_short) - self.error_number = error_number + super().__init__(f"{description_short} {description_long}", error_code) self.description_short = description_short self.description_long = description_long self.severity = severity self.restart_needed = restart_needed == 1 - def __str__(self): - return "{} {}".format(self.description_short, self.description_long) + @property + def error_number(self) -> int: + """Deprecated. Renamed to :attr:`can.CanError.error_code`.""" + warn("ICSApiError::error_number has been renamed to error_code defined by CanError", DeprecationWarning) + return self.error_code @property - def is_critical(self): + def is_critical(self) -> bool: return self.severity == self.ICS_SPY_ERR_CRITICAL +class ICSInitializationError(ICSApiError, CanInitializationError): + pass + + +class ICSOperationError(ICSApiError, CanInitializationError): + pass + + class NeoViBus(BusABC): """ The CAN Bus implemented for the python_ics interface @@ -130,6 +142,12 @@ def __init__(self, channel, can_filters=None, **kwargs): Defaults to arbitration bitrate. :param override_library_name: Absolute path or relative path to the library including filename. + + :raise ImportError: + If *python-ics* is not available + :raise CanInitializationError: + If the bus could not be set up. + May or may not be a :class:`~ICSInitializationError`. """ if ics is None: raise ImportError("Please install python-ics") @@ -171,7 +189,7 @@ def __init__(self, channel, can_filters=None, **kwargs): ) except ics.RuntimeError as re: logger.error(re) - err = ICSApiError(*ics.get_last_api_error(self.dev)) + err = ICSInitializationError(*ics.get_last_api_error(self.dev)) try: self.shutdown() finally: @@ -246,6 +264,11 @@ def _detect_available_configs(): ] def _find_device(self, type_filter=None, serial=None): + """Returns the first matching device or raises an error. + + :raise CanInitializationError: + If not matching device could be found + """ if type_filter is not None: devices = ics.find_devices(type_filter) else: @@ -253,8 +276,7 @@ def _find_device(self, type_filter=None, serial=None): for device in devices: if serial is None or self.get_serial_number(device) == str(serial): - dev = device - break + return device else: msg = ["No device"] @@ -263,8 +285,7 @@ def _find_device(self, type_filter=None, serial=None): if serial is not None: msg.append("with serial {}".format(serial)) msg.append("found.") - raise Exception(" ".join(msg)) - return dev + raise CanInitializationError(" ".join(msg)) def _process_msg_queue(self, timeout=0.1): try: @@ -291,7 +312,7 @@ def _process_msg_queue(self, timeout=0.1): logger.warning("%d error(s) found", errors) for msg in ics.get_error_messages(self.dev): - error = ICSApiError(*msg) + error = ICSOperationError(*msg) logger.warning(error) def _get_timestamp_for_msg(self, ics_msg): @@ -375,11 +396,16 @@ def send(self, msg, timeout=0): If timeout is exceeded, an exception will be raised. None blocks indefinitely. - :raises can.CanError: - if the message could not be sent + :raises ValueError: + if the message is invalid + :raises can.CanTimeoutError: + if sending timed out + :raises CanOperationError: + If the bus is closed or the message could otherwise not be sent. + May or may not be a :class:`~ICSOperationError`. """ if not ics.validate_hobject(self.dev): - raise CanError("bus not open") + raise CanOperationError("bus not open") message = ics.SpyMessage() flag0 = 0 @@ -423,10 +449,10 @@ def send(self, msg, timeout=0): try: ics.transmit_messages(self.dev, message) except ics.RuntimeError: - raise ICSApiError(*ics.get_last_api_error(self.dev)) + raise ICSOperationError(*ics.get_last_api_error(self.dev)) # If timeout is set, wait for ACK # This requires a notifier for the bus or # some other thread calling recv periodically if timeout != 0 and not self.message_receipts[receipt_key].wait(timeout): - raise CanError("Transmit timeout") + raise CanTimeoutError("Transmit timeout") From 3d7f87a769b3063094dd148a2119c5659db8f653 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 13:56:22 +0000 Subject: [PATCH 0679/1235] Format code with black --- can/interfaces/ics_neovi/neovi_bus.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 2a49e29d9..096bc0621 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -17,7 +17,12 @@ from warnings import warn from can import Message, BusABC -from ...exceptions import CanError, CanTimeoutError, CanOperationError, CanInitializationError +from ...exceptions import ( + CanError, + CanTimeoutError, + CanOperationError, + CanInitializationError, +) logger = logging.getLogger(__name__) @@ -93,7 +98,10 @@ def __init__( @property def error_number(self) -> int: """Deprecated. Renamed to :attr:`can.CanError.error_code`.""" - warn("ICSApiError::error_number has been renamed to error_code defined by CanError", DeprecationWarning) + warn( + "ICSApiError::error_number has been renamed to error_code defined by CanError", + DeprecationWarning, + ) return self.error_code @property From 1b72dd3743a8d451e6a36e79bd5102b43e8b3c4b Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 8 Jun 2021 18:30:38 +0200 Subject: [PATCH 0680/1235] complete typing --- can/interfaces/vector/canlib.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index c5838de21..4e5a0e17d 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -39,6 +39,7 @@ time_perfcounter_correlation, ) from .exceptions import VectorError +from can.typechecking import AutoDetectedConfig # Define Module Logger # ==================== @@ -88,7 +89,7 @@ def __init__( tseg1_dbr=6, tseg2_dbr=3, **kwargs, - ): + ) -> None: """ :param list channel: The channel indexes to create this bus with. @@ -322,7 +323,7 @@ def __init__( self._is_filtered = False super().__init__(channel=channel, can_filters=can_filters, **kwargs) - def _apply_filters(self, filters): + def _apply_filters(self, filters) -> None: if filters: # Only up to one filter per ID type allowed if len(filters) == 1 or ( @@ -599,22 +600,22 @@ def _build_xl_can_tx_event(msg: Message) -> xlclass.XLcanTxEvent: return xl_can_tx_event - def flush_tx_buffer(self): + def flush_tx_buffer(self) -> None: xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask) - def shutdown(self): + def shutdown(self) -> None: xldriver.xlDeactivateChannel(self.port_handle, self.mask) xldriver.xlClosePort(self.port_handle) xldriver.xlCloseDriver() - def reset(self): + def reset(self) -> None: xldriver.xlDeactivateChannel(self.port_handle, self.mask) xldriver.xlActivateChannel( self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0 ) @staticmethod - def _detect_available_configs(): + def _detect_available_configs() -> List[AutoDetectedConfig]: configs = [] channel_configs = get_channel_configs() LOG.info("Found %d channels", len(channel_configs)) From 6cb4b9420fe5650fa34f79eda6c89a94c6e4a7b9 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 8 Jun 2021 22:51:44 +0200 Subject: [PATCH 0681/1235] Remove remaining encoding comments (#1075) * remove unused import * remove remaining 'coding: utf-8' comments * Format code with black --- can/interfaces/gs_usb.py | 2 +- can/interfaces/nixnet.py | 2 -- test/__init__.py | 1 - test/back2back_test.py | 1 - test/config.py | 1 - test/contextmanager_test.py | 1 - test/data/__init__.py | 1 - test/data/example_data.py | 1 - test/listener_test.py | 1 - test/logformats_test.py | 1 - test/message_helper.py | 1 - test/network_test.py | 1 - test/notifier_test.py | 1 - test/serial_test.py | 1 - test/simplecyclic_test.py | 1 - test/test_kvaser.py | 1 - 16 files changed, 1 insertion(+), 17 deletions(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index ff6ba634e..447cdf47f 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -1,4 +1,4 @@ -from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union +from typing import Optional, Tuple from gs_usb.gs_usb import GsUsb from gs_usb.gs_usb_frame import GsUsbFrame diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index f8b54ea4e..993c215dd 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ NI-XNET interface module. diff --git a/test/__init__.py b/test/__init__.py index 394a0a067..4265cc3e6 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,2 +1 @@ #!/usr/bin/env python -# coding: utf-8 diff --git a/test/back2back_test.py b/test/back2back_test.py index 1950c5fa5..66104a43e 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests two buses attached to each other. diff --git a/test/config.py b/test/config.py index 58ad780fc..fc1bca6c2 100644 --- a/test/config.py +++ b/test/config.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module contains various configuration for the tests. diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py index 95785b128..014dfb121 100644 --- a/test/contextmanager_test.py +++ b/test/contextmanager_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests the context manager of Bus and Notifier classes diff --git a/test/data/__init__.py b/test/data/__init__.py index 394a0a067..4265cc3e6 100644 --- a/test/data/__init__.py +++ b/test/data/__init__.py @@ -1,2 +1 @@ #!/usr/bin/env python -# coding: utf-8 diff --git a/test/data/example_data.py b/test/data/example_data.py index d642887cf..d41544334 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module contains some example data, like messages of different diff --git a/test/listener_test.py b/test/listener_test.py index 83b4a63fc..e5abd94a2 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ """ diff --git a/test/logformats_test.py b/test/logformats_test.py index 8bdb4c215..ae3106aa7 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This test module test the separate reader/writer combinations of the can.io.* diff --git a/test/message_helper.py b/test/message_helper.py index 5f437dbde..1e7989156 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module contains a helper for writing test cases that need to compare messages. diff --git a/test/network_test.py b/test/network_test.py index ad02fae41..a4e40e901 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 import unittest diff --git a/test/notifier_test.py b/test/notifier_test.py index 0a60bd25d..c9d8f4a27 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 import unittest import time diff --git a/test/serial_test.py b/test/serial_test.py index 29f1ea2b3..c9c035634 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module is testing the serial interface. diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 82f0850a7..4b9ded43f 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests cyclic send tasks. diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 3acc2731d..2637ae0e7 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ """ From fde2544c3fc37bdeed27c66738e9cbf2e40e4db4 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 8 Jun 2021 22:55:36 +0200 Subject: [PATCH 0682/1235] Repair Travis CI ARM setup (#1070) Elevate python installation on Travis CI to fix ARM setup Rewrite ARM Travis CI setup --- .travis.yml | 10 ++++++++++ test/back2back_test.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/.travis.yml b/.travis.yml index a7aafd5e7..806aa1215 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,6 +53,16 @@ jobs: # Travis doesn't seem to provide Python binaries yet for this arch - sudo update-alternatives --install /usr/bin/python python $(which python3) 10 - sudo update-alternatives --install /usr/bin/pip pip $(which pip3) 10 + # The below is the same as in the Socketcan job but with elevated privileges + install: + - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi + - travis_retry sudo python setup.py install + script: + - | + # install tox + travis_retry sudo pip install tox + # Run the tests + sudo tox -e travis - stage: documentation name: "Sphinx Build" diff --git a/test/back2back_test.py b/test/back2back_test.py index 66104a43e..7d3274481 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -88,6 +88,8 @@ def _send_and_receive(self, msg: can.Message) -> None: # Add 1 to arbitration ID to make it a different message msg.arbitration_id += 1 self.bus2.send(msg) + # Some buses may receive their own messages. Remove it from the queue + self.bus2.recv(0) recv_msg = self.bus1.recv(self.TIMEOUT) self._check_received_message(recv_msg, msg) @@ -151,6 +153,8 @@ def test_message_is_rx(self): is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3], is_rx=False ) self.bus1.send(msg) + # Some buses may receive their own messages. Remove it from the queue + self.bus1.recv(0) self_recv_msg = self.bus2.recv(self.TIMEOUT) self.assertIsNotNone(self_recv_msg) self.assertTrue(self_recv_msg.is_rx) @@ -237,6 +241,8 @@ def test_fileno(self): def test_timestamp_is_absolute(self): """Tests that the timestamp that is returned is an absolute one.""" self.bus2.send(can.Message()) + # Some buses may receive their own messages. Remove it from the queue + self.bus2.recv(0) message = self.bus1.recv(self.TIMEOUT) # The allowed delta is still quite large to make this work on the CI server self.assertAlmostEqual(message.timestamp, time(), delta=self.TIMEOUT) @@ -262,6 +268,10 @@ def test_sub_second_timestamp_resolution(self): sub_second_fraction_2 = recv_msg_2.timestamp % 1 self.assertGreater(sub_second_fraction_1 + sub_second_fraction_2, 0) + # Some buses may receive their own messages. Remove it from the queue + self.bus2.recv(0) + self.bus2.recv(0) + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class BasicTestSocketCan(Back2BackTestCase): From 2c07d543ab3c21332b91561d33507ce6aa52896f Mon Sep 17 00:00:00 2001 From: Fabio Crestani Date: Tue, 8 Jun 2021 23:27:23 +0200 Subject: [PATCH 0683/1235] Fix test for CAN-FD direction in BLF reader --- test/logformats_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/logformats_test.py b/test/logformats_test.py index fbca43306..d1da03da2 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -602,6 +602,7 @@ def test_can_fd_message_64(self): channel=0x10, dlc=64, is_fd=True, + is_rx=False, bitrate_switch=True, error_state_indicator=True, ) From b897bd73f0e1597ebd584d39da4b4308d360638f Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Tue, 8 Jun 2021 15:33:41 -0400 Subject: [PATCH 0684/1235] ICSOperationError base exception class should be CanOperationError --- can/interfaces/ics_neovi/neovi_bus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 096bc0621..bb043002a 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -113,7 +113,7 @@ class ICSInitializationError(ICSApiError, CanInitializationError): pass -class ICSOperationError(ICSApiError, CanInitializationError): +class ICSOperationError(ICSApiError, CanOperationError): pass From 0f06e212b9b187afc4515e44b91a0720dd2966bd Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 8 Jun 2021 22:06:29 +0000 Subject: [PATCH 0685/1235] Format code with black --- can/message.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index 8ce0bdd22..11df695be 100644 --- a/can/message.py +++ b/can/message.py @@ -228,7 +228,9 @@ def __deepcopy__(self, memo: dict) -> "Message": error_state_indicator=self.error_state_indicator, ) - def _check(self) -> None: # pylint: disable=too-many-branches; it's still simple code + def _check( + self, + ) -> None: # pylint: disable=too-many-branches; it's still simple code """Checks if the message parameters are valid. Assumes that the attribute types are already correct. From 94e9693c2dd4477db9b998597ffc7f32cc5095cf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 8 Jun 2021 22:26:50 +0000 Subject: [PATCH 0686/1235] Format code with black --- can/io/blf.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/can/io/blf.py b/can/io/blf.py index c9aa7165f..90296e81d 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -312,7 +312,23 @@ def _parse_data(self, data): channel=channel - 1, ) elif obj_type == CAN_FD_MESSAGE_64: - channel, dlc, valid_bytes, _, can_id, _, fd_flags, _, _, _, _, _, direction, _, _ = unpack_can_fd_64_msg(data, pos) + ( + channel, + dlc, + valid_bytes, + _, + can_id, + _, + fd_flags, + _, + _, + _, + _, + _, + direction, + _, + _, + ) = unpack_can_fd_64_msg(data, pos) pos += can_fd_64_msg_size yield Message( timestamp=timestamp, From ed107afa5833315921c52d4da95b99ddb32cb6ce Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Jun 2021 09:32:20 +0200 Subject: [PATCH 0687/1235] Adjust vector interface to new exceptions --- can/interfaces/vector/__init__.py | 2 +- can/interfaces/vector/canlib.py | 73 +++++++++++++++-------------- can/interfaces/vector/exceptions.py | 21 +++++++-- can/interfaces/vector/xldriver.py | 68 +++++++++++++++------------ 4 files changed, 94 insertions(+), 70 deletions(-) diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index 7fc567017..cdeb1d3cb 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -2,4 +2,4 @@ """ from .canlib import VectorBus, VectorChannelConfig -from .exceptions import VectorError +from .exceptions import VectorError, VectorOperationError, VectorInitializationError diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 4e5a0e17d..ce3c98fa2 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,9 +10,7 @@ import logging import time import os -from typing import List, Optional, Tuple - -import typing +from typing import List, Optional, Tuple, Sequence, NamedTuple try: # Try builtin Python 3 Windows API @@ -31,22 +29,22 @@ # Import Modules # ============== -from can import BusABC, Message +from can import BusABC, Message, CanInterfaceNotImplementedError, CanInitializationError from can.util import ( len2dlc, dlc2len, deprecated_args_alias, time_perfcounter_correlation, ) -from .exceptions import VectorError from can.typechecking import AutoDetectedConfig # Define Module Logger # ==================== LOG = logging.getLogger(__name__) -# Import Vector API module -# ======================== +# Import Vector API modules +# ========================= +from .exceptions import VectorError, VectorInitializationError, VectorOperationError from . import xldefine, xlclass # Import safely Vector API module for Travis tests @@ -126,14 +124,20 @@ def __init__( Bus timing value tseg1 (data) :param int tseg2_dbr: Bus timing value tseg2 (data) + + :raise can.CanInterfaceNotImplementedError: + If the current operating system is not supported or the driver could not be loaded. + :raise can.CanInitializationError: + If the bus could not be set up. + This may or may not be a :class:`can.interfaces.vector.VectorInitializationError`. """ if os.name != "nt" and not kwargs.get("_testing", False): - raise OSError( + raise CanInterfaceNotImplementedError( 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") + raise CanInterfaceNotImplementedError("The Vector API has not been loaded") self.poll_interval = poll_interval if isinstance(channel, (list, tuple)): @@ -165,7 +169,7 @@ def __init__( self.channels = channel_index else: # Is there any better way to raise the error? - raise Exception( + raise CanInitializationError( "None of the configured channels could be found on the specified hardware." ) @@ -190,7 +194,7 @@ def __init__( # If hardware is unavailable, this function returns -1. # Raise an exception as if the driver # would have signalled XL_ERR_HW_NOT_PRESENT. - raise VectorError( + raise VectorInitializationError( xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name, "xlGetChannelIndex", @@ -294,9 +298,9 @@ def __init__( xldriver.xlActivateChannel( self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0 ) - except VectorError: + except VectorOperationError as error: self.shutdown() - raise + raise VectorInitializationError.from_generic(error) from None # Calculate time offset for absolute timestamps offset = xlclass.XLuint64() @@ -305,7 +309,7 @@ def __init__( ts, perfcounter = time_perfcounter_correlation() try: xldriver.xlGetSyncTime(self.port_handle, offset) - except VectorError: + except VectorInitializationError: xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) current_perfcounter = time.perf_counter() now = ts + (current_perfcounter - perfcounter) @@ -313,11 +317,11 @@ def __init__( else: try: xldriver.xlGetSyncTime(self.port_handle, offset) - except VectorError: + except VectorInitializationError: xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) self._time_offset = time.time() - offset.value * 1e-9 - except VectorError: + except VectorInitializationError: self._time_offset = 0.0 self._is_filtered = False @@ -341,7 +345,7 @@ def _apply_filters(self, filters) -> None: if can_filter.get("extended") else xldefine.XL_AcceptanceFilter.XL_CAN_STD, ) - except VectorError as exc: + except VectorOperationError as exc: LOG.warning("Could not set filters: %s", exc) # go to fallback else: @@ -368,7 +372,7 @@ def _apply_filters(self, filters) -> None: 0x0, xldefine.XL_AcceptanceFilter.XL_CAN_STD, ) - except VectorError as exc: + except VectorOperationError as exc: LOG.warning("Could not reset filters: %s", exc) def _recv_internal( @@ -383,7 +387,7 @@ def _recv_internal( else: msg = self._recv_can() - except VectorError as exc: + except VectorOperationError as exc: if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY: raise else: @@ -426,7 +430,7 @@ def _recv_canfd(self) -> Optional[Message]: timestamp = xl_can_rx_event.timeStamp * 1e-9 channel = self.index_to_channel.get(xl_can_rx_event.chanIndex) - msg = Message( + return Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, is_extended_id=bool( @@ -450,7 +454,6 @@ def _recv_canfd(self) -> Optional[Message]: dlc=dlc, data=data_struct.data[:dlc], ) - return msg def _recv_can(self) -> Optional[Message]: xl_event = xlclass.XLevent() @@ -467,7 +470,7 @@ def _recv_can(self) -> Optional[Message]: timestamp = xl_event.timeStamp * 1e-9 channel = self.index_to_channel.get(xl_event.chanIndex) - msg = Message( + return Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, is_extended_id=bool( @@ -487,7 +490,6 @@ def _recv_can(self) -> Optional[Message]: data=xl_event.tagData.msg.data[:dlc], channel=channel, ) - return msg def handle_can_event(self, event: xlclass.XLevent) -> None: """Handle non-message CAN events. @@ -496,9 +498,7 @@ def handle_can_event(self, event: xlclass.XLevent) -> None: when `event.tag` is not `XL_RECEIVE_MSG`. Subclasses can implement this method. :param event: XLevent that could have a `XL_CHIP_STATE`, `XL_TIMER` or `XL_SYNC_PULSE` tag. - :return: None """ - pass def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: """Handle non-message CAN FD events. @@ -509,27 +509,25 @@ def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: :param event: `XLcanRxEvent` that could have a `XL_CAN_EV_TAG_RX_ERROR`, `XL_CAN_EV_TAG_TX_ERROR`, `XL_TIMER` or `XL_CAN_EV_TAG_CHIP_STATE` tag. - :return: None """ - pass - def send(self, msg: Message, timeout: typing.Optional[float] = None): + def send(self, msg: Message, timeout: Optional[float] = None): self._send_sequence([msg]) - def _send_sequence(self, msgs: typing.Sequence[Message]) -> int: + def _send_sequence(self, msgs: Sequence[Message]) -> int: """Send messages and return number of successful transmissions.""" if self.fd: return self._send_can_fd_msg_sequence(msgs) else: return self._send_can_msg_sequence(msgs) - def _get_tx_channel_mask(self, msgs: typing.Sequence[Message]) -> int: + def _get_tx_channel_mask(self, msgs: Sequence[Message]) -> int: if len(msgs) == 1: return self.channel_masks.get(msgs[0].channel, self.mask) else: return self.mask - def _send_can_msg_sequence(self, msgs: typing.Sequence[Message]) -> int: + def _send_can_msg_sequence(self, msgs: Sequence[Message]) -> int: """Send CAN messages and return number of successful transmissions.""" mask = self._get_tx_channel_mask(msgs) message_count = ctypes.c_uint(len(msgs)) @@ -560,7 +558,7 @@ def _build_xl_event(msg: Message) -> xlclass.XLevent: return xl_event - def _send_can_fd_msg_sequence(self, msgs: typing.Sequence[Message]) -> int: + def _send_can_fd_msg_sequence(self, msgs: Sequence[Message]) -> int: """Send CAN FD messages and return number of successful transmissions.""" mask = self._get_tx_channel_mask(msgs) message_count = len(msgs) @@ -652,7 +650,7 @@ def _detect_available_configs() -> List[AutoDetectedConfig]: def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: """Open vector hardware configuration window. - :param int wait_for_finish: + :param wait_for_finish: Time to wait for user input in milliseconds. """ xldriver.xlPopupHwConfig(ctypes.c_char_p(), ctypes.c_uint(wait_for_finish)) @@ -670,7 +668,8 @@ def get_application_config( :return: Returns a tuple of the hardware type, the hardware index and the hardware channel. - :raises VectorError: + + :raises can.interfaces.vector.VectorInitializationError: Raises a VectorError when the application name does not exist in Vector Hardware Configuration. """ @@ -721,6 +720,10 @@ def set_application_config( hardware type are present. :param hw_channel: The channel index of the interface. + + :raises can.interfaces.vector.VectorInitializationError: + Raises a VectorError when the application name does not exist in + Vector Hardware Configuration. """ xldriver.xlSetApplConfig( app_name.encode(), @@ -746,7 +749,7 @@ def set_timer_rate(self, timer_rate_ms: int) -> None: xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) -class VectorChannelConfig(typing.NamedTuple): +class VectorChannelConfig(NamedTuple): name: str hwType: xldefine.XL_HardwareType hwIndex: int diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 739060188..cb1961986 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -1,7 +1,6 @@ -""" -""" +"""Exception/error declarations for the vector interface.""" -from can import CanError +from can import CanError, CanInitializationError, CanOperationError class VectorError(CanError): @@ -14,4 +13,18 @@ def __init__(self, error_code, error_string, function): self._args = error_code, error_string, function def __reduce__(self): - return VectorError, self._args, {} + return type(self), self._args, {} + + +class VectorInitializationError(VectorError, CanInitializationError): + + @staticmethod + def from_generic(error: VectorError) -> "VectorInitializationError": + return VectorInitializationError(*error._args) + + +class VectorOperationError(VectorError, CanOperationError): + + @staticmethod + def from_generic(error: VectorError) -> "VectorOperationError": + return VectorOperationError(*error._args) diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 5027cb9e6..32f260c2c 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -9,7 +9,7 @@ import ctypes import logging import platform -from .exceptions import VectorError +from .exceptions import VectorOperationError, VectorInitializationError # Define Module Logger # ==================== @@ -30,26 +30,34 @@ xlGetErrorString.restype = xlclass.XLstringType -def check_status(result, function, arguments): +def check_status_operation(result, function, arguments): + """Check the status and raise a :class:`VectorOperationError` on error.""" if result > 0: - raise VectorError(result, xlGetErrorString(result).decode(), function.__name__) + raise VectorOperationError(result, xlGetErrorString(result).decode(), function.__name__) + return result + + +def check_status_initialization(result, function, arguments): + """Check the status and raise a :class:`VectorInitializationError` on error.""" + if result > 0: + raise VectorInitializationError(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 +xlGetDriverConfig.errcheck = check_status_operation xlOpenDriver = _xlapi_dll.xlOpenDriver xlOpenDriver.argtypes = [] xlOpenDriver.restype = xlclass.XLstatus -xlOpenDriver.errcheck = check_status +xlOpenDriver.errcheck = check_status_initialization xlCloseDriver = _xlapi_dll.xlCloseDriver xlCloseDriver.argtypes = [] xlCloseDriver.restype = xlclass.XLstatus -xlCloseDriver.errcheck = check_status +xlCloseDriver.errcheck = check_status_operation xlGetApplConfig = _xlapi_dll.xlGetApplConfig xlGetApplConfig.argtypes = [ @@ -61,7 +69,7 @@ def check_status(result, function, arguments): ctypes.c_uint, ] xlGetApplConfig.restype = xlclass.XLstatus -xlGetApplConfig.errcheck = check_status +xlGetApplConfig.errcheck = check_status_initialization xlSetApplConfig = _xlapi_dll.xlSetApplConfig xlSetApplConfig.argtypes = [ @@ -73,7 +81,7 @@ def check_status(result, function, arguments): ctypes.c_uint, ] xlSetApplConfig.restype = xlclass.XLstatus -xlSetApplConfig.errcheck = check_status +xlSetApplConfig.errcheck = check_status_initialization xlGetChannelIndex = _xlapi_dll.xlGetChannelIndex xlGetChannelIndex.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] @@ -94,12 +102,12 @@ def check_status(result, function, arguments): ctypes.c_uint, ] xlOpenPort.restype = xlclass.XLstatus -xlOpenPort.errcheck = check_status +xlOpenPort.errcheck = check_status_initialization xlGetSyncTime = _xlapi_dll.xlGetSyncTime xlGetSyncTime.argtypes = [xlclass.XLportHandle, ctypes.POINTER(xlclass.XLuint64)] xlGetSyncTime.restype = xlclass.XLstatus -xlGetSyncTime.errcheck = check_status +xlGetSyncTime.errcheck = check_status_initialization xlGetChannelTime = _xlapi_dll.xlGetChannelTime xlGetChannelTime.argtypes = [ @@ -108,12 +116,12 @@ def check_status(result, function, arguments): ctypes.POINTER(xlclass.XLuint64), ] xlGetChannelTime.restype = xlclass.XLstatus -xlGetChannelTime.errcheck = check_status +xlGetChannelTime.errcheck = check_status_initialization xlClosePort = _xlapi_dll.xlClosePort xlClosePort.argtypes = [xlclass.XLportHandle] xlClosePort.restype = xlclass.XLstatus -xlClosePort.errcheck = check_status +xlClosePort.errcheck = check_status_operation xlSetNotification = _xlapi_dll.xlSetNotification xlSetNotification.argtypes = [ @@ -122,7 +130,7 @@ def check_status(result, function, arguments): ctypes.c_int, ] xlSetNotification.restype = xlclass.XLstatus -xlSetNotification.errcheck = check_status +xlSetNotification.errcheck = check_status_initialization xlCanSetChannelMode = _xlapi_dll.xlCanSetChannelMode xlCanSetChannelMode.argtypes = [ @@ -132,7 +140,7 @@ def check_status(result, function, arguments): ctypes.c_int, ] xlCanSetChannelMode.restype = xlclass.XLstatus -xlCanSetChannelMode.errcheck = check_status +xlCanSetChannelMode.errcheck = check_status_initialization xlActivateChannel = _xlapi_dll.xlActivateChannel xlActivateChannel.argtypes = [ @@ -142,12 +150,12 @@ def check_status(result, function, arguments): ctypes.c_uint, ] xlActivateChannel.restype = xlclass.XLstatus -xlActivateChannel.errcheck = check_status +xlActivateChannel.errcheck = check_status_operation xlDeactivateChannel = _xlapi_dll.xlDeactivateChannel xlDeactivateChannel.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] xlDeactivateChannel.restype = xlclass.XLstatus -xlDeactivateChannel.errcheck = check_status +xlDeactivateChannel.errcheck = check_status_operation xlCanFdSetConfiguration = _xlapi_dll.xlCanFdSetConfiguration xlCanFdSetConfiguration.argtypes = [ @@ -156,7 +164,7 @@ def check_status(result, function, arguments): ctypes.POINTER(xlclass.XLcanFdConf), ] xlCanFdSetConfiguration.restype = xlclass.XLstatus -xlCanFdSetConfiguration.errcheck = check_status +xlCanFdSetConfiguration.errcheck = check_status_initialization xlReceive = _xlapi_dll.xlReceive xlReceive.argtypes = [ @@ -165,12 +173,12 @@ def check_status(result, function, arguments): ctypes.POINTER(xlclass.XLevent), ] xlReceive.restype = xlclass.XLstatus -xlReceive.errcheck = check_status +xlReceive.errcheck = check_status_operation xlCanReceive = _xlapi_dll.xlCanReceive xlCanReceive.argtypes = [xlclass.XLportHandle, ctypes.POINTER(xlclass.XLcanRxEvent)] xlCanReceive.restype = xlclass.XLstatus -xlCanReceive.errcheck = check_status +xlCanReceive.errcheck = check_status_operation xlCanSetChannelBitrate = _xlapi_dll.xlCanSetChannelBitrate xlCanSetChannelBitrate.argtypes = [ @@ -179,7 +187,7 @@ def check_status(result, function, arguments): ctypes.c_ulong, ] xlCanSetChannelBitrate.restype = xlclass.XLstatus -xlCanSetChannelBitrate.errcheck = check_status +xlCanSetChannelBitrate.errcheck = check_status_initialization xlCanSetChannelParams = _xlapi_dll.xlCanSetChannelParams xlCanSetChannelParams.argtypes = [ @@ -188,7 +196,7 @@ def check_status(result, function, arguments): ctypes.POINTER(xlclass.XLchipParams), ] xlCanSetChannelParams.restype = xlclass.XLstatus -xlCanSetChannelParams.errcheck = check_status +xlCanSetChannelParams.errcheck = check_status_operation xlCanTransmit = _xlapi_dll.xlCanTransmit xlCanTransmit.argtypes = [ @@ -198,7 +206,7 @@ def check_status(result, function, arguments): ctypes.POINTER(xlclass.XLevent), ] xlCanTransmit.restype = xlclass.XLstatus -xlCanTransmit.errcheck = check_status +xlCanTransmit.errcheck = check_status_operation xlCanTransmitEx = _xlapi_dll.xlCanTransmitEx xlCanTransmitEx.argtypes = [ @@ -209,12 +217,12 @@ def check_status(result, function, arguments): ctypes.POINTER(xlclass.XLcanTxEvent), ] xlCanTransmitEx.restype = xlclass.XLstatus -xlCanTransmitEx.errcheck = check_status +xlCanTransmitEx.errcheck = check_status_operation xlCanFlushTransmitQueue = _xlapi_dll.xlCanFlushTransmitQueue xlCanFlushTransmitQueue.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] xlCanFlushTransmitQueue.restype = xlclass.XLstatus -xlCanFlushTransmitQueue.errcheck = check_status +xlCanFlushTransmitQueue.errcheck = check_status_operation xlCanSetChannelAcceptance = _xlapi_dll.xlCanSetChannelAcceptance xlCanSetChannelAcceptance.argtypes = [ @@ -225,32 +233,32 @@ def check_status(result, function, arguments): ctypes.c_uint, ] xlCanSetChannelAcceptance.restype = xlclass.XLstatus -xlCanSetChannelAcceptance.errcheck = check_status +xlCanSetChannelAcceptance.errcheck = check_status_operation xlCanResetAcceptance = _xlapi_dll.xlCanResetAcceptance xlCanResetAcceptance.argtypes = [xlclass.XLportHandle, xlclass.XLaccess, ctypes.c_uint] xlCanResetAcceptance.restype = xlclass.XLstatus -xlCanResetAcceptance.errcheck = check_status +xlCanResetAcceptance.errcheck = check_status_operation xlCanRequestChipState = _xlapi_dll.xlCanRequestChipState xlCanRequestChipState.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] xlCanRequestChipState.restype = xlclass.XLstatus -xlCanRequestChipState.errcheck = check_status +xlCanRequestChipState.errcheck = check_status_operation xlCanSetChannelOutput = _xlapi_dll.xlCanSetChannelOutput xlCanSetChannelOutput.argtypes = [xlclass.XLportHandle, xlclass.XLaccess, ctypes.c_char] xlCanSetChannelOutput.restype = xlclass.XLstatus -xlCanSetChannelOutput.errcheck = check_status +xlCanSetChannelOutput.errcheck = check_status_operation xlPopupHwConfig = _xlapi_dll.xlPopupHwConfig xlPopupHwConfig.argtypes = [ctypes.c_char_p, ctypes.c_uint] xlPopupHwConfig.restype = xlclass.XLstatus -xlPopupHwConfig.errcheck = check_status +xlPopupHwConfig.errcheck = check_status_operation xlSetTimerRate = _xlapi_dll.xlSetTimerRate xlSetTimerRate.argtypes = [xlclass.XLportHandle, ctypes.c_ulong] xlSetTimerRate.restype = xlclass.XLstatus -xlSetTimerRate.errcheck = check_status +xlSetTimerRate.errcheck = check_status_operation xlGetEventString = _xlapi_dll.xlGetEventString xlGetEventString.argtypes = [ctypes.POINTER(xlclass.XLevent)] From 5b8c43c7c656c665aae8f11ed29cc58e9d5e7682 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Jun 2021 09:42:26 +0200 Subject: [PATCH 0688/1235] Add test cases for new error vector types --- test/test_vector.py | 46 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/test/test_vector.py b/test/test_vector.py index 09ad7c9c3..f61032f38 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -19,6 +19,8 @@ xldefine, xlclass, VectorError, + VectorInitializationError, + VectorOperationError, VectorChannelConfig, ) @@ -285,21 +287,43 @@ def test_called_without_testing_argument(self) -> None: can.Bus(channel=0, bustype="vector") def test_vector_error_pickle(self) -> None: - error_code = 118 - error_string = "XL_ERROR" - function = "function_name" + for error_type in [VectorError, VectorInitializationError, VectorOperationError]: + with self.subTest(f"error_type = {error_type.__name__}"): - exc = VectorError(error_code, error_string, function) + error_code = 118 + error_string = "XL_ERROR" + function = "function_name" - # pickle and unpickle - p = pickle.dumps(exc) - exc_unpickled: VectorError = pickle.loads(p) + exc = error_type(error_code, error_string, function) - self.assertEqual(str(exc), str(exc_unpickled)) - self.assertEqual(error_code, exc_unpickled.error_code) + # pickle and unpickle + p = pickle.dumps(exc) + exc_unpickled: VectorError = pickle.loads(p) - with pytest.raises(VectorError): - raise exc_unpickled + self.assertEqual(str(exc), str(exc_unpickled)) + self.assertEqual(error_code, exc_unpickled.error_code) + + with pytest.raises(error_type): + raise exc_unpickled + + def test_vector_subteype_error_from_generic(self) -> None: + for error_type in [VectorInitializationError, VectorOperationError]: + with self.subTest(f"error_type = {error_type.__name__}"): + + error_code = 118 + error_string = "XL_ERROR" + function = "function_name" + + generic = VectorError(error_code, error_string, function) + + # pickle and unpickle + specififc: VectorError = error_type.from_generic(generic) + + self.assertEqual(str(generic), str(specififc)) + self.assertEqual(error_code, specififc.error_code) + + with pytest.raises(error_type): + raise specififc class TestVectorChannelConfig: From bdc462a710330eb6df56fcb69a417f8da3247e2d Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Jun 2021 07:43:31 +0000 Subject: [PATCH 0689/1235] Format code with black --- can/interfaces/vector/exceptions.py | 2 -- can/interfaces/vector/xldriver.py | 8 ++++++-- test/test_vector.py | 6 +++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index cb1961986..53c774e6f 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -17,14 +17,12 @@ def __reduce__(self): class VectorInitializationError(VectorError, CanInitializationError): - @staticmethod def from_generic(error: VectorError) -> "VectorInitializationError": return VectorInitializationError(*error._args) class VectorOperationError(VectorError, CanOperationError): - @staticmethod def from_generic(error: VectorError) -> "VectorOperationError": return VectorOperationError(*error._args) diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 32f260c2c..db57d5911 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -33,14 +33,18 @@ def check_status_operation(result, function, arguments): """Check the status and raise a :class:`VectorOperationError` on error.""" if result > 0: - raise VectorOperationError(result, xlGetErrorString(result).decode(), function.__name__) + raise VectorOperationError( + result, xlGetErrorString(result).decode(), function.__name__ + ) return result def check_status_initialization(result, function, arguments): """Check the status and raise a :class:`VectorInitializationError` on error.""" if result > 0: - raise VectorInitializationError(result, xlGetErrorString(result).decode(), function.__name__) + raise VectorInitializationError( + result, xlGetErrorString(result).decode(), function.__name__ + ) return result diff --git a/test/test_vector.py b/test/test_vector.py index f61032f38..52188837c 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -287,7 +287,11 @@ def test_called_without_testing_argument(self) -> None: can.Bus(channel=0, bustype="vector") def test_vector_error_pickle(self) -> None: - for error_type in [VectorError, VectorInitializationError, VectorOperationError]: + for error_type in [ + VectorError, + VectorInitializationError, + VectorOperationError, + ]: with self.subTest(f"error_type = {error_type.__name__}"): error_code = 118 From 6d280ed7c20ebcdeccd42bc38797a17848592703 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Jun 2021 09:51:47 +0200 Subject: [PATCH 0690/1235] improve docstrings related to exceptions --- can/interfaces/vector/canlib.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ce3c98fa2..3938532b3 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -670,8 +670,7 @@ def get_application_config( hardware channel. :raises can.interfaces.vector.VectorInitializationError: - Raises a VectorError when the application name does not exist in - Vector Hardware Configuration. + If the application name does not exist in the Vector hardware configuration. """ hw_type = ctypes.c_uint() hw_index = ctypes.c_uint() @@ -722,8 +721,7 @@ def set_application_config( The channel index of the interface. :raises can.interfaces.vector.VectorInitializationError: - Raises a VectorError when the application name does not exist in - Vector Hardware Configuration. + If the application name does not exist in the Vector hardware configuration. """ xldriver.xlSetApplConfig( app_name.encode(), From 6e219267b482604e73ea3d3c59157ab0ac8f5fd9 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Jun 2021 09:54:52 +0200 Subject: [PATCH 0691/1235] Fix test_called_without_testing_argument() unit test case --- test/test_vector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_vector.py b/test/test_vector.py index 52188837c..fc77dd48d 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -282,8 +282,8 @@ def test_set_timer_rate(self) -> None: 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 suppresses the exception + with self.assertRaises(can.CanInterfaceNotImplementedError): + # do not set the _testing argument, since it would suppress the exception can.Bus(channel=0, bustype="vector") def test_vector_error_pickle(self) -> None: From 8e6f05a1e9075da13fa52c900cc0be3e3f7d7833 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Wed, 9 Jun 2021 08:56:10 -0400 Subject: [PATCH 0692/1235] Setting the transmit exception __cause__ to None Setting the transmit exception __cause__ to None to remove the original exception from the default traceback display. --- can/interfaces/ics_neovi/neovi_bus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index bb043002a..22087b4a4 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -457,7 +457,7 @@ def send(self, msg, timeout=0): try: ics.transmit_messages(self.dev, message) except ics.RuntimeError: - raise ICSOperationError(*ics.get_last_api_error(self.dev)) + raise ICSOperationError(*ics.get_last_api_error(self.dev)) from None # If timeout is set, wait for ACK # This requires a notifier for the bus or From f7bf68f4cf6fce8eaae358475ed46fad1cac7a7a Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 14 Jun 2021 11:34:13 +0200 Subject: [PATCH 0693/1235] Improve PCAN installation procedure --- can/interfaces/pcan/pcan.py | 18 ++++++++++++------ doc/installation.rst | 22 ++++++++++------------ doc/interfaces/pcan.rst | 16 +++++----------- setup.py | 1 + tox.ini | 1 + 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index bda677bd6..70190cdb1 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -4,6 +4,7 @@ import logging import time +from datetime import datetime from typing import Optional from can import CanError, Message, BusABC @@ -11,16 +12,24 @@ from can.util import len2dlc, dlc2len from .basic import * + +# Set up logging +log = logging.getLogger("can.pcan") + + try: # use the "uptime" library if available import uptime - import datetime # boottime() and fromtimestamp() are timezone offset, so the difference is not. boottimeEpoch = ( - uptime.boottime() - datetime.datetime.fromtimestamp(0) + uptime.boottime() - datetime.fromtimestamp(0) ).total_seconds() -except ImportError: +except ImportError as error: + log.warning( + "uptime library not available, timestamps are relative to boot time and not to Epoch UTC", + exc_info=True, + ) boottimeEpoch = 0 try: @@ -40,9 +49,6 @@ # Use polling instead HAS_EVENTS = False -# Set up logging -log = logging.getLogger("can.pcan") - pcan_bitrate_objs = { 1000000: PCAN_BAUD_1M, diff --git a/doc/installation.rst b/doc/installation.rst index 710618cf7..fe7204ba6 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -29,25 +29,24 @@ Kvaser To install ``python-can`` using the Kvaser CANLib SDK as the backend: -1. Install the `latest stable release of - Python `__. +1. Install `Kvaser's latest Windows CANLib drivers `__. -2. Install `Kvaser's latest Windows CANLib - drivers `__. - -3. Test that Kvaser's own tools work to ensure the driver is properly +2. Test that Kvaser's own tools work to ensure the driver is properly installed and that the hardware is working. PCAN ~~~~ -Download and install the latest driver for your interface from -`PEAK-System's download page `__. +Download and install the latest driver for your interface: + +- `Windows `__ (also supported on *Cygwin*) +- `Linux `__ (`also works without `__, see also :ref:`pcandoc linux installation`) +- `macOS `__ Note that PCANBasic API timestamps count seconds from system startup. To convert these to epoch times, the uptime library is used. If it is not available, the times are returned as number of seconds from system -startup. To install the uptime library, run ``pip install uptime``. +startup. To install the uptime library, run ``pip install python-can[pcan]``. This library can take advantage of the `Python for Windows Extensions `__ library if installed. @@ -69,7 +68,7 @@ NI-CAN ~~~~~~ Download and install the NI-CAN drivers from -`National Instruments `__. +`National Instruments `__. Currently the driver only supports 32-bit Python on Windows. @@ -102,7 +101,7 @@ If ``python-can`` is already installed, the CANtact backend can be installed sep ``python3 -m pip install cantact`` -Additional CANtact documentation is available at https://cantact.io. +Additional CANtact documentation is available at `cantact.io `__. Installing python-can in development mode ----------------------------------------- @@ -115,4 +114,3 @@ reinstall. Download or clone the source repository then: python setup.py develop - diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index 378d70e9b..ff82ba9f4 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -5,12 +5,6 @@ PCAN Basic API Interface to `Peak-System `__'s PCAN-Basic API. -The required drivers can be downloaded here: - -- `Windows `__ (also supported on *Cygwin*) -- `Linux `__ (`also works without `__, see also :ref:`pcandoc linux installation`) -- `macOS `__ - Configuration ------------- @@ -24,11 +18,11 @@ Here is an example configuration file for using `PCAN-USB = 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketcan`, refer to: :ref:`socketcan-pcan`. +Beginning with version 3.4, Linux kernels support the PCAN adapters natively via :doc:`/interfaces/socketcan`, refer to: :ref:`socketcan-pcan`. Bus --- diff --git a/setup.py b/setup.py index 1b56379cf..ecf53615f 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ "cantact": ["cantact>=0.0.7"], "gs_usb": ["gs_usb>=0.2.1"], "nixnet": ["nixnet>=0.3.1"], + "pcan": ["uptime~=3.0.1"], } setup( diff --git a/tox.ini b/tox.ini index 164dfdd8e..c9fb00575 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ deps = hypothesis~=4.56 pyserial~=3.0 parameterized~=0.8 + uptime~=3.0.1 commands = pytest {posargs} From 0fda7f42b4d9a95055446fc577f8e3892b4c0ac2 Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 14 Jun 2021 09:34:58 +0000 Subject: [PATCH 0694/1235] Format code with black --- can/interfaces/pcan/pcan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 70190cdb1..251b258de 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -22,9 +22,7 @@ import uptime # boottime() and fromtimestamp() are timezone offset, so the difference is not. - boottimeEpoch = ( - uptime.boottime() - datetime.fromtimestamp(0) - ).total_seconds() + boottimeEpoch = (uptime.boottime() - datetime.fromtimestamp(0)).total_seconds() except ImportError as error: log.warning( "uptime library not available, timestamps are relative to boot time and not to Epoch UTC", From 0ebb0a376673f490e044c1a428379b1a25e3a7e3 Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 14 Jun 2021 12:49:46 +0200 Subject: [PATCH 0695/1235] revert installation of uptime in CI --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index c9fb00575..164dfdd8e 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ deps = hypothesis~=4.56 pyserial~=3.0 parameterized~=0.8 - uptime~=3.0.1 commands = pytest {posargs} From e5ad3323e2f482ea18ab40340fde559d5d29b1d3 Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 14 Jun 2021 11:37:48 +0200 Subject: [PATCH 0696/1235] Add new language classifiers to setup.py --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index ecf53615f..6ad5e88d3 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,9 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Natural Language :: English", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", From 599ab8db658698c749cdebdc82dbfdf25d76755f Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 14 Jun 2021 12:48:35 +0200 Subject: [PATCH 0697/1235] Clean up pcan_test: Group imports and remove redundant one --- can/interfaces/pcan/__init__.py | 2 +- test/test_pcan.py | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py index 3627f0a36..0f28b0ffe 100644 --- a/can/interfaces/pcan/__init__.py +++ b/can/interfaces/pcan/__init__.py @@ -1,4 +1,4 @@ """ """ -from can.interfaces.pcan.pcan import PcanBus +from can.interfaces.pcan.pcan import PcanBus, PcanError diff --git a/test/test_pcan.py b/test/test_pcan.py index 32579d894..595d72b48 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -6,15 +6,14 @@ import unittest from unittest import mock from unittest.mock import Mock -from can.bus import BusState -from can.interfaces.pcan.basic import * -from can.interfaces.pcan import PcanBus -from parameterized import parameterized import pytest +from parameterized import parameterized import can -from can.interfaces.pcan import pcan +from can.bus import BusState +from can.interfaces.pcan.basic import * +from can.interfaces.pcan import PcanBus, PcanError class TestPCANBus(unittest.TestCase): @@ -37,7 +36,7 @@ def tearDown(self) -> None: def test_bus_creation(self) -> None: self.bus = can.Bus(bustype="pcan") - self.assertIsInstance(self.bus, pcan.PcanBus) + self.assertIsInstance(self.bus, PcanBus) self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_called_once() self.mock_pcan.InitializeFD.assert_not_called() @@ -45,11 +44,11 @@ def test_bus_creation(self) -> None: def test_bus_creation_state_error(self) -> None: with self.assertRaises(ctypes.ArgumentError): self.bus = can.Bus(bustype="pcan", state=BusState.ERROR) - self.assertIsInstance(self.bus, pcan.PcanBus) + self.assertIsInstance(self.bus, PcanBus) def test_bus_creation_fd(self) -> None: self.bus = can.Bus(bustype="pcan", fd=True) - self.assertIsInstance(self.bus, pcan.PcanBus) + self.assertIsInstance(self.bus, PcanBus) self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_not_called() self.mock_pcan.InitializeFD.assert_called_once() @@ -254,7 +253,7 @@ def test_send_error(self) -> None: arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True ) - with self.assertRaises(pcan.PcanError): + with self.assertRaises(PcanError): self.bus.send(msg) @parameterized.expand([("on", True), ("off", False)]) From 253705adfb70db38132078262914d70873eb1022 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Jun 2021 10:10:50 +0200 Subject: [PATCH 0698/1235] Improve docstring of VectorBus.__init__; better parsing; and add typing information --- can/interfaces/vector/canlib.py | 92 ++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index c5838de21..78ce85dd7 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,7 +10,7 @@ import logging import time import os -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Sequence, Union import typing @@ -38,6 +38,7 @@ deprecated_args_alias, time_perfcounter_correlation, ) +from can.typechecking import CanFilters from .exceptions import VectorError # Define Module Logger @@ -71,59 +72,64 @@ class VectorBus(BusABC): @deprecated_args_alias(**deprecated_args) def __init__( self, - channel, - can_filters=None, - poll_interval=0.01, - receive_own_messages=False, - bitrate=None, - rx_queue_size=2 ** 14, - app_name="CANalyzer", - serial=None, - fd=False, - data_bitrate=None, - sjw_abr=2, - tseg1_abr=6, - tseg2_abr=3, - sjw_dbr=2, - tseg1_dbr=6, - tseg2_dbr=3, + channel: Union[int, Sequence[int], str], + can_filters: Optional[CanFilters] = None, + poll_interval: float = 0.01, + receive_own_messages: bool = False, + bitrate: int = None, + rx_queue_size: int = 2 ** 14, + app_name: str = "CANalyzer", + serial: int = None, + fd: bool = False, + data_bitrate: int = None, + sjw_abr: int = 2, + tseg1_abr: int = 6, + tseg2_abr: int = 3, + sjw_dbr: int = 2, + tseg1_dbr: int = 6, + tseg2_dbr: int = 3, **kwargs, - ): + ) -> None: """ - :param list channel: + :param channel: The channel indexes to create this bus with. Can also be a single integer or a comma separated string. - :param float poll_interval: + :param can_filters: + See :class:`can.BusABC`. + :param receive_own_messages: + See :class:`can.BusABC`. + :param poll_interval: Poll interval in seconds. - :param int bitrate: + :param bitrate: Bitrate in bits/s. - :param int rx_queue_size: + :param rx_queue_size: Number of messages in receive queue (power of 2). - CAN: range 16…32768 - CAN-FD: range 8192…524288 - :param str app_name: - Name of application in Hardware Config. - If set to None, the channel should be a global channel index. - :param int serial: + CAN: range `16…32768` + CAN-FD: range `8192…524288` + :param app_name: + Name of application in the hardware config. + If set to `None`, the channel should be a global channel index. + :param serial: Serial number of the hardware to be used. If set, the channel parameter refers to the channels ONLY on the specified hardware. - If set, the app_name does not have to be previously defined in Vector Hardware Config. - :param bool fd: + If set, the app_name does not have to be previously defined in the Vector hardware + config. + :param fd: If CAN-FD frames should be supported. - :param int data_bitrate: + :param data_bitrate: Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. - :param int sjw_abr: + :param sjw_abr: Bus timing value sample jump width (arbitration). - :param int tseg1_abr: + :param tseg1_abr: Bus timing value tseg1 (arbitration) - :param int tseg2_abr: + :param tseg2_abr: Bus timing value tseg2 (arbitration) - :param int sjw_dbr: + :param sjw_dbr: Bus timing value sample jump width (data) - :param int tseg1_dbr: + :param tseg1_dbr: Bus timing value tseg1 (data) - :param int tseg2_dbr: + :param tseg2_dbr: Bus timing value tseg2 (data) """ if os.name != "nt" and not kwargs.get("_testing", False): @@ -135,13 +141,17 @@ def __init__( raise ImportError("The Vector API has not been loaded") self.poll_interval = poll_interval - if isinstance(channel, (list, tuple)): - self.channels = channel + + if isinstance(channel, str): # must be checked before generic Sequence + # Assume comma separated string of channels + self.channels = [int(ch.strip()) for ch in channel.split(",")] elif isinstance(channel, int): self.channels = [channel] + elif isinstance(channel, Sequence): + self.channels = channel else: - # Assume comma separated string of channels - self.channels = [int(ch.strip()) for ch in channel.split(",")] + raise TypeError(f"Invalid type for channels parameter: {type(channel).__name__}") + self._app_name = app_name.encode() if app_name is not None else b"" self.channel_info = "Application %s: %s" % ( app_name, From 6b2d9830bfc007d5858091b02711b25cb0d36c02 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Jun 2021 08:11:53 +0000 Subject: [PATCH 0699/1235] Format code with black --- can/interfaces/vector/canlib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 78ce85dd7..80e5f05ef 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -150,7 +150,9 @@ def __init__( elif isinstance(channel, Sequence): self.channels = channel else: - raise TypeError(f"Invalid type for channels parameter: {type(channel).__name__}") + raise TypeError( + f"Invalid type for channels parameter: {type(channel).__name__}" + ) self._app_name = app_name.encode() if app_name is not None else b"" self.channel_info = "Application %s: %s" % ( From d501728dbbf7a7a947ea5490366ca9b0cc19cef8 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 13 Jun 2021 10:02:39 +0200 Subject: [PATCH 0700/1235] Update can/interfaces/vector/canlib.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/vector/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 80e5f05ef..50d6c0ecc 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -107,7 +107,7 @@ def __init__( CAN: range `16…32768` CAN-FD: range `8192…524288` :param app_name: - Name of application in the hardware config. + Name of application in Vector Hardware Configuration. If set to `None`, the channel should be a global channel index. :param serial: Serial number of the hardware to be used. From 2afb0bfff73000e24bf43d18c8dd2fa0e85d1b4c Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 13 Jun 2021 10:03:38 +0200 Subject: [PATCH 0701/1235] Update can/interfaces/vector/canlib.py --- can/interfaces/vector/canlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 50d6c0ecc..8758e8b10 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -112,8 +112,8 @@ def __init__( :param serial: Serial number of the hardware to be used. If set, the channel parameter refers to the channels ONLY on the specified hardware. - If set, the app_name does not have to be previously defined in the Vector hardware - config. + If set, the app_name does not have to be previously defined in Vector Hardware + Configuration. :param fd: If CAN-FD frames should be supported. :param data_bitrate: From 3943a869e839e1085d8a0fa57784db8fefe8de84 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 13 Jun 2021 10:06:48 +0200 Subject: [PATCH 0702/1235] fix 'Vector Hardware Config' naming --- can/interfaces/vector/canlib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 8758e8b10..aef77da92 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -107,13 +107,13 @@ def __init__( CAN: range `16…32768` CAN-FD: range `8192…524288` :param app_name: - Name of application in Vector Hardware Configuration. + Name of application in *Vector Hardware Config*. If set to `None`, the channel should be a global channel index. :param serial: Serial number of the hardware to be used. If set, the channel parameter refers to the channels ONLY on the specified hardware. - If set, the app_name does not have to be previously defined in Vector Hardware - Configuration. + If set, the `app_name` does not have to be previously defined in + *Vector Hardware Config*. :param fd: If CAN-FD frames should be supported. :param data_bitrate: From 689a87db9727418f950c0865506149e36c63b73e Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 14 Jun 2021 10:13:25 +0200 Subject: [PATCH 0703/1235] Adjust iscan to new exceptions; simplify; add typing --- can/interfaces/iscan.py | 90 ++++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index a0bb413f2..376ccc3fa 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -1,12 +1,19 @@ """ -Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH. +Interface for isCAN from *Thorsis Technologies GmbH*, former *ifak system GmbH*. """ import ctypes import time import logging +from typing import Optional, Tuple, Union -from can import CanError, BusABC, Message +from can import BusABC, Message +from can import ( + CanError, + CanInterfaceNotImplementedError, + CanInitializationError, + CanOperationError, +) logger = logging.getLogger(__name__) @@ -23,9 +30,15 @@ class MessageExStruct(ctypes.Structure): ] -def check_status(result, function, arguments): +def check_status_initialization(result: int, function, arguments) -> int: if result > 0: - raise IscanError(function, result, arguments) + raise IscanInitializationError(function, result, arguments) + return result + + +def check_status(result: int, function, arguments) -> int: + if result > 0: + raise IscanOperationError(function, result, arguments) return result @@ -36,12 +49,15 @@ def check_status(result, function, arguments): logger.warning("Failed to load IS-CAN driver: %s", e) else: iscan.isCAN_DeviceInitEx.argtypes = [ctypes.c_ubyte, ctypes.c_ubyte] - iscan.isCAN_DeviceInitEx.errcheck = check_status + iscan.isCAN_DeviceInitEx.errcheck = check_status_initialization iscan.isCAN_DeviceInitEx.restype = ctypes.c_ubyte + iscan.isCAN_ReceiveMessageEx.errcheck = check_status iscan.isCAN_ReceiveMessageEx.restype = ctypes.c_ubyte + iscan.isCAN_TransmitMessageEx.errcheck = check_status iscan.isCAN_TransmitMessageEx.restype = ctypes.c_ubyte + iscan.isCAN_CloseDevice.errcheck = check_status iscan.isCAN_CloseDevice.restype = ctypes.c_ubyte @@ -62,24 +78,29 @@ class IscanBus(BusABC): 1000000: 9, } - def __init__(self, channel, bitrate=500000, poll_interval=0.01, **kwargs): + def __init__( + self, + channel: Union[str, int], + bitrate: int = 500000, + poll_interval: float = 0.01, + **kwargs, + ) -> None: """ - :param int channel: + :param channel: Device number - :param int bitrate: + :param bitrate: Bitrate in bits/s - :param float poll_interval: + :param poll_interval: Poll interval in seconds when reading messages """ if iscan is None: - raise ImportError("Could not load isCAN driver") + raise CanInterfaceNotImplementedError("Could not load isCAN driver") self.channel = ctypes.c_ubyte(int(channel)) - self.channel_info = "IS-CAN: %s" % channel + self.channel_info = f"IS-CAN: {self.channel}" if bitrate not in self.BAUDRATES: - valid_bitrates = ", ".join(str(bitrate) for bitrate in self.BAUDRATES) - raise ValueError("Invalid bitrate, choose one of " + valid_bitrates) + raise ValueError(f"Invalid bitrate, choose one of {set(self.BAUDRATES)}") self.poll_interval = poll_interval iscan.isCAN_DeviceInitEx(self.channel, self.BAUDRATES[bitrate]) @@ -88,14 +109,16 @@ def __init__(self, channel, bitrate=500000, poll_interval=0.01, **kwargs): channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs ) - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: raw_msg = MessageExStruct() end_time = time.time() + timeout if timeout is not None else None while True: try: iscan.isCAN_ReceiveMessageEx(self.channel, ctypes.byref(raw_msg)) except IscanError as e: - if e.error_code != 8: + if e.error_code != 8: # "No message received" # An error occurred raise if end_time is not None and time.time() > end_time: @@ -118,7 +141,7 @@ def _recv_internal(self, timeout): ) return msg, False - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: raw_msg = MessageExStruct( msg.arbitration_id, bool(msg.is_extended_id), @@ -128,14 +151,14 @@ def send(self, msg, timeout=None): ) iscan.isCAN_TransmitMessageEx(self.channel, ctypes.byref(raw_msg)) - def shutdown(self): + def shutdown(self) -> None: iscan.isCAN_CloseDevice(self.channel) class IscanError(CanError): - # TODO: document ERROR_CODES = { + 0: "Success", 1: "No access to device", 2: "Device with ID not found", 3: "Driver operation failed", @@ -161,17 +184,28 @@ class IscanError(CanError): 40: "Need a licence number under NT4", } - def __init__(self, function, error_code, arguments): - super().__init__() - # :Status code + def __init__(self, function, error_code: int, arguments) -> None: + try: + description = ": " + self.ERROR_CODES[self.error_code] + except KeyError: + description = "" + + super().__init__( + f"Function {self.function.__name__} failed{description}", + error_code=error_code, + ) + + #: Status code self.error_code = error_code - # :Function that failed + #: Function that failed self.function = function - # :Arguments passed to function + #: Arguments passed to function self.arguments = arguments - def __str__(self): - description = self.ERROR_CODES.get( - self.error_code, "Error code %d" % self.error_code - ) - return "Function %s failed: %s" % (self.function.__name__, description) + +class IscanOperationError(IscanError, CanOperationError): + pass + + +class IscanInitializationError(IscanError, CanInitializationError): + pass From fcbef0ead3b02a6cdf867c4433c7b448cc6dc1cb Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 17 Jun 2021 09:55:23 +0200 Subject: [PATCH 0704/1235] Specific Exceptions: Adapting udp_multicast interface (#1089) * Adjust exceptions in udp_multicast interface * Format code with black --- can/interfaces/udp_multicast/bus.py | 40 +++++++++++++++++++++------ can/interfaces/udp_multicast/utils.py | 5 ++-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index ee6a6e1ee..86a78a1ff 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -92,7 +92,9 @@ def __init__( check_msgpack_installed() if receive_own_messages: - raise NotImplementedError("receiving own messages is not yet implemented") + raise can.CanInterfaceNotImplementedError( + "receiving own messages is not yet implemented" + ) super().__init__(channel, **kwargs) @@ -105,7 +107,14 @@ def _recv_internal(self, timeout: Optional[float]): return None, False data, _, timestamp = result - can_message = unpack_message(data, replace={"timestamp": timestamp}) + try: + can_message = unpack_message( + data, replace={"timestamp": timestamp}, check=True + ) + except Exception as exception: + raise can.CanOperationError( + "could not unpack received message" + ) from exception if not self.is_fd and can_message.is_fd: return None, False @@ -114,7 +123,9 @@ def _recv_internal(self, timeout: Optional[float]): def send(self, message: can.Message, timeout: Optional[float] = None) -> None: if not self.is_fd and message.is_fd: - raise RuntimeError("cannot send FD message over bus with CAN FD disabled") + raise can.CanOperationError( + "cannot send FD message over bus with CAN FD disabled" + ) data = pack_message(message) self._multicast.send(data, timeout) @@ -149,7 +160,10 @@ def _detect_available_configs() -> List[AutoDetectedConfig]: class GeneralPurposeUdpMulticastBus: - """A general purpose send and receive handler for multicast over IP/UDP.""" + """A general purpose send and receive handler for multicast over IP/UDP. + + However, it raises CAN-specific exceptions for convenience. + """ def __init__( self, group: str, port: int, hop_limit: int, max_buffer: int = 4096 @@ -178,7 +192,9 @@ def __init__( if sock is not None: self._socket = sock else: - raise RuntimeError("could not connect to a multicast IP network") + raise can.CanInitializationError( + "could not connect to a multicast IP network" + ) # used in recv() self.received_timestamp_struct = "@ll" @@ -193,8 +209,10 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: """Creates a new socket. This might fail and raise an exception! :param address_family: whether this is of type `socket.AF_INET` or `socket.AF_INET6` - :raises OSError: if the socket could not be opened or configured correctly; in this case, it is - guaranteed to be closed/cleaned up + + :raises can.CanInitializationError: + if the socket could not be opened or configured correctly; in this case, it is + guaranteed to be closed/cleaned up """ # create the UDP socket # this might already fail but then there is nothing to clean up @@ -243,7 +261,9 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: log.warning("Could not close partly configured socket: %s", close_error) # still raise the error - raise error + raise can.CanInitializationError( + "could not create or configure socket" + ) from error def send(self, data: bytes, timeout: Optional[float] = None) -> None: """Send data to all group members. This call blocks. @@ -283,7 +303,9 @@ def recv( ready_receive_sockets, _, _ = select.select([self._socket], [], [], timeout) except socket.error as exc: # something bad (not a timeout) happened (e.g. the interface went down) - raise can.CanError(f"Failed to wait for IP/UDP socket: {exc}") + raise can.CanOperationError( + f"Failed to wait for IP/UDP socket: {exc}" + ) from exc if ready_receive_sockets: # not empty # fetch data & source address diff --git a/can/interfaces/udp_multicast/utils.py b/can/interfaces/udp_multicast/utils.py index dad368f0b..2658bf0a9 100644 --- a/can/interfaces/udp_multicast/utils.py +++ b/can/interfaces/udp_multicast/utils.py @@ -7,6 +7,7 @@ from typing import Optional from can import Message +from can import CanInterfaceNotImplementedError from can.typechecking import ReadableBytesLike try: @@ -16,9 +17,9 @@ def check_msgpack_installed() -> None: - """Raises a `RuntimeError` if `msgpack` is not installed.""" + """Raises a :class:`can.CanInterfaceNotImplementedError` if `msgpack` is not installed.""" if msgpack is None: - raise RuntimeError("msgpack not installed") + raise CanInterfaceNotImplementedError("msgpack not installed") def pack_message(message: Message) -> bytes: From 872486afd1a438ad82eb2abefc2e9fcc8c96985d Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 17 Jun 2021 09:56:04 +0200 Subject: [PATCH 0705/1235] Specific Exceptions: Adapting serial interface (#1088) * Adjust exceptions in serial * Format code with black --- can/interfaces/serial/serial_can.py | 43 ++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 9fe10e3b3..8efd36bb9 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -11,7 +11,13 @@ import struct from typing import Any, List, Tuple, Optional -from can import BusABC, Message, CanError +from can import BusABC, Message +from can import ( + CanInterfaceNotImplementedError, + CanInitializationError, + CanOperationError, + CanTimeoutError, +) from can.typechecking import AutoDetectedConfig logger = logging.getLogger("can.serial") @@ -67,14 +73,26 @@ def __init__( :param rtscts: turn hardware handshake (RTS/CTS) on and off + :raises can.CanInitializationError: If the given parameters are invalid. + :raises can.CanInterfaceNotImplementedError: If the serial module is not installed. """ + + if not serial: + raise CanInterfaceNotImplementedError("the serial module is not installed") + if not channel: - raise ValueError("Must specify a serial port.") + raise TypeError("Must specify a serial port.") self.channel_info = f"Serial interface: {channel}" - self._ser = serial.serial_for_url( - channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts - ) + + try: + self._ser = serial.serial_for_url( + channel, baudrate=baudrate, timeout=timeout, rtscts=rtscts + ) + except ValueError as error: + raise CanInitializationError( + "could not create the serial device" + ) from error super().__init__(channel, *args, **kwargs) @@ -124,7 +142,12 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: byte_msg.append(0xBB) # Write to serial device - self._ser.write(byte_msg) + try: + self._ser.write(byte_msg) + except serial.PortNotOpenError as error: + raise CanOperationError("writing to closed port") from error + except serial.SerialTimeoutException as error: + raise CanTimeoutError() from error def _recv_internal( self, timeout: Optional[float] @@ -153,12 +176,12 @@ def _recv_internal( timestamp = struct.unpack(" 8: - raise CanError("received DLC may not exceed 8 bytes") + raise ValueError("received DLC may not exceed 8 bytes") s = self._ser.read(4) arbitration_id = struct.unpack("= 0x20000000: - raise CanError( + raise ValueError( "received arbitration id may not exceed 2^29 (0x20000000)" ) @@ -177,7 +200,7 @@ def _recv_internal( return msg, False else: - raise CanError( + raise CanOperationError( f"invalid delimiter byte while reading message: {delimiter_byte}" ) @@ -185,7 +208,7 @@ def _recv_internal( return None, False except serial.SerialException as error: - raise CanError("could not read from serial") from error + raise CanOperationError("could not read from serial") from error def fileno(self) -> int: if hasattr(self._ser, "fileno"): From 0e0c64fd7104774dbcfe3641bd9a362ff54b2641 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 17 Jun 2021 09:56:31 +0200 Subject: [PATCH 0706/1235] Specific Exceptions: Adapting socketcan interface (#1087) * Improve exception handling in socketcan * adjust cyclic socketcan test case * fix typo in other test case * Format code with black --- can/exceptions.py | 1 + can/interfaces/socketcan/socketcan.py | 64 +++++++++++++-------------- test/logformats_test.py | 2 +- test/test_cyclic_socketcan.py | 4 +- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index 0f41dfa36..7d6374735 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -85,6 +85,7 @@ class CanOperationError(CanError): - The driver rejected a message that was meant to be sent - Cyclic redundancy check (CRC) failed - A message remained unacknowledged + - A buffer is full """ diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 95387e776..3d25f7d4b 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -278,24 +278,19 @@ def send_bcm(bcm_socket: socket.socket, data: bytes) -> int: """ try: return bcm_socket.send(data) - except OSError as e: - base = "Couldn't send CAN BCM frame. OS Error {}: {}\n".format( - e.errno, e.strerror - ) - - if e.errno == errno.EINVAL: - raise can.CanError( - base + "You are probably referring to a non-existing frame." - ) - - elif e.errno == errno.ENETDOWN: - raise can.CanError(base + "The CAN interface appears to be down.") - - elif e.errno == errno.EBADF: - raise can.CanError(base + "The CAN socket appears to be closed.") - + except OSError as error: + base = f"Couldn't send CAN BCM frame due to OS Error: {error.strerror}" + + if error.errno == errno.EINVAL: + specific_message = " You are probably referring to a non-existing frame." + elif error.errno == errno.ENETDOWN: + specific_message = " The CAN interface appears to be down." + elif error.errno == errno.EBADF: + specific_message = " The CAN socket appears to be closed." else: - raise e + specific_message = "" + + raise can.CanOperationError(base + specific_message, error.errno) from error def _compose_arbitration_id(message: Message) -> int: @@ -330,7 +325,7 @@ def __init__( messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, - ): + ) -> None: """Construct and :meth:`~start` a task. :param bcm_socket: An open BCM socket on the desired CAN channel. @@ -378,7 +373,7 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: log.debug("Sending BCM command") send_bcm(self.bcm_socket, header + body) - def _check_bcm_task(self): + def _check_bcm_task(self) -> None: # Do a TX_READ on a task ID, and check if we get EINVAL. If so, # then we are referring to a CAN message with the existing ID check_header = build_bcm_header( @@ -394,14 +389,13 @@ def _check_bcm_task(self): ) try: self.bcm_socket.send(check_header) - except OSError as e: - if e.errno != errno.EINVAL: - raise e + except OSError as error: + if error.errno != errno.EINVAL: + raise can.CanOperationError("failed to check", error.errno) from error else: - raise ValueError( - "A periodic task for Task ID {} is already in progress by SocketCAN Linux layer".format( - self.task_id - ) + raise can.CanOperationError( + f"A periodic task for task ID {self.task_id} is already in progress " + "by the SocketCAN Linux layer" ) def stop(self) -> None: @@ -537,7 +531,7 @@ def capture_message( else: channel = None except socket.error as error: - raise can.CanError(f"Error receiving: {error}") + raise can.CanOperationError(f"Error receiving: {error.strerror}", error.errno) can_id, can_dlc, flags, data = dissect_can_frame(cf) @@ -550,7 +544,7 @@ def capture_message( # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data) if nanoseconds >= 1e9: - raise can.CanError( + raise can.CanOperationError( f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9" ) timestamp = seconds + nanoseconds * 1e-9 @@ -716,9 +710,11 @@ def _recv_internal( # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout) - except socket.error as exc: + except socket.error as error: # something bad happened (e.g. the interface went down) - raise can.CanError(f"Failed to receive: {exc}") + raise can.CanOperationError( + f"Failed to receive: {error.strerror}", error.errno + ) if ready_receive_sockets: # not empty get_channel = self.channel == "" @@ -767,7 +763,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: data = data[sent:] time_left = timeout - (time.time() - started) - raise can.CanError("Transmit buffer full") + raise can.CanOperationError("Transmit buffer full") def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: try: @@ -776,8 +772,10 @@ def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: sent = self.socket.sendto(data, (channel,)) else: sent = self.socket.send(data) - except socket.error as exc: - raise can.CanError("Failed to transmit: %s" % exc) + except socket.error as error: + raise can.CanOperationError( + f"Failed to transmit: {error.strerror}", error.errno + ) return sent def _send_periodic_internal( diff --git a/test/logformats_test.py b/test/logformats_test.py index d8a06762e..f7aafa5b0 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -316,7 +316,7 @@ def test_append_mode(self): try: writer = self.writer_constructor(self.test_file_name) except TypeError: - # is the is still a problem, raise the initial error + # if it is still a problem, raise the initial error raise e with writer: for message in second_part: diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index 40c7af582..4e3887ad6 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -255,10 +255,10 @@ def test_start_already_started_task(self): time.sleep(0.1) # Try to start it again, task_id is not incremented in this case - with self.assertRaises(ValueError) as ctx: + with self.assertRaises(can.CanOperationError) as ctx: task_a.start() self.assertEqual( - "A periodic task for Task ID 1 is already in progress by SocketCAN Linux layer", + "A periodic task for task ID 1 is already in progress by the SocketCAN Linux layer", str(ctx.exception), ) From c64f16260465af5e60214bbd5d3e2a81e9f04d86 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Fri, 25 Jun 2021 01:26:10 +0200 Subject: [PATCH 0707/1235] Supress exception occuring in NIXNET _detect_available_configs() (#1085) * Supress exception occuring in NIXNET _detect_available_configs() * Format code with black * fix error import * Format code with black Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- can/interfaces/nixnet.py | 41 +++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index 993c215dd..f50daf4c9 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -19,7 +19,15 @@ if sys.platform == "win32": try: - from nixnet import session, types, constants, errors, system, database + from nixnet import ( + session, + types, + constants, + errors, + system, + database, + XnetError, + ) except ImportError: logger.error("Error, NIXNET python module cannot be loaded.") raise ImportError() @@ -225,18 +233,25 @@ def shutdown(self): @staticmethod def _detect_available_configs(): configs = [] - nixnet_system = system.System() - for can_intf in nixnet_system.intf_refs_can: - logger.info("Channel index %d: %s", can_intf.port_num, str(can_intf)) - configs.append( - { - "interface": "nixnet", - "channel": str(can_intf), - "can_term_available": can_intf.can_term_cap - == constants.CanTermCap.YES, - } - ) - nixnet_system.close() + + try: + with system.System() as nixnet_system: + for interface in nixnet_system.intf_refs_can: + cahnnel = str(interface) + logger.debug( + "Found channel index %d: %s", interface.port_num, cahnnel + ) + configs.append( + { + "interface": "nixnet", + "channel": cahnnel, + "can_term_available": interface.can_term_cap + == constants.CanTermCap.YES, + } + ) + except XnetError as error: + logger.debug("An error occured while searching for configs: %s", str(error)) + return configs From 5dcebd750fa2e1818669ee7524b34d5d66dca0c9 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Fri, 25 Jun 2021 01:28:23 +0200 Subject: [PATCH 0708/1235] Supress 'SystemError: DeviceNotFound' in cantact detect_available_configs() (#1077) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- can/interfaces/cantact.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 5d71cf3ad..9f8bf6314 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -25,7 +25,7 @@ class CantactBus(BusABC): def _detect_available_configs(): try: interface = cantact.Interface() - except NameError: + except (NameError, SystemError): # couldn't import cantact, so no configurations are available return [] From 1d53b2579e49cc88e5ebd3a55e12cf0c21bb323f Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 6 Jul 2021 22:55:19 +0200 Subject: [PATCH 0709/1235] Update docstring of can.io.generic.FileIOMessageWriter (#1098) --- can/io/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/generic.py b/can/io/generic.py index daead706e..3ce9aa643 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -73,7 +73,7 @@ class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): # pylint: disable=abstract-method,too-few-public-methods class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): - """The base class for all writers.""" + """A specialized base class for all writers with file descriptors.""" file: Union[TextIO, BinaryIO] From 5b129001859e3c1a812c843ed9aafba06baa0469 Mon Sep 17 00:00:00 2001 From: ChrisSweetKT <74566531+ChrisSweetKT@users.noreply.github.com> Date: Tue, 6 Jul 2021 22:16:03 -0400 Subject: [PATCH 0710/1235] Handle case where uptime is imported successfully but does not return anything (#1103) Co-authored-by: Chris Sweet --- can/interfaces/pcan/pcan.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 251b258de..670918a17 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -22,7 +22,10 @@ import uptime # boottime() and fromtimestamp() are timezone offset, so the difference is not. - boottimeEpoch = (uptime.boottime() - datetime.fromtimestamp(0)).total_seconds() + if uptime.boottime() is None: + boottimeEpoch = 0 + else: + boottimeEpoch = (uptime.boottime() - datetime.fromtimestamp(0)).total_seconds() except ImportError as error: log.warning( "uptime library not available, timestamps are relative to boot time and not to Epoch UTC", From 2cb411a22fc2a29646302a82314e0805ced5ffa4 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 14 Jul 2021 14:06:22 +0200 Subject: [PATCH 0711/1235] Log suppressed error in notifier (#1040) * comment, document and log * Make the default Listener raise an explicit NotImplementedError() by default and handle that in the notifier * Update can/notifier.py Co-authored-by: Brian Thorne --- can/listener.py | 1 + can/notifier.py | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/can/listener.py b/can/listener.py index e0261d262..a4a535887 100644 --- a/can/listener.py +++ b/can/listener.py @@ -52,6 +52,7 @@ def on_error(self, exc: Exception) -> None: :param exc: The exception causing the thread to stop """ + raise NotImplementedError() def stop(self) -> None: """ diff --git a/can/notifier.py b/can/notifier.py index 2b4ca30b5..3b80c7461 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -121,9 +121,14 @@ def _rx_thread(self, bus: BusABC) -> None: self.exception = exc if self._loop is not None: self._loop.call_soon_threadsafe(self._on_error, exc) + # Raise anyways raise elif not self._on_error(exc): + # If it was not handled, raise the exception here raise + else: + # It was handled, so only log it + logger.info("suppressed exception: %s", exc) def _on_message_available(self, bus: BusABC) -> None: msg = bus.recv(0) @@ -138,14 +143,22 @@ def _on_message_received(self, msg: Message) -> None: self._loop.create_task(res) def _on_error(self, exc: Exception) -> bool: - listeners_with_on_error = [ - listener for listener in self.listeners if hasattr(listener, "on_error") - ] + """Calls ``on_error()`` for all listeners if they implement it. - for listener in listeners_with_on_error: - listener.on_error(exc) + :returns: ``True`` if at least one error handler was called. + """ + was_handled = False - return bool(listeners_with_on_error) + for listener in self.listeners: + if hasattr(listener, "on_error"): + try: + listener.on_error(exc) + except NotImplementedError: + pass + else: + was_handled = True + + return was_handled def add_listener(self, listener: Listener) -> None: """Add new Listener to the notification list. From da53ba23ee8951b6c1deccf3ab2c2bc2fd34adc9 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 14 Jul 2021 23:39:13 +0200 Subject: [PATCH 0712/1235] Mark CAN FD support as tested (#1107) Closes #269. This is already tested sufficiently (as much as the other parts). --- test/logformats_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index f7aafa5b0..9f362faf3 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -10,7 +10,6 @@ comments. TODO: correctly set preserves_channel and adds_default_channel -TODO: implement CAN FD support testing """ import logging From 158c17aac1fa67bb7cb8f0f651b0bc47a5abf1d7 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 14 Jul 2021 23:39:25 +0200 Subject: [PATCH 0713/1235] Mention buffer sizes in socketcan docs (#1108) --- doc/interfaces/socketcan.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 95aa32bd1..d3a583d75 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -240,6 +240,12 @@ alter or cancel the periodic message task: .. autoclass:: can.interfaces.socketcan.CyclicSendTask :members: +Buffer Sizes +------------ + +Currently, the sending buffer size cannot be adjusted by this library. +However, `this issue `__ describes how to change it via the command line/shell. + Bus --- From 2043822c0d07ff2d80446ba7972268a5f5040e43 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 14 Jul 2021 23:41:20 +0200 Subject: [PATCH 0714/1235] Specific Exceptions: Adapting nican interface (+ add some typing) (#1100) --- can/interfaces/nican.py | 152 ++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 54 deletions(-) diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index fc75ba14a..9c096cee3 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -17,7 +17,16 @@ import logging import sys -from can import CanError, BusABC, Message +from can import BusABC, Message +import can.typechecking +from ..exceptions import ( + CanError, + CanInterfaceNotImplementedError, + CanOperationError, + CanInitializationError, +) +from typing import Optional, Tuple, Type + logger = logging.getLogger(__name__) @@ -74,15 +83,48 @@ class TxMessageStruct(ctypes.Structure): ] -def check_status(result, function, arguments): +class NicanError(CanError): + """Error from NI-CAN driver.""" + + def __init__(self, function, error_code: int, arguments) -> None: + super().__init__( + message=f"{function} failed: {get_error_message(self.error_code)}", + error_code=error_code, + ) + + #: Function that failed + self.function = function + + #: Arguments passed to function + self.arguments = arguments + + +class NicanInitializationError(NicanError, CanInitializationError): + pass + + +class NicanOperationError(NicanError, CanOperationError): + pass + + +def check_status( + result: int, + function, + arguments, + error_class: Type[NicanError] = NicanOperationError, +) -> int: if result > 0: logger.warning(get_error_message(result)) elif result < 0: - raise NicanError(function, result, arguments) + raise error_class(function, result, arguments) return result -def get_error_message(status_code): +def check_status_init(*args, **kwargs) -> int: + return check_status(*args, **kwargs, error_class=NicanInitializationError) + + +def get_error_message(status_code: int) -> str: """Convert status code to descriptive string.""" errmsg = ctypes.create_string_buffer(1024) nican.ncStatusToString(status_code, len(errmsg), errmsg) @@ -102,14 +144,20 @@ def get_error_message(status_code): ctypes.c_void_p, ctypes.c_void_p, ] - nican.ncConfig.errcheck = check_status + nican.ncConfig.errcheck = check_status_init + nican.ncOpenObject.argtypes = [ctypes.c_char_p, ctypes.c_void_p] - nican.ncOpenObject.errcheck = check_status + nican.ncOpenObject.errcheck = check_status_init + nican.ncCloseObject.errcheck = check_status + nican.ncAction.argtypes = [ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong] nican.ncAction.errcheck = check_status + nican.ncRead.errcheck = check_status + nican.ncWrite.errcheck = check_status + nican.ncWaitForState.argtypes = [ ctypes.c_ulong, ctypes.c_ulong, @@ -117,6 +165,7 @@ def get_error_message(status_code): ctypes.c_void_p, ] nican.ncWaitForState.errcheck = check_status + nican.ncStatusToString.argtypes = [ctypes.c_int, ctypes.c_uint, ctypes.c_char_p] else: nican = None @@ -137,37 +186,42 @@ class NicanBus(BusABC): """ def __init__( - self, channel, can_filters=None, bitrate=None, log_errors=True, **kwargs - ): + self, + channel: str, + can_filters: Optional[can.typechecking.CanFilters] = None, + bitrate: Optional[int] = None, + log_errors: bool = True, + **kwargs, + ) -> None: """ - :param str channel: - Name of the object to open (e.g. 'CAN0') + :param channel: + Name of the object to open (e.g. `"CAN0"`) - :param int bitrate: - Bitrate in bits/s + :param bitrate: + Bitrate in bit/s - :param list can_filters: + :param can_filters: See :meth:`can.BusABC.set_filters`. - :param bool log_errors: + :param log_errors: If True, communication errors will appear as CAN messages with ``is_error_frame`` set to True and ``arbitration_id`` will identify the error (default True) - :raises can.interfaces.nican.NicanError: - If starting communication fails - + :raise can.CanInterfaceNotImplementedError: + If the current operating system is not supported or the driver could not be loaded. + :raise can.interfaces.nican.NicanInitializationError: + If the bus could not be set up. """ if nican is None: - raise ImportError( + raise CanInterfaceNotImplementedError( "The NI-CAN driver could not be loaded. " "Check that you are using 32-bit Python on Windows." ) self.channel = channel - self.channel_info = "NI-CAN: " + channel - if not isinstance(channel, bytes): - channel = channel.encode() + self.channel_info = f"NI-CAN: {channel}" + channel_bytes = channel.encode("ascii") config = [(NC_ATTR_START_ON_OPEN, True), (NC_ATTR_LOG_COMM_ERRS, log_errors)] @@ -208,31 +262,33 @@ def __init__( attr_id_list = AttrList(*(row[0] for row in config)) attr_value_list = AttrList(*(row[1] for row in config)) nican.ncConfig( - channel, + channel_bytes, len(config), ctypes.byref(attr_id_list), ctypes.byref(attr_value_list), ) self.handle = ctypes.c_ulong() - nican.ncOpenObject(channel, ctypes.byref(self.handle)) + nican.ncOpenObject(channel_bytes, ctypes.byref(self.handle)) super().__init__( channel=channel, can_filters=can_filters, bitrate=bitrate, log_errors=log_errors, - **kwargs + **kwargs, ) - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: """ Read a message from a NI-CAN bus. - :param float timeout: - Max time to wait in seconds or None if infinite + :param timeout: + Max time to wait in seconds or ``None`` if infinite - :raises can.interfaces.nican.NicanError: + :raises can.interfaces.nican.NicanOperationError: If reception fails """ if timeout is None: @@ -274,14 +330,19 @@ def _recv_internal(self, timeout): ) return msg, True - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: """ Send a message to NI-CAN. - :param can.Message msg: + :param msg: Message to send - :raises can.interfaces.nican.NicanError: + :param timeout: + The timeout + + .. warning:: This gets ignored. + + :raises can.interfaces.nican.NicanOperationError: If writing to transmit buffer fails. It does not wait for message to be ACKed currently. """ @@ -299,36 +360,19 @@ def send(self, msg, timeout=None): # Maybe it is possible to use ncCreateNotification instead but seems a # bit overkill at the moment. # state = ctypes.c_ulong() - # nican.ncWaitForState( - # self.handle, NC_ST_WRITE_SUCCESS, int(timeout * 1000), ctypes.byref(state)) + # nican.ncWaitForState(self.handle, NC_ST_WRITE_SUCCESS, int(timeout * 1000), ctypes.byref(state)) - def reset(self): + def reset(self) -> None: """ Resets network interface. Stops network interface, then resets the CAN chip to clear the CAN error counters (clear error passive state). Resetting includes clearing all entries from read and write queues. + + :raises can.interfaces.nican.NicanOperationError: + If resetting fails. """ nican.ncAction(self.handle, NC_OP_RESET, 0) - def shutdown(self): + def shutdown(self) -> None: """Close object.""" nican.ncCloseObject(self.handle) - - -class NicanError(CanError): - """Error from NI-CAN driver.""" - - def __init__(self, function, error_code, arguments): - super().__init__() - #: Status code - self.error_code = error_code - #: Function that failed - self.function = function - #: Arguments passed to function - self.arguments = arguments - - def __str__(self): - return "Function %s failed:\n%s" % ( - self.function.__name__, - get_error_message(self.error_code), - ) From dd5886c0fc2df3b6b93c28dee54f78675d14b589 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 14 Jul 2021 23:41:59 +0200 Subject: [PATCH 0715/1235] Removes an unneeded line from the two scripts; Use context manager in player.py (#1099) * Removes an unneeded line from the two scripts each Was discussed in #1072 * Tiny cleanup in plyer.py --- can/logger.py | 1 - can/player.py | 51 ++++++++++++++++++++++----------------------------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/can/logger.py b/can/logger.py index 652d47d51..0798fa827 100644 --- a/can/logger.py +++ b/can/logger.py @@ -124,7 +124,6 @@ def _parse_filters(parsed_args: Any) -> CanFilters: def main() -> None: parser = argparse.ArgumentParser( - "python -m can.logger", description="Log CAN traffic, printing messages to stdout or to a given file.", ) diff --git a/can/player.py b/can/player.py index a703739f7..632cc331b 100644 --- a/can/player.py +++ b/can/player.py @@ -13,14 +13,11 @@ from can import LogReader, Message, MessageSync - from .logger import _create_base_argument_parser, _create_bus def main() -> None: - parser = argparse.ArgumentParser( - "python -m can.player", description="Replay CAN traffic." - ) + parser = argparse.ArgumentParser(description="Replay CAN traffic.") _create_base_argument_parser(parser) @@ -87,31 +84,27 @@ def main() -> None: error_frames = results.error_frames - bus = _create_bus(results) - - reader = LogReader(results.infile) - - in_sync = MessageSync( - cast(Iterable[Message], reader), - timestamps=results.timestamps, - gap=results.gap, - skip=results.skip, - ) - - print(f"Can LogReader (Started on {datetime.now()})") - - try: - for m in in_sync: - if m.is_error_frame and not error_frames: - continue - if verbosity >= 3: - print(m) - bus.send(m) - except KeyboardInterrupt: - pass - finally: - bus.shutdown() - reader.stop() + with _create_bus(results) as bus: + with LogReader(results.infile) as reader: + + in_sync = MessageSync( + cast(Iterable[Message], reader), + timestamps=results.timestamps, + gap=results.gap, + skip=results.skip, + ) + + print(f"Can LogReader (Started on {datetime.now()})") + + try: + for message in in_sync: + if message.is_error_frame and not error_frames: + continue + if verbosity >= 3: + print(message) + bus.send(message) + except KeyboardInterrupt: + pass if __name__ == "__main__": From bb1706b24fb441c35c5601b1b45492e477e6fc61 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 14 Jul 2021 23:44:47 +0200 Subject: [PATCH 0716/1235] Specific Exceptions: Adapting seeedstudio interface (#1090) * Adjust exceptions in seeedstudio interface * Format code with black * Logging level change --- can/interfaces/seeedstudio/seeedstudio.py | 122 ++++++++++++++-------- 1 file changed, 81 insertions(+), 41 deletions(-) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 25d848e9d..7cfa6d670 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -10,6 +10,8 @@ import struct import io from time import time + +import can from can import BusABC, Message logger = logging.getLogger("seeedbus") @@ -84,19 +86,32 @@ def __init__( :param bitrate CAN bus bit rate, selected from available list. + :raises can.CanInitializationError: If the given parameters are invalid. + :raises can.CanInterfaceNotImplementedError: If the serial module is not installed. """ + + if serial is None: + raise can.CanInterfaceNotImplementedError( + "the serial module is not installed" + ) + self.bit_rate = bitrate self.frame_type = frame_type self.op_mode = operation_mode self.filter_id = bytearray([0x00, 0x00, 0x00, 0x00]) self.mask_id = bytearray([0x00, 0x00, 0x00, 0x00]) if not channel: - raise ValueError("Must specify a serial port.") + raise can.CanInitializationError("Must specify a serial port.") self.channel_info = "Serial interface: " + channel - self.ser = serial.Serial( - channel, baudrate=baudrate, timeout=timeout, rtscts=False - ) + try: + self.ser = serial.Serial( + channel, baudrate=baudrate, timeout=timeout, rtscts=False + ) + except ValueError as error: + raise can.CanInitializationError( + "could not create the serial device" + ) from error super(SeeedBus, self).__init__(channel=channel, *args, **kwargs) self.init_frame() @@ -133,7 +148,10 @@ def init_frame(self, timeout=None): byte_msg.append(crc) logger.debug("init_frm:\t%s", byte_msg.hex()) - self.ser.write(byte_msg) + try: + self.ser.write(byte_msg) + except Exception as error: + raise can.CanInitializationError("could send init frame") from error def flush_buffer(self): self.ser.flushInput() @@ -160,7 +178,7 @@ def status_frame(self, timeout=None): byte_msg.append(crc) logger.debug("status_frm:\t%s", byte_msg.hex()) - self.ser.write(byte_msg) + self._write(byte_msg) def send(self, msg, timeout=None): """ @@ -197,7 +215,15 @@ def send(self, msg, timeout=None): byte_msg.append(0x55) logger.debug("sending:\t%s", byte_msg.hex()) - self.ser.write(byte_msg) + self._write(byte_msg) + + def _write(self, byte_msg: bytearray) -> None: + try: + self.ser.write(byte_msg) + except serial.PortNotOpenError as error: + raise can.CanOperationError("writing to closed port") from error + except serial.SerialTimeoutException as error: + raise can.CanTimeoutError() from error def _recv_internal(self, timeout): """ @@ -220,50 +246,64 @@ def _recv_internal(self, timeout): # or raise a SerialException rx_byte_1 = self.ser.read() + except serial.PortNotOpenError as error: + raise can.CanOperationError("reading from closed port") from error except serial.SerialException: return None, False if rx_byte_1 and ord(rx_byte_1) == 0xAA: - rx_byte_2 = ord(self.ser.read()) - time_stamp = time() - if rx_byte_2 == 0x55: - status = bytearray([0xAA, 0x55]) - status += bytearray(self.ser.read(18)) - logger.debug("status resp:\t%s", status.hex()) - - else: - length = int(rx_byte_2 & 0x0F) - is_extended = bool(rx_byte_2 & 0x20) - is_remote = bool(rx_byte_2 & 0x10) - if is_extended: - s_3_4_5_6 = bytearray(self.ser.read(4)) - arb_id = (struct.unpack(" Date: Wed, 14 Jul 2021 23:47:21 +0200 Subject: [PATCH 0717/1235] Specific Exceptions: Adapting usb2can interface (+ simplify + partially type) (#1091) * Simplify + partially type usb2can; use new exceptions --- can/interfaces/usb2can/serial_selector.py | 9 +- can/interfaces/usb2can/usb2canInterface.py | 44 +++--- .../usb2can/usb2canabstractionlayer.py | 144 +++++++----------- 3 files changed, 87 insertions(+), 110 deletions(-) diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index d9beb5df4..fcc951262 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -2,6 +2,7 @@ """ import logging +from typing import List try: import win32com.client @@ -10,7 +11,7 @@ raise -def WMIDateStringToDate(dtmDate): +def WMIDateStringToDate(dtmDate) -> str: if dtmDate[4] == 0: strDateTime = dtmDate[5] + "/" else: @@ -39,14 +40,12 @@ def WMIDateStringToDate(dtmDate): return strDateTime -def find_serial_devices(serial_matcher="ED"): +def find_serial_devices(serial_matcher: str = "ED") -> List[str]: """ Finds a list of USB devices where the serial number (partially) matches the given string. - :param str serial_matcher (optional): + :param serial_matcher: only device IDs starting with this string are returned - - :rtype: List[str] """ objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") objSWbemServices = objWMIService.ConnectServer(".", "root\\cimv2") diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 479c54764..4c086adea 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -1,12 +1,19 @@ """ -This interface is for Windows only, otherwise use socketCAN. +This interface is for Windows only, otherwise use SocketCAN. """ import logging from ctypes import byref - -from can import BusABC, Message, CanError -from .usb2canabstractionlayer import * +from typing import Optional + +from can import BusABC, Message, CanInitializationError, CanOperationError +from .usb2canabstractionlayer import Usb2CanAbstractionLayer, CanalMsg, CanalError +from .usb2canabstractionlayer import ( + flags_t, + IS_ERROR_FRAME, + IS_REMOTE_FRAME, + IS_ID_TYPE, +) from .serial_selector import find_serial_devices # Set up logging @@ -90,7 +97,7 @@ def __init__( flags=0x00000008, *args, bitrate=500000, - **kwargs + **kwargs, ): self.can = Usb2CanAbstractionLayer(dll) @@ -102,15 +109,15 @@ def __init__( if not device_id: devices = find_serial_devices() if not devices: - raise CanError("could not automatically find any device") + raise CanInitializationError("could not automatically find any device") device_id = devices[0] # convert to kb/s and cap: max rate is 1000 kb/s baudrate = min(int(bitrate // 1000), 1000) - self.channel_info = "USB2CAN device {}".format(device_id) + self.channel_info = f"USB2CAN device {device_id}" - connector = "{}; {}".format(device_id, baudrate) + connector = f"{device_id}; {baudrate}" self.handle = self.can.open(connector, flags_t) super().__init__( @@ -126,7 +133,7 @@ def send(self, msg, timeout=None): status = self.can.send(self.handle, byref(tx)) if status != CanalError.SUCCESS: - raise CanError("could not send message: status == {}".format(status)) + raise CanOperationError("could not send message", error_code=status) def _recv_internal(self, timeout): @@ -148,8 +155,7 @@ def _recv_internal(self, timeout): ): rx = None else: - log.error("Canal Error %s", status) - rx = None + raise CanOperationError("could not receive message", error_code=status) return rx, False @@ -157,28 +163,28 @@ def shutdown(self): """ Shuts down connection to the device safely. - :raise cam.CanError: is closing the connection did not work + :raise cam.CanOperationError: is closing the connection did not work """ status = self.can.close(self.handle) if status != CanalError.SUCCESS: - raise CanError("could not shut down bus: status == {}".format(status)) + raise CanOperationError("could not shut down bus", error_code=status) @staticmethod def _detect_available_configs(): return Usb2canBus.detect_available_configs() @staticmethod - def detect_available_configs(serial_matcher=None): + def detect_available_configs(serial_matcher: Optional[str] = None): """ - Uses the Windows Management Instrumentation to identify serial devices. + Uses the *Windows Management Instrumentation* to identify serial devices. - :param str serial_matcher (optional): + :param serial_matcher: search string for automatic detection of the device serial """ - if serial_matcher: - channels = find_serial_devices(serial_matcher) - else: + if serial_matcher is None: channels = find_serial_devices() + else: + channels = find_serial_devices(serial_matcher) return [{"interface": "usb2can", "channel": c} for c in channels] diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index ce8157a09..f3d7ad0ee 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -4,9 +4,9 @@ """ from ctypes import * -from struct import * -from enum import Enum +from enum import IntEnum import logging +from contextlib import contextmanager import can @@ -25,7 +25,7 @@ IS_ID_TYPE = 1 -class CanalError(Enum): +class CanalError(IntEnum): SUCCESS = 0 BAUDRATE = 1 BUS_OFF = 2 @@ -102,6 +102,14 @@ class CanalMsg(Structure): ] +@contextmanager +def error_check(error_message: str) -> None: + try: + yield + except Exception as error: + raise can.CanOperationError(error_message) from error + + class Usb2CanAbstractionLayer: """A low level wrapper around the usb2can library. @@ -112,21 +120,26 @@ def __init__(self, dll="usb2can.dll"): """ :type dll: str or path-like :param dll (optional): the path to the usb2can DLL to load - :raises OSError: if the DLL could not be loaded + + :raises can.CanInterfaceNotImplementedError: if the DLL could not be loaded """ - self.__m_dllBasic = windll.LoadLibrary(dll) + try: + self.__m_dllBasic = windll.LoadLibrary(dll) + if self.__m_dllBasic is None: + raise Exception("__m_dllBasic is None") - if self.__m_dllBasic is None: - log.warning("DLL failed to load at path: {}".format(dll)) + except Exception as error: + message = f"DLL failed to load at path: {dll}" + raise can.CanInterfaceNotImplementedError(message) from error - def open(self, configuration, flags): + def open(self, configuration: str, flags: int): """ Opens a CAN connection using `CanalOpen()`. - :param str configuration: the configuration: "device_id; baudrate" - :param int flags: the flags to be set + :param configuration: the configuration: "device_id; baudrate" + :param flags: the flags to be set - :raises can.CanError: if any error occurred + :raises can.CanInitializationError: if any error occurred :returns: Valid handle for CANAL API functions on success """ try: @@ -136,100 +149,59 @@ def open(self, configuration, flags): result = self.__m_dllBasic.CanalOpen(config_ascii, flags) except Exception as ex: # catch any errors thrown by this call and re-raise - raise can.CanError( - 'CanalOpen() failed, configuration: "{}", error: {}'.format( - configuration, ex - ) + raise can.CanInitializationError( + f'CanalOpen() failed, configuration: "{configuration}", error: {ex}' ) else: # any greater-than-zero return value indicates a success # (see https://grodansparadis.gitbooks.io/the-vscp-daemon/canal_interface_specification.html) # raise an error if the return code is <= 0 if result <= 0: - raise can.CanError( - 'CanalOpen() failed, configuration: "{}", return code: {}'.format( - configuration, result - ) + raise can.CanInitializationError( + f'CanalOpen() failed, configuration: "{configuration}"', + error_code=result, ) else: return result - def close(self, handle): - try: - res = self.__m_dllBasic.CanalClose(handle) - return CanalError(res) - except: - log.warning("Failed to close") - raise + def close(self, handle) -> CanalError: + with error_check("Failed to close"): + return CanalError(self.__m_dllBasic.CanalClose(handle)) - def send(self, handle, msg): - try: - res = self.__m_dllBasic.CanalSend(handle, msg) - return CanalError(res) - except: - log.warning("Sending error") - raise can.CanError("Failed to transmit frame") + def send(self, handle, msg) -> CanalError: + with error_check("Failed to transmit frame"): + return CanalError(self.__m_dllBasic.CanalSend(handle, msg)) - def receive(self, handle, msg): - try: - res = self.__m_dllBasic.CanalReceive(handle, msg) - return CanalError(res) - except: - log.warning("Receive error") - raise + def receive(self, handle, msg) -> CanalError: + with error_check("Receive error"): + return CanalError(self.__m_dllBasic.CanalReceive(handle, msg)) - def blocking_send(self, handle, msg, timeout): - try: - res = self.__m_dllBasic.CanalBlockingSend(handle, msg, timeout) - return CanalError(res) - except: - log.warning("Blocking send error") - raise + def blocking_send(self, handle, msg, timeout) -> CanalError: + with error_check("Blocking send error"): + return CanalError(self.__m_dllBasic.CanalBlockingSend(handle, msg, timeout)) - def blocking_receive(self, handle, msg, timeout): - try: - res = self.__m_dllBasic.CanalBlockingReceive(handle, msg, timeout) - return CanalError(res) - except: - log.warning("Blocking Receive Failed") - raise + def blocking_receive(self, handle, msg, timeout) -> CanalError: + with error_check("Blocking Receive Failed"): + return CanalError( + self.__m_dllBasic.CanalBlockingReceive(handle, msg, timeout) + ) - def get_status(self, handle, status): - try: - res = self.__m_dllBasic.CanalGetStatus(handle, status) - return CanalError(res) - except: - log.warning("Get status failed") - raise + def get_status(self, handle, status) -> CanalError: + with error_check("Get status failed"): + return CanalError(self.__m_dllBasic.CanalGetStatus(handle, status)) - def get_statistics(self, handle, statistics): - try: - res = self.__m_dllBasic.CanalGetStatistics(handle, statistics) - return CanalError(res) - except: - log.warning("Get Statistics failed") - raise + def get_statistics(self, handle, statistics) -> CanalError: + with error_check("Get Statistics failed"): + return CanalError(self.__m_dllBasic.CanalGetStatistics(handle, statistics)) def get_version(self): - try: - res = self.__m_dllBasic.CanalGetVersion() - return res - except: - log.warning("Failed to get version info") - raise + with error_check("Failed to get version info"): + return self.__m_dllBasic.CanalGetVersion() def get_library_version(self): - try: - res = self.__m_dllBasic.CanalGetDllVersion() - return res - except: - log.warning("Failed to get DLL version") - raise + with error_check("Failed to get DLL version"): + return self.__m_dllBasic.CanalGetDllVersion() def get_vendor_string(self): - try: - res = self.__m_dllBasic.CanalGetVendorString() - return res - except: - log.warning("Failed to get vendor string") - raise + with error_check("Failed to get vendor string"): + return self.__m_dllBasic.CanalGetVendorString() From 5b63bb28bd849dcf273cd648e32db53e21daf9d9 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 14 Jul 2021 23:49:07 +0200 Subject: [PATCH 0718/1235] Update github actions to test with latest python (#1069) * test 3.10.0-beta.2 * update pytest * use codecov action * generate coverage.xml * Update .github/workflows/build.yml Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> Co-authored-by: Brian Thorne --- .github/workflows/build.yml | 6 +++++- tox.ini | 14 +++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25834314a..c3af74077 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: experimental: [false] python-version: [3.6, 3.7, 3.8, 3.9, pypy3] include: - - python-version: 3.10.0-alpha.7 # Newest: https://github.com/actions/python-versions/blob/main/versions-manifest.json + - python-version: 3.10.0-beta.4 # Newest: https://github.com/actions/python-versions/blob/main/versions-manifest.json os: ubuntu-latest experimental: true fail-fast: false @@ -32,6 +32,10 @@ jobs: - name: Test with pytest via tox run: | tox -e gh + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + fail_ci_if_error: true format: runs-on: ubuntu-latest diff --git a/tox.ini b/tox.ini index 164dfdd8e..4709f9d82 100644 --- a/tox.ini +++ b/tox.ini @@ -2,10 +2,10 @@ [testenv] deps = - pytest~=5.3 - pytest-timeout~=1.3 - pytest-cov~=2.8 - coverage==4.5.* + pytest==6.2.*,>=6.2.4 + pytest-timeout==1.4.* + pytest-cov==2.12.* + coverage==5.5 codecov==2.1.10 hypothesis~=4.56 pyserial~=3.0 @@ -20,12 +20,8 @@ recreate = True passenv = CI GITHUB_* - CODECOV_* PY_COLORS -commands_post = - codecov -X gcov - [testenv:travis] passenv = CI @@ -39,7 +35,7 @@ commands_post = [pytest] testpaths = test -addopts = -v --timeout=300 --cov=can --cov-append --cov-report=term +addopts = -v --timeout=300 --cov=can --cov-config=tox.ini --cov-report=xml --cov-report=term [coverage:run] From 44bd901b55c58240c3071f59ca5691f25d559650 Mon Sep 17 00:00:00 2001 From: ausserlesh Date: Sat, 14 Aug 2021 01:06:23 +0200 Subject: [PATCH 0719/1235] fix(ctypesutil): correctly apply wrapped lib function as CLibrary member (#1116) Co-authored-by: Simon Ausserlechner --- can/ctypesutil.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index cfcaa6e08..7c1e1f573 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -55,13 +55,13 @@ def map_symbol( else: prototype = _FUNCTION_TYPE(restype) try: - self.func_name = prototype((func_name, self)) + func = prototype((func_name, self)) except AttributeError: raise ImportError( f'Could not map function "{func_name}" from library {self._name}' ) from None - self.func_name._name = func_name # pylint: disable=protected-access + func._name = func_name # pylint: disable=protected-access log.debug( 'Wrapped function "%s", result type: %s, error_check %s', func_name, @@ -70,9 +70,10 @@ def map_symbol( ) if errcheck is not None: - self.func_name.errcheck = errcheck + func.errcheck = errcheck - return self.func_name + setattr(self, func_name, func) + return func if sys.platform == "win32": From 2d79b5179002f9f246cd74223c9e857168d5de3e Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Wed, 18 Aug 2021 23:19:22 +0200 Subject: [PATCH 0720/1235] Viewer fixes (#1118) * The parsed arguments should be parsed to "_create_bus" and not the channel This was introduced in: 49a42b695723643e0cf5a9df2751118460523583 * Put the verbosity flag into the "optional" argument group * Clear the old data bytes when the length of the new message is shorter in the viewer script This bug could easily be replicated by sending: cansend vcan0 123#0102 Followed by: cansend vcan0 123#01 --- can/viewer.py | 14 ++++++++++++-- test/test_viewer.py | 22 +++++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/can/viewer.py b/can/viewer.py index 7d27a2fba..7e290bc4e 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -188,6 +188,16 @@ def draw_can_bus_message(self, msg, sorting=False): elif msg.dlc != self.ids[key]["msg"].dlc: length_changed = True + # Clear the old data bytes when the length of the new message is shorter + if msg.dlc < self.ids[key]["msg"].dlc: + self.draw_line( + self.ids[key]["row"], + # Start drawing at the last byte that is not in the new message + 52 + msg.dlc * 3, + # Draw spaces where the old bytes were drawn + " " * ((self.ids[key]["msg"].dlc - msg.dlc) * 3 - 1), + ) + if new_id_added or length_changed: # Increment the index if it was just added, but keep it if the length just changed row = len(self.ids) + 1 if new_id_added else self.ids[key]["row"] @@ -441,7 +451,7 @@ def parse_args(args): choices=sorted(can.VALID_INTERFACES), ) - parser.add_argument( + optional.add_argument( "-v", action="count", dest="verbosity", @@ -515,7 +525,7 @@ def main() -> None: parsed_args, can_filters, data_structs = parse_args(sys.argv[1:]) additional_config = {"can_filters": can_filters} if can_filters else {} - bus = _create_bus(parsed_args.channel, **additional_config) + bus = _create_bus(parsed_args, **additional_config) # print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") curses.wrapper(CanViewer, bus, data_structs) diff --git a/test/test_viewer.py b/test/test_viewer.py index 4e7a87a57..32ea69691 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -30,6 +30,7 @@ import struct import time import unittest +from collections import defaultdict from typing import Dict, Tuple, Union from unittest.mock import patch @@ -53,6 +54,7 @@ class StdscrDummy: def __init__(self): self.key_counter = 0 + self.draw_buffer = defaultdict(dict) @staticmethod def clear(): @@ -65,13 +67,18 @@ def erase(): @staticmethod def getmaxyx(): # Set y-value, so scrolling gets tested - return 1, 1 + # Set x-value, so the text will fit in the window + return 1, 100 - @staticmethod - def addstr(row, col, txt, *args): + def addstr(self, row, col, txt, *args): assert row >= 0 assert col >= 0 assert txt is not None + + # Save the text written into the buffer + for i, t in enumerate(txt): + self.draw_buffer[row][col + i] = t + # Raise an exception 50 % of the time, so we can make sure the code handles it if random.random() < 0.5: raise curses.error @@ -114,7 +121,7 @@ def setUpClass(cls): random.seed(0) def setUp(self): - stdscr = StdscrDummy() + self.stdscr_dummy = StdscrDummy() config = {"interface": "virtual", "receive_own_messages": True} bus = can.Bus(**config) data_structs = None @@ -145,7 +152,7 @@ def setUp(self): patch_resizeterm.start() self.addCleanup(patch_resizeterm.stop) - self.can_viewer = CanViewer(stdscr, bus, data_structs, testing=True) + self.can_viewer = CanViewer(self.stdscr_dummy, bus, data_structs, testing=True) def tearDown(self): # Run the viewer after the test, this is done, so we can receive the CAN-Bus messages and make sure that they @@ -213,6 +220,11 @@ def test_receive(self): if _id["msg"].arbitration_id == 0x101: # Check if the counter is reset when the length has changed self.assertEqual(_id["count"], 1) + + # Make sure the line has been cleared after the shorted message was send + for col, v in self.stdscr_dummy.draw_buffer[_id["row"]].items(): + if col >= 52 + _id["msg"].dlc * 3: + self.assertEqual(v, " ") elif _id["msg"].arbitration_id == 0x123456: # Check if the counter is incremented if _id["dt"] == 0: From 9f7077c79fa8ac02be178f451ae94a604866ba0c Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Wed, 18 Aug 2021 17:20:09 -0400 Subject: [PATCH 0721/1235] Speed up imports by removing pkg_resources (#1110) * Add method to calculate load on an interface * Fix usage of isinstance for calc_load in bus.py * Include ID length in bus load calculation * Remove pkg_resources to speed up import time * Readd pkg_resources but default to importlib to speed up import time * Resolve handling of importlib entry_points for plugins * Fall back to ImportError only if importlib is missing * Remove pkg_resources to speed up import time * Readd pkg_resources but default to importlib to speed up import time * Resolve handling of importlib entry_points for plugins * Fall back to ImportError only if importlib is missing * Load user-installed interface modules only on demand if importlib is not installed * Revert "Add method to calculate load on an interface" This reverts commit 43c0c55b * Revert "Load user-installed interface modules only on demand if importlib is not installed" This reverts commit 2fed5322 * Rename entry_points back to iter_entry_points --- can/interfaces/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index bfedea60a..7020e2f68 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -2,10 +2,6 @@ Interfaces contain low level implementations that interact with CAN hardware. """ -import warnings -from pkg_resources import iter_entry_points - - # interface_name => (module, classname) BACKENDS = { "kvaser": ("can.interfaces.kvaser", "KvaserBus"), @@ -31,11 +27,14 @@ "neousys": ("can.interfaces.neousys", "NeousysBus"), } -BACKENDS.update( - { - interface.name: (interface.module_name, interface.attrs[0]) - for interface in iter_entry_points("can.interface") - } -) +try: + from importlib.metadata import entry_points + entry = entry_points() + if 'can.interface' in entry: + BACKENDS.update({interface.name: tuple(interface.value.split(':')) for interface in entry['can.interface']}) +except ImportError: + from pkg_resources import iter_entry_points + entry = iter_entry_points("can.interface") + BACKENDS.update({interface.name: (interface.module_name, interface.attrs[0]) for interface in entry}) VALID_INTERFACES = frozenset(list(BACKENDS.keys())) From 1979a75ce505a7d3a29651bd34f7fc111a33d730 Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Wed, 18 Aug 2021 21:22:20 +0000 Subject: [PATCH 0722/1235] Format code with black --- can/interfaces/__init__.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 7020e2f68..884665934 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -29,12 +29,24 @@ try: from importlib.metadata import entry_points + entry = entry_points() - if 'can.interface' in entry: - BACKENDS.update({interface.name: tuple(interface.value.split(':')) for interface in entry['can.interface']}) + if "can.interface" in entry: + BACKENDS.update( + { + interface.name: tuple(interface.value.split(":")) + for interface in entry["can.interface"] + } + ) except ImportError: from pkg_resources import iter_entry_points + entry = iter_entry_points("can.interface") - BACKENDS.update({interface.name: (interface.module_name, interface.attrs[0]) for interface in entry}) + BACKENDS.update( + { + interface.name: (interface.module_name, interface.attrs[0]) + for interface in entry + } + ) VALID_INTERFACES = frozenset(list(BACKENDS.keys())) From 596f07dbe8fa0301db44764adedfb37c0c1eadff Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Thu, 9 Sep 2021 22:30:01 -0400 Subject: [PATCH 0723/1235] Avoid flooding the logger with ~500 errors when they are the same (#1125) Only log/warn each unique error once with repeat count, when needed. Adding dot to separate end the short description sentence before the long description. --- can/interfaces/ics_neovi/neovi_bus.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 22087b4a4..ec08e80ef 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -11,7 +11,7 @@ import logging import os import tempfile -from collections import deque, defaultdict +from collections import deque, defaultdict, Counter from itertools import cycle from threading import Event from warnings import warn @@ -89,7 +89,7 @@ def __init__( severity: int, restart_needed: int, ): - super().__init__(f"{description_short} {description_long}", error_code) + super().__init__(f"{description_short}. {description_long}", error_code) self.description_short = description_short self.description_long = description_long self.severity = severity @@ -319,9 +319,12 @@ def _process_msg_queue(self, timeout=0.1): if errors: logger.warning("%d error(s) found", errors) - for msg in ics.get_error_messages(self.dev): - error = ICSOperationError(*msg) - logger.warning(error) + for msg, count in Counter(ics.get_error_messages(self.dev)).items(): + error = ICSApiError(*msg) + if count > 1: + logger.warning(f"{error} (Repeated {count} times)") + else: + logger.warning(error) def _get_timestamp_for_msg(self, ics_msg): if self._use_system_timestamp: From 71580d8b369db213a3edab657c2c737e6a4b3684 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 24 Sep 2021 17:02:20 +1000 Subject: [PATCH 0724/1235] canalystii: Fix transmitting onto a busy bus (#1114) * canalystii: Add a note about the library * canalystii: Retry if sending fails the first time --- can/interfaces/canalystii.py | 5 ++++- doc/interfaces/canalystii.rst | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index e0bdf006e..da8ab1f99 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -32,6 +32,9 @@ class VCI_CAN_OBJ(Structure): ] +SENDTYPE_ALLOW_RETRY = 0 # Retry send if there is bus contention +SENDTYPE_ONE_SHOT = 1 # Drop the message if transmission fails first time + VCI_USBCAN2 = 4 STATUS_OK = 0x01 @@ -145,7 +148,7 @@ def send(self, msg, timeout=None): msg.arbitration_id, 0, 0, - 1, + SENDTYPE_ALLOW_RETRY, msg.is_remote_frame, extern_flag, msg.dlc, diff --git a/doc/interfaces/canalystii.rst b/doc/interfaces/canalystii.rst index 687f61fcc..ef27ad868 100644 --- a/doc/interfaces/canalystii.rst +++ b/doc/interfaces/canalystii.rst @@ -4,6 +4,9 @@ CANalyst-II CANalyst-II(+) is a USB to CAN Analyzer. The controlcan library is originally developed by `ZLG ZHIYUAN Electronics`_. +.. note:: + + Use of this interface requires the ``ControlCAN.dll`` (Windows) or ``libcontrolcan.so`` vendor library to be placed in the Python working directory. Bus --- From 352b1f1f03011497753cd34757fd709ec2051671 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 26 Oct 2021 07:43:30 +1100 Subject: [PATCH 0725/1235] ci: Update to pypy-3.7 and add Python 3.10 on Ubuntu & MacOS (#1143) * ci: Change pypy3 to pypy-3.7 PyPy 3.6 is no longer available on the macos-latest (macOS-11) image, see https://github.com/actions/virtual-environments/issues/4060 * ci: Use release version Python 3.10, run syntax checks under 3.10 * listener: Fix PyPy 3.7 error passing a timeout parameter together with block=False * test: Ignore Hypothesis float generation issue with Windows+PyPy-3.7 Hypothesis throws a "Flaky" error for inconsistent data on PyPy-3.7 + Windows only, when generating timestamp fields. I think this is a bug in either Hypothesis or PyPy, but I don't know which. --- .github/workflows/build.yml | 17 +++++++++++------ can/listener.py | 5 ++++- test/test_message_class.py | 10 +++++++++- tox.ini | 2 +- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3af74077..bb4c9a03b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,11 +13,16 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] experimental: [false] - python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + python-version: ["3.6", "3.7", "3.8", "3.9", "pypy-3.7"] include: - - python-version: 3.10.0-beta.4 # Newest: https://github.com/actions/python-versions/blob/main/versions-manifest.json - os: ubuntu-latest - experimental: true + # Skipping Py 3.10 on Windows until windows-curses has a cp310 wheel, + # see https://github.com/zephyrproject-rtos/windows-curses/issues/26 + - os: ubuntu-latest + experimental: false + python-version: "3.10" + - os: macos-latest + experimental: false + python-version: "3.10" fail-fast: false steps: - uses: actions/checkout@v2 @@ -41,10 +46,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/can/listener.py b/can/listener.py index a4a535887..815c7ecef 100644 --- a/can/listener.py +++ b/can/listener.py @@ -116,7 +116,10 @@ def get_message(self, timeout: float = 0.5) -> Optional[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) + if self.is_stopped: + return self.buffer.get(block=False) + else: + return self.buffer.get(block=True, timeout=timeout) except Empty: return None diff --git a/test/test_message_class.py b/test/test_message_class.py index 997f6df8d..d0908e363 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -9,12 +9,15 @@ from datetime import timedelta from hypothesis import given, settings +import hypothesis.errors import hypothesis.strategies as st from can import Message from .message_helper import ComparingMessagesTestCase -from .config import IS_GITHUB_ACTIONS +from .config import IS_GITHUB_ACTIONS, IS_WINDOWS, IS_PYPY + +import pytest class TestMessageClass(unittest.TestCase): @@ -42,6 +45,11 @@ class TestMessageClass(unittest.TestCase): max_examples=2000, deadline=None if IS_GITHUB_ACTIONS else timedelta(milliseconds=500), ) + @pytest.mark.xfail( + IS_WINDOWS and IS_PYPY, + raises=hypothesis.errors.Flaky, + reason="Hypothesis generates inconistent timestamp floats on Windows+PyPy-3.7", + ) def test_methods(self, **kwargs): is_valid = not ( ( diff --git a/tox.ini b/tox.ini index 4709f9d82..7834f8d73 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ deps = pytest-cov==2.12.* coverage==5.5 codecov==2.1.10 - hypothesis~=4.56 + hypothesis~=6.24.0 pyserial~=3.0 parameterized~=0.8 From d4aefcf3bd63cc72e262b25611df497d14e71494 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Mon, 25 Oct 2021 21:24:11 +0200 Subject: [PATCH 0726/1235] Tiny: Fix doc in TestCsvFileFormat --- test/logformats_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 9f362faf3..347785b4b 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -670,7 +670,7 @@ def _setup_instance(self): class TestCsvFileFormat(ReaderWriterTest): - """Tests can.ASCWriter and can.ASCReader""" + """Tests can.CSVWriter and can.CSVReader""" def _setup_instance(self): super()._setup_instance_helper( @@ -731,7 +731,7 @@ 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 """ From 784b8711284010d99ff8ced05fcd0cbd61cb8b15 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 26 Oct 2021 08:57:20 +0200 Subject: [PATCH 0727/1235] Update Mergify rules (#1146) * Update Mergify rules This change was required due to the deprection of our current setup (https://blog.mergify.io/strict-mode-deprecation/). It was shown in the mergify results tab on our PRs and would cause the Mergify setup to fail from December on. I hope that I nowconfigured everything correctly. * Fix mergify to use Github Actions --- .mergify.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 9a6141e6d..e2ded27f0 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,16 +1,22 @@ +queue_rules: + - name: default + conditions: + - "status-success=test,format" # "GitHub Actions works slightly differently [...], only the job name is used." + pull_request_rules: - name: Automatic merge passing PR on up to date branch with approving CR conditions: + - "base=develop" - "#approved-reviews-by>=1" - "#review-requested=0" - "#changes-requested-reviews-by=0" - - "status-success=Travis CI - Pull Request" + - "status-success=test,format" - "label!=work-in-progress" actions: - merge: - method: merge - strict: smart+fasttrack - - name: Request Brian to review changes on core api. + queue: + name: default + + - name: Request Brian to review changes to the core API conditions: - "-files~=^can/interfaces/$" - "-closed" @@ -20,7 +26,7 @@ pull_request_rules: users: - hardbyte - - name: delete head branch after merge + - name: Delete head branch after merge conditions: - merged actions: From beb57279b0769135d6a0877cfa0f19cc4009db27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samu=20a=20Kov=C3=A1cs?= Date: Tue, 26 Oct 2021 12:27:42 +0200 Subject: [PATCH 0728/1235] Add app_name to logger module (#1142) * Add app_name to logger module * Fix formatting Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/logger.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/can/logger.py b/can/logger.py index 0798fa827..41085dc4e 100644 --- a/can/logger.py +++ b/can/logger.py @@ -57,6 +57,11 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: help="Bitrate to use for the data phase in case of CAN-FD.", ) + parser.add_argument( + "app_name", + help="""App name can be necessary in the initializer. For example with Vector.""", + ) + def _append_filter_argument( parser: Union[ @@ -97,6 +102,8 @@ def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus: config["fd"] = True if parsed_args.data_bitrate: config["data_bitrate"] = parsed_args.data_bitrate + if parse_args.app_name: + config["app_name"] = parsed_args.app_name return Bus(parsed_args.channel, **config) # type: ignore From 1e171933c0b818d7e07423cd9b6fb10bca9a365c Mon Sep 17 00:00:00 2001 From: Alexey Tereshenkov Date: Wed, 27 Oct 2021 20:50:25 +0100 Subject: [PATCH 0729/1235] Make setuptools a package dependency --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 6ad5e88d3..3e4f7144c 100644 --- a/setup.py +++ b/setup.py @@ -83,9 +83,7 @@ # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=3.6", install_requires=[ - # Note setuptools provides pkg_resources which python-can makes use of, - # but we assume it is already installed. - # "setuptools", + "setuptools", "wrapt~=1.10", 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"', "typing_extensions>=3.10.0.0", From c9c30480d1159fbe357e5dd5584773de848ba580 Mon Sep 17 00:00:00 2001 From: Kovacs Samuel Date: Tue, 26 Oct 2021 19:20:33 +0200 Subject: [PATCH 0730/1235] Correct app_name argument --- can/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/logger.py b/can/logger.py index 41085dc4e..7e0b32931 100644 --- a/can/logger.py +++ b/can/logger.py @@ -58,7 +58,7 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: ) parser.add_argument( - "app_name", + "--app_name", help="""App name can be necessary in the initializer. For example with Vector.""", ) @@ -102,7 +102,7 @@ def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus: config["fd"] = True if parsed_args.data_bitrate: config["data_bitrate"] = parsed_args.data_bitrate - if parse_args.app_name: + if parsed_args.app_name: config["app_name"] = parsed_args.app_name return Bus(parsed_args.channel, **config) # type: ignore From 18f346211bf937593fbe2304d3f850be2474a129 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 24 Aug 2021 20:10:07 +1000 Subject: [PATCH 0731/1235] canalystii: Switch from binary library to pure Python driver - Project 'canalystii' hosted at https://github.com/projectgus/python-canalystii Compared to previous implementation: - No more binary library, can support MacOS as well as Windows and Linux - Can receive on all enabled channels (previous implementation received on one channel only) - Performance is slightly slower but still easily possible to receive at max message rate on both channels - send timeout is now based on CAN layer send not USB layer send - User configurable software rx message queue size - Adds tests (mocking at the layer of the underlying driver 'canalystii') - Adds type annotations - Replaces Timing0 & Timing1 parameters (for BTR0 & BTR1) with the BitTiming class Closes #726 --- can/interfaces/canalystii.py | 319 +++++++++++++++--------------- doc/interfaces/canalystii.rst | 27 ++- setup.py | 1 + test/test_interface_canalystii.py | 103 ++++++++++ tox.ini | 3 + 5 files changed, 291 insertions(+), 162 deletions(-) create mode 100755 test/test_interface_canalystii.py diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index da8ab1f99..256ac582c 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,159 +1,112 @@ -from ctypes import * +import collections +from ctypes import c_ubyte import logging -import platform -from can import BusABC, Message +import canalystii as driver +import time +import warnings +from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union +from can import BitTiming, BusABC, Message +from can.exceptions import CanTimeoutError +from can.typechecking import CanFilters logger = logging.getLogger(__name__) -class VCI_INIT_CONFIG(Structure): - _fields_ = [ - ("AccCode", c_int32), - ("AccMask", c_int32), - ("Reserved", c_int32), - ("Filter", c_ubyte), - ("Timing0", c_ubyte), - ("Timing1", c_ubyte), - ("Mode", c_ubyte), - ] - - -class VCI_CAN_OBJ(Structure): - _fields_ = [ - ("ID", c_uint), - ("TimeStamp", c_int), - ("TimeFlag", c_byte), - ("SendType", c_byte), - ("RemoteFlag", c_byte), - ("ExternFlag", c_byte), - ("DataLen", c_byte), - ("Data", c_ubyte * 8), - ("Reserved", c_byte * 3), - ] - - -SENDTYPE_ALLOW_RETRY = 0 # Retry send if there is bus contention -SENDTYPE_ONE_SHOT = 1 # Drop the message if transmission fails first time - -VCI_USBCAN2 = 4 - -STATUS_OK = 0x01 -STATUS_ERR = 0x00 - -TIMING_DICT = { - 5000: (0xBF, 0xFF), - 10000: (0x31, 0x1C), - 20000: (0x18, 0x1C), - 33330: (0x09, 0x6F), - 40000: (0x87, 0xFF), - 50000: (0x09, 0x1C), - 66660: (0x04, 0x6F), - 80000: (0x83, 0xFF), - 83330: (0x03, 0x6F), - 100000: (0x04, 0x1C), - 125000: (0x03, 0x1C), - 200000: (0x81, 0xFA), - 250000: (0x01, 0x1C), - 400000: (0x80, 0xFA), - 500000: (0x00, 0x1C), - 666000: (0x80, 0xB6), - 800000: (0x00, 0x16), - 1000000: (0x00, 0x14), -} - -try: - if platform.system() == "Windows": - CANalystII = WinDLL("./ControlCAN.dll") - else: - CANalystII = CDLL("./libcontrolcan.so") - logger.info("Loaded CANalystII library") -except OSError as e: - CANalystII = None - logger.info("Cannot load CANalystII library") - - class CANalystIIBus(BusABC): def __init__( self, - channel, - device=0, - bitrate=None, - Timing0=None, - Timing1=None, - can_filters=None, - **kwargs, + channel: Union[int, Sequence[int], str] = (0, 1), + device: int = 0, + bitrate: Optional[int] = None, + bit_timing: Optional[BitTiming] = None, + can_filters: Optional[CanFilters] = None, + rx_queue_size: Optional[int] = None, + **kwargs: Dict[str, Any], ): """ - :param channel: channel number - :param device: device number - :param bitrate: CAN network bandwidth (bits/s) - :param Timing0: customize the timing register if bitrate is not specified - :param Timing1: - :param can_filters: filters for packet + :param channel: + Optional channel number, list/tuple of multiple channels, or comma + separated string of channels. Default is to configure both + channels. + :param device: + Optional USB device number. Default is 0 (first device found). + :param bitrate: + CAN bitrate in bits/second. Required unless the bit_timing argument is set. + :param bit_timing: + Optional BitTiming instance to use for custom bit timing setting. + If this argument is set then it overrides the bitrate argument. + :param can_filters: + Optional filters for received CAN messages. + :param rx_queue_size: + If set, software received message queue can only grow to this many + messages (for all channels) before older messages are dropped """ super().__init__(channel=channel, can_filters=can_filters, **kwargs) - if isinstance(channel, (list, tuple)): - self.channels = channel - elif isinstance(channel, int): - self.channels = [channel] - else: + if not (bitrate or bit_timing): + raise ValueError("Either bitrate or bit_timing argument is required") + + if isinstance(channel, str): # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(",")] + elif isinstance(channel, int): + self.channels = [channel] + else: # Sequence[int] + self.channels = [c for c in channel] - self.device = device - - self.channel_info = "CANalyst-II: device {}, channels {}".format( - self.device, self.channels - ) - - if bitrate is not None: - try: - Timing0, Timing1 = TIMING_DICT[bitrate] - except KeyError: - raise ValueError("Bitrate is not supported") - - if Timing0 is None or Timing1 is None: - raise ValueError("Timing registers are not set") - - self.init_config = VCI_INIT_CONFIG(0, 0xFFFFFFFF, 0, 1, Timing0, Timing1, 0) + self.rx_queue = collections.deque( + maxlen=rx_queue_size + ) # type: Deque[Tuple[int, driver.Message]] - if CANalystII.VCI_OpenDevice(VCI_USBCAN2, self.device, 0) == STATUS_ERR: - logger.error("VCI_OpenDevice Error") + self.channel_info = f"CANalyst-II: device {device}, channels {self.channels}" + self.device = driver.CanalystDevice(device_index=device) for channel in self.channels: - status = CANalystII.VCI_InitCAN( - VCI_USBCAN2, self.device, channel, byref(self.init_config) - ) - if status == STATUS_ERR: - logger.error("VCI_InitCAN Error") - self.shutdown() - return - - if CANalystII.VCI_StartCAN(VCI_USBCAN2, self.device, channel) == STATUS_ERR: - logger.error("VCI_StartCAN Error") - self.shutdown() - return - - def send(self, msg, timeout=None): - """ + if bit_timing: + try: + if bit_timing.f_clock != 8_000_000: + warnings.warn( + f"bit_timing.f_clock value {bit_timing.f_clock} " + "doesn't match expected device f_clock 8MHz." + ) + except ValueError: + pass # f_clock not specified + self.device.init( + channel, timing0=bit_timing.btr0, timing1=bit_timing.btr1 + ) + else: + self.device.init(channel, bitrate=bitrate) + + # Delay to use between each poll for new messages + # + # The timeout is deliberately kept low to avoid the possibility of + # a hardware buffer overflow. This value was determined + # experimentally, but the ideal value will depend on the specific + # system. + RX_POLL_DELAY = 0.020 + + def send(self, msg: Message, timeout: Optional[float] = None) -> None: + """Send a CAN message to the bus :param msg: message to send - :param timeout: timeout is not used here - :return: + :param timeout: timeout (in seconds) to wait for the TX queue to clear. + If set to ``None`` (default) the function returns immediately. + + Note: Due to limitations in the device firmware and protocol, the + timeout will not trigger if there are problems with CAN arbitration, + but only if the device is overloaded with a backlog of too many + messages to send. """ - extern_flag = 1 if msg.is_extended_id else 0 - raw_message = VCI_CAN_OBJ( + raw_message = driver.Message( msg.arbitration_id, - 0, - 0, - SENDTYPE_ALLOW_RETRY, + 0, # timestamp + 1, # time_flag + 0, # send_type msg.is_remote_frame, - extern_flag, + msg.is_extended_id, msg.dlc, (c_ubyte * 8)(*msg.data), - (c_byte * 3)(*[0, 0, 0]), ) if msg.channel is not None: @@ -161,44 +114,96 @@ def send(self, msg, timeout=None): elif len(self.channels) == 1: channel = self.channels[0] else: - raise ValueError("msg.channel must be set when using multiple channels.") + raise ValueError( + "Message channel must be set when using multiple channels." + ) - CANalystII.VCI_Transmit( - VCI_USBCAN2, self.device, channel, byref(raw_message), 1 + send_result = self.device.send(channel, [raw_message], timeout) + if timeout is not None and not send_result: + raise CanTimeoutError(f"Send timed out after {timeout} seconds") + + def _recv_from_queue(self) -> Tuple[Message, bool]: + """Return a message from the internal receive queue""" + channel, raw_msg = self.rx_queue.popleft() + + # Protocol timestamps are in units of 100us, convert to seconds as + # float + timestamp = raw_msg.timestamp * 100e-6 + + return ( + Message( + channel=channel, + timestamp=timestamp, + arbitration_id=raw_msg.can_id, + is_extended_id=raw_msg.extended, + is_remote_frame=raw_msg.remote, + dlc=raw_msg.data_len, + data=bytes(raw_msg.data), + ), + False, ) - def _recv_internal(self, timeout=None): + def poll_received_messages(self) -> None: + """Poll new messages from the device into the rx queue but don't + return any message to the caller + + Calling this function isn't necessary as polling the device is done + automatically when calling recv(). This function is for the situation + where an application needs to empty the hardware receive buffer without + consuming any message. + """ + for channel in self.channels: + self.rx_queue.extend( + (channel, raw_msg) for raw_msg in self.device.receive(channel) + ) + + def _recv_internal( + self, timeout: Optional[float] = None + ) -> Tuple[Optional[Message], bool]: """ :param timeout: float in seconds :return: """ - raw_message = VCI_CAN_OBJ() - timeout = -1 if timeout is None else int(timeout * 1000) + if self.rx_queue: + return self._recv_from_queue() - status = CANalystII.VCI_Receive( - VCI_USBCAN2, self.device, self.channels[0], byref(raw_message), 1, timeout - ) - if status <= STATUS_ERR: - return None, False + deadline = None + while deadline is None or time.time() < deadline: + if deadline is None and timeout is not None: + deadline = time.time() + timeout + + self.poll_received_messages() + + if self.rx_queue: + return self._recv_from_queue() + + # If blocking on a timeout, add a sleep before we loop again + # to reduce CPU usage. + if deadline is None or deadline - time.time() > 0.050: + time.sleep(self.RX_POLL_DELAY) + + return (None, False) + + def flush_tx_buffer(self, channel: Optional[int] = None) -> None: + """Flush the TX buffer of the device. + + :param channel: + Optional channel number to flush. If set to None, all initialized + channels are flushed. + + Note that because of protocol limitations this function returning + doesn't mean that messages have been sent, it may also mean they + failed to send. + """ + if channel: + self.device.flush_tx_buffer(channel, float("infinity")) else: - return ( - Message( - timestamp=raw_message.TimeStamp if raw_message.TimeFlag else 0.0, - arbitration_id=raw_message.ID, - is_remote_frame=raw_message.RemoteFlag, - is_extended_id=raw_message.ExternFlag, - channel=0, - dlc=raw_message.DataLen, - data=raw_message.Data, - ), - False, - ) + for ch in self.channels: + self.device.flush_tx_buffer(ch, float("infinity")) - def flush_tx_buffer(self): + def shutdown(self) -> None: for channel in self.channels: - CANalystII.VCI_ClearBuffer(VCI_USBCAN2, self.device, channel) - - def shutdown(self): - CANalystII.VCI_CloseDevice(VCI_USBCAN2, self.device) + self.device.stop(channel) + self.device = None diff --git a/doc/interfaces/canalystii.rst b/doc/interfaces/canalystii.rst index ef27ad868..375e1b754 100644 --- a/doc/interfaces/canalystii.rst +++ b/doc/interfaces/canalystii.rst @@ -1,17 +1,34 @@ CANalyst-II =========== -CANalyst-II(+) is a USB to CAN Analyzer. The controlcan library is originally developed by -`ZLG ZHIYUAN Electronics`_. +CANalyst-II is a USB to CAN Analyzer device produced by Chuangxin Technology. + +Install: ``pip install "python-can[canalystii]"`` + +Supported platform +------------------ + +Windows, Linux and Mac. .. note:: - Use of this interface requires the ``ControlCAN.dll`` (Windows) or ``libcontrolcan.so`` vendor library to be placed in the Python working directory. + The backend driver depends on `pyusb ` so a ``pyusb`` backend driver library such as ``libusb`` must be installed. On Windows a tool such as `Zadig ` can be used to set the Canalyst-II USB device driver to ``libusb-win32``. + +Limitations +----------- + +Multiple Channels +^^^^^^^^^^^^^^^^^ + +The USB protocol transfers messages grouped by channel. Messages received on channel 0 and channel 1 may be returned by software out of order between the two channels (although inside each channel, all messages are in order). The timestamp field of each message comes from the hardware and shows the exact time each message was received. To compare ordering of messages on channel 0 vs channel 1, sort the received messages by the timestamp field first. + +Backend Driver +-------------- + +The backend driver module `canalystii ` must be installed to use this interface. This open source driver is unofficial and based on reverse engineering. Earlier versions of python-can required a binary library from the vendor for this functionality. Bus --- .. autoclass:: can.interfaces.canalystii.CANalystIIBus - -.. _ZLG ZHIYUAN Electronics: http://www.zlg.com/can/can/product/id/42.html diff --git a/setup.py b/setup.py index 3e4f7144c..6e2dcd95a 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ "seeedstudio": ["pyserial>=3.0"], "serial": ["pyserial~=3.0"], "neovi": ["filelock", "python-ics>=2.12"], + "canalystii": ["canalystii>=0.1.0"], "cantact": ["cantact>=0.0.7"], "gs_usb": ["gs_usb>=0.2.1"], "nixnet": ["nixnet>=0.3.1"], diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py new file mode 100755 index 000000000..467473671 --- /dev/null +++ b/test/test_interface_canalystii.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +""" +""" + +import time +import unittest +from unittest.mock import Mock, patch, call +from ctypes import c_ubyte + +import canalystii as driver # low-level driver module, mock out this layer +import can +from can.interfaces.canalystii import CANalystIIBus + + +def create_mock_device(): + return patch("canalystii.CanalystDevice") + + +class CanalystIITest(unittest.TestCase): + def test_initialize_from_constructor(self): + with create_mock_device() as mock_device: + instance = mock_device.return_value + bus = CANalystIIBus(bitrate=1000000) + instance.init.assert_has_calls( + [ + call(0, bitrate=1000000), + call(1, bitrate=1000000), + ] + ) + + def test_initialize_single_channel_only(self): + for channel in 0, 1: + with create_mock_device() as mock_device: + instance = mock_device.return_value + bus = CANalystIIBus(channel, bitrate=1000000) + instance.init.assert_called_once_with(channel, bitrate=1000000) + + def test_initialize_with_timing_registers(self): + with create_mock_device() as mock_device: + instance = mock_device.return_value + timing = can.BitTiming(btr0=0x03, btr1=0x6F) + bus = CANalystIIBus(bitrate=None, bit_timing=timing) + instance.init.assert_has_calls( + [ + call(0, timing0=0x03, timing1=0x6F), + call(1, timing0=0x03, timing1=0x6F), + ] + ) + + def test_missing_bitrate(self): + with self.assertRaises(ValueError) as cm: + bus = CANalystIIBus(0, bitrate=None, bit_timing=None) + self.assertIn("bitrate", str(cm.exception)) + + def test_invalid_bit_timing(self): + with create_mock_device() as mock_device: + with self.assertRaises(ValueError) as cm: + invalid_timings = can.BitTiming() + CANalystIIBus(0, bit_timing=invalid_timings) + + def test_receive_message(self): + driver_message = driver.Message( + can_id=0x333, + timestamp=1000000, + time_flag=1, + send_type=0, + remote=False, + extended=False, + data_len=8, + data=(c_ubyte * 8)(*range(8)), + ) + + with create_mock_device() as mock_device: + instance = mock_device.return_value + instance.receive.return_value = [driver_message] + bus = CANalystIIBus(bitrate=1000000) + msg = bus.recv(0) + self.assertEqual(driver_message.can_id, msg.arbitration_id) + self.assertEqual(bytearray(driver_message.data), msg.data) + + def test_send_message(self): + message = can.Message(arbitration_id=0x123, data=[3] * 8, is_extended_id=False) + + with create_mock_device() as mock_device: + instance = mock_device.return_value + bus = CANalystIIBus(channel=0, bitrate=5000000) + bus.send(message) + instance.send.assert_called_once() + + (channel, driver_messages, _timeout), _kwargs = instance.send.call_args + self.assertEqual(0, channel) + + self.assertEqual(1, len(driver_messages)) + + driver_message = driver_messages[0] + self.assertEqual(message.arbitration_id, driver_message.can_id) + self.assertEqual(message.data, bytearray(driver_message.data)) + self.assertEqual(8, driver_message.data_len) + + +if __name__ == "__main__": + unittest.main() diff --git a/tox.ini b/tox.ini index 7834f8d73..c8e6b483d 100644 --- a/tox.ini +++ b/tox.ini @@ -14,6 +14,9 @@ deps = commands = pytest {posargs} +extras = + canalystii + recreate = True [testenv:gh] From 69b70db427343bb29a596a3eb3f87d9c1fe715cb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 27 Oct 2021 17:37:36 +1100 Subject: [PATCH 0732/1235] gs_usb: Update the libusb-win32 documentation to provide a link --- doc/interfaces/gs_usb.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index 88646547a..aae6c39f5 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -29,7 +29,9 @@ Supported platform Windows, Linux and Mac. -Note: Since ``pyusb`` with ```libusb0``` as backend is used, ``libusb-win32`` usb driver is required to be installed in Windows. +.. note:: + + The backend driver depends on `pyusb ` so a ``pyusb`` backend driver library such as ``libusb`` must be installed. On Windows a tool such as `Zadig ` can be used to set the USB device driver to ``libusb-win32``. Supplementary Info on ``gs_usb`` From b37a9905147d6e400ac61dd14e6d610aba30103c Mon Sep 17 00:00:00 2001 From: Felix Nieuwenhuizen Date: Sat, 6 Nov 2021 12:32:59 +0100 Subject: [PATCH 0733/1235] add ETAS interface --- CONTRIBUTORS.txt | 1 + can/interfaces/__init__.py | 1 + can/interfaces/etas/__init__.py | 392 +++++++++++++++++++ can/interfaces/etas/boa.py | 665 ++++++++++++++++++++++++++++++++ doc/interfaces.rst | 4 +- doc/interfaces/etas.rst | 33 ++ test/back2back_test.py | 20 + 7 files changed, 1114 insertions(+), 2 deletions(-) create mode 100644 can/interfaces/etas/__init__.py create mode 100644 can/interfaces/etas/boa.py create mode 100644 doc/interfaces/etas.rst diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b7ac9fbd4..aa96cd405 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -28,3 +28,4 @@ Jan Goeteyn "ykzheng" Lear Corporation Nick Black +Felix Nieuwenhuizen diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 884665934..8dd0d7706 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -25,6 +25,7 @@ "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"), "neousys": ("can.interfaces.neousys", "NeousysBus"), + "etas": ("can.interfaces.etas", "EtasBus"), } try: diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py new file mode 100644 index 000000000..8324bf6ea --- /dev/null +++ b/can/interfaces/etas/__init__.py @@ -0,0 +1,392 @@ +from typing import Any, List, Optional, Tuple + +import can +from .boa import * + + +class EtasBus(can.BusABC): + def __init__( + self, + channel: Any, + can_filters: Optional[can.typechecking.CanFilters] = None, + receive_own_messages: bool = False, + bitrate: int = 1000000, + fd: bool = True, + data_bitrate: int = 2000000, + **kwargs: object, + ): + self.receive_own_messages = receive_own_messages + + nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX) + self.tree = ctypes.POINTER(CSI_Tree)() + ec = CSI_CreateProtocolTree( + ctypes.c_char_p(b""), nodeRange, ctypes.byref(self.tree) + ) + if ec != 0x0: + raise can.exceptions.CanInitializationError( + f"CSI_CreateProtocolTree failed with error 0x{ec:X}" + ) + + oci_can_v = BOA_Version(1, 4, 0, 0) + + # Common + + self.ctrl = OCI_ControllerHandle() + ec = OCI_CreateCANControllerNoSearch( + channel.encode(), + ctypes.byref(oci_can_v), + self.tree, + ctypes.byref(self.ctrl), + ) + if ec != 0x0: + raise can.exceptions.CanInitializationError( + f"OCI_CreateCANControllerNoSearch failed with error 0x{ec:X}" + ) + + ctrlConf = OCI_CANConfiguration() + ctrlConf.baudrate = bitrate + ctrlConf.samplePoint = 80 + ctrlConf.samplesPerBit = OCI_CAN_THREE_SAMPLES_PER_BIT + ctrlConf.BTL_Cycles = 10 + ctrlConf.SJW = 1 + ctrlConf.syncEdge = OCI_CAN_SINGLE_SYNC_EDGE + ctrlConf.physicalMedia = OCI_CAN_MEDIA_HIGH_SPEED + if receive_own_messages: + ctrlConf.selfReceptionMode = OCI_SELF_RECEPTION_ON + else: + ctrlConf.selfReceptionMode = OCI_SELF_RECEPTION_OFF + ctrlConf.busParticipationMode = OCI_BUSMODE_ACTIVE + + if fd: + ctrlConf.canFDEnabled = True + ctrlConf.canFDConfig.dataBitRate = data_bitrate + ctrlConf.canFDConfig.dataBTL_Cycles = 10 + ctrlConf.canFDConfig.dataSamplePoint = 80 + ctrlConf.canFDConfig.dataSJW = 1 + ctrlConf.canFDConfig.flags = 0 + ctrlConf.canFDConfig.canFdTxConfig = OCI_CANFDTX_USE_CAN_AND_CANFD_FRAMES + ctrlConf.canFDConfig.canFdRxConfig.canRxMode = ( + OCI_CAN_RXMODE_CAN_FRAMES_USING_CAN_MESSAGE + ) + ctrlConf.canFDConfig.canFdRxConfig.canFdRxMode = ( + OCI_CANFDRXMODE_CANFD_FRAMES_USING_CANFD_MESSAGE + ) + + ctrlProp = OCI_CANControllerProperties() + ctrlProp.mode = OCI_CONTROLLER_MODE_RUNNING + + ec = OCI_OpenCANController( + self.ctrl, ctypes.byref(ctrlConf), ctypes.byref(ctrlProp) + ) + if ec != 0x0 and ec != 0x40004000: # accept BOA_WARN_PARAM_ADAPTED + raise can.exceptions.CanInitializationError( + f"OCI_OpenCANController failed with error 0x{ec:X}" + ) + + # RX + + rxQConf = OCI_CANRxQueueConfiguration() + rxQConf.onFrame.function = ctypes.cast(None, OCI_CANRxCallbackFunctionSingleMsg) + rxQConf.onFrame.userData = None + rxQConf.onEvent.function = ctypes.cast(None, OCI_CANRxCallbackFunctionSingleMsg) + rxQConf.onEvent.userData = None + if receive_own_messages: + rxQConf.selfReceptionMode = OCI_SELF_RECEPTION_ON + else: + rxQConf.selfReceptionMode = OCI_SELF_RECEPTION_OFF + self.rxQueue = OCI_QueueHandle() + ec = OCI_CreateCANRxQueue( + self.ctrl, ctypes.byref(rxQConf), ctypes.byref(self.rxQueue) + ) + if ec != 0x0: + raise can.exceptions.CanInitializationError( + f"OCI_CreateCANRxQueue failed with error 0x{ec:X}" + ) + + self._oci_filters = None + self.filters = can_filters + + # TX + + txQConf = OCI_CANTxQueueConfiguration() + txQConf.reserved = 0 + self.txQueue = OCI_QueueHandle() + ec = OCI_CreateCANTxQueue( + self.ctrl, ctypes.byref(txQConf), ctypes.byref(self.txQueue) + ) + if ec != 0x0: + raise can.exceptions.CanInitializationError( + f"OCI_CreateCANTxQueue failed with error 0x{ec:X}" + ) + + # Common + + timerCapabilities = OCI_TimerCapabilities() + OCI_GetTimerCapabilities(self.ctrl, ctypes.byref(timerCapabilities)) + self.tickFrequency = timerCapabilities.tickFrequency # clock ticks per second + + self.channel_info = channel + + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[can.Message], bool]: + canMessages = (ctypes.POINTER(OCI_CANMessageEx) * 1)() + + m = OCI_CANMessageEx() + canMessages[0].contents = m + + count = ctypes.c_uint32() + remaining = ctypes.c_uint32() + if timeout is not None: + t = OCI_Time(int(timeout * self.tickFrequency)) + else: + t = OCI_NO_TIME + ec = OCI_ReadCANDataEx( + self.rxQueue, + t, + canMessages, + 1, + ctypes.byref(count), + ctypes.byref(remaining), + ) + if ec != 0x0: + text = ctypes.create_string_buffer(500) + OCI_GetError(self.ctrl, ec, text, 500) + raise can.exceptions.CanOperationError( + f"OCI_ReadCANDataEx failed with error 0x{ec:X}" + ) + + msg = None + + if count.value != 0: + m = canMessages[0].contents + if m.type == OCI_CANFDRX_MESSAGE.value: + msg = can.Message( + timestamp=float(m.data.canFDRxMessage.timeStamp) + / self.tickFrequency, + arbitration_id=m.data.canFDRxMessage.frameID, + is_extended_id=bool( + m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_EXTENDED + ), + is_remote_frame=bool( + m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME + ), + # is_error_frame=False, + # channel=None, + dlc=m.data.canFDRxMessage.size, + data=m.data.canFDRxMessage.data[0 : m.data.canFDRxMessage.size], + is_fd=True, + is_rx=not bool( + m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION + ), + bitrate_switch=bool( + m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE + ), + # error_state_indicator=False, + # check=False, + ) + elif m.type == OCI_CAN_RX_MESSAGE.value: + msg = can.Message( + timestamp=float(m.data.rxMessage.timeStamp) / self.tickFrequency, + arbitration_id=m.data.rxMessage.frameID, + is_extended_id=bool( + m.data.rxMessage.flags & OCI_CAN_MSG_FLAG_EXTENDED + ), + is_remote_frame=bool( + m.data.rxMessage.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME + ), + # is_error_frame=False, + # channel=None, + dlc=m.data.rxMessage.dlc, + data=m.data.rxMessage.data[0 : m.data.rxMessage.dlc], + # is_fd=False, + is_rx=not bool( + m.data.rxMessage.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION + ), + # bitrate_switch=False, + # error_state_indicator=False, + # check=False, + ) + + return (msg, True) + + def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: + canMessages = (ctypes.POINTER(OCI_CANMessageEx) * 1)() + + m = OCI_CANMessageEx() + + if msg.is_fd: + m.type = OCI_CANFDTX_MESSAGE + m.data.canFDTxMessage.frameID = msg.arbitration_id + m.data.canFDTxMessage.flags = 0 + if msg.is_extended_id: + m.data.canFDTxMessage.flags |= OCI_CAN_MSG_FLAG_EXTENDED + if msg.is_remote_frame: + m.data.canFDTxMessage.flags |= OCI_CAN_MSG_FLAG_REMOTE_FRAME + m.data.canFDTxMessage.flags |= OCI_CAN_MSG_FLAG_FD_DATA + if msg.bitrate_switch: + m.data.canFDTxMessage.flags |= OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE + m.data.canFDTxMessage.size = msg.dlc + m.data.canFDTxMessage.data = tuple(msg.data) + else: + m.type = OCI_CAN_TX_MESSAGE + m.data.txMessage.frameID = msg.arbitration_id + m.data.txMessage.flags = 0 + if msg.is_extended_id: + m.data.txMessage.flags |= OCI_CAN_MSG_FLAG_EXTENDED + if msg.is_remote_frame: + m.data.txMessage.flags |= OCI_CAN_MSG_FLAG_REMOTE_FRAME + m.data.txMessage.dlc = msg.dlc + m.data.txMessage.data = tuple(msg.data) + + canMessages[0].contents = m + + ec = OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, canMessages, 1, None) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_WriteCANDataEx failed with error 0x{ec:X}" + ) + + def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: + if self._oci_filters: + ec = OCI_RemoveCANFrameFilterEx(self.rxQueue, self._oci_filters, 1) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_RemoveCANFrameFilterEx failed with error 0x{ec:X}" + ) + + # "accept all" filter + if filters is None: + filters = [{"can_id": 0x0, "can_mask": 0x0}] + + self._oci_filters = (ctypes.POINTER(OCI_CANRxFilterEx) * len(filters))() + for i, filter in enumerate(filters): + f = OCI_CANRxFilterEx() + f.frameIDValue = filter["can_id"] + f.frameIDMask = filter["can_mask"] + f.tag = 0 + f.flagsValue = 0 + if self.receive_own_messages: + # mask out the SR bit, i.e. ignore the bit -> receive all + f.flagsMask = 0 + else: + # enable the SR bit in the mask. since the bit is 0 in flagsValue -> do not self-receive + f.flagsMask = OCI_CAN_MSG_FLAG_SELFRECEPTION + if filter.get("extended"): + f.flagsValue |= OCI_CAN_MSG_FLAG_EXTENDED + f.flagsMask |= OCI_CAN_MSG_FLAG_EXTENDED + self._oci_filters[i].contents = f + + ec = OCI_AddCANFrameFilterEx( + self.rxQueue, self._oci_filters, len(self._oci_filters) + ) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_AddCANFrameFilterEx failed with error 0x{ec:X}" + ) + + def flush_tx_buffer(self) -> None: + ec = OCI_ResetQueue(self.txQueue) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_ResetQueue failed with error 0x{ec:X}" + ) + + def shutdown(self) -> None: + # Cleanup TX + if self.txQueue: + ec = OCI_DestroyCANTxQueue(self.txQueue) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_DestroyCANTxQueue failed with error 0x{ec:X}" + ) + self.txQueue = None + + # Cleanup RX + if self.rxQueue: + ec = OCI_DestroyCANRxQueue(self.rxQueue) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_DestroyCANRxQueue failed with error 0x{ec:X}" + ) + self.rxQueue = None + + # Cleanup common + if self.ctrl: + ec = OCI_CloseCANController(self.ctrl) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_CloseCANController failed with error 0x{ec:X}" + ) + ec = OCI_DestroyCANController(self.ctrl) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_DestroyCANController failed with error 0x{ec:X}" + ) + self.ctrl = None + + if self.tree: + ec = CSI_DestroyProtocolTree(self.tree) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"CSI_DestroyProtocolTree failed with error 0x{ec:X}" + ) + self.tree = None + + @property + def state(self) -> can.BusState: + status = OCI_CANControllerStatus() + ec = OCI_GetCANControllerStatus(self.ctrl, ctypes.byref(status)) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"OCI_GetCANControllerStatus failed with error 0x{ec:X}" + ) + if status.stateCode & OCI_CAN_STATE_ACTIVE: + return can.BusState.ACTIVE + elif status.stateCode & OCI_CAN_STATE_PASSIVE: + return can.BusState.PASSIVE + + @state.setter + def state(self, new_state: can.BusState) -> None: + # if new_state == can.BusState.ACTIVE: + # self.ctrlConf.busParticipationMode = OCI_BUSMODE_ACTIVE + # else: + # self.ctrlConf.busParticipationMode = OCI_BUSMODE_PASSIVE + # ec = OCI_AdaptCANConfiguration(self.ctrl, ctypes.byref(self.ctrlConf)) + # if ec != 0x0: + # raise can.exceptions.CanOperationError(f"OCI_AdaptCANConfiguration failed with error 0x{ec:X}") + raise NotImplementedError("Setting state is not implemented.") + + def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: + nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX) + tree = ctypes.POINTER(CSI_Tree)() + ec = CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(tree)) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"CSI_CreateProtocolTree failed with error 0x{ec:X}" + ) + + nodes = [] + + def _findNodes(tree, prefix): + uri = f"{prefix}/{tree.contents.item.uriName.decode()}" + if "CAN:" in uri: + nodes.append({"interface": "etas", "channel": uri}) + elif tree.contents.child: + _findNodes( + tree.contents.child, + f"{prefix}/{tree.contents.item.uriName.decode()}", + ) + + if tree.contents.sibling: + _findNodes(tree.contents.sibling, prefix) + + _findNodes(tree, "ETAS:/") + + ec = CSI_DestroyProtocolTree(tree) + if ec != 0x0: + raise can.exceptions.CanOperationError( + f"CSI_DestroyProtocolTree failed with error 0x{ec:X}" + ) + + return nodes diff --git a/can/interfaces/etas/boa.py b/can/interfaces/etas/boa.py new file mode 100644 index 000000000..5e55b29c1 --- /dev/null +++ b/can/interfaces/etas/boa.py @@ -0,0 +1,665 @@ +import ctypes + +try: + # try to load libraries from the system default paths + _csi = ctypes.windll.LoadLibrary("dll-csiBind") + _oci = ctypes.windll.LoadLibrary("dll-ocdProxy") +except FileNotFoundError: + # try to load libraries with hardcoded paths + if ctypes.sizeof(ctypes.c_voidp) == 4: + # 32 bit + path = "C:/Program Files (x86)/ETAS/BOA_V2/Bin/Win32/Dll/Framework/" + elif ctypes.sizeof(ctypes.c_voidp) == 8: + # 64 bit + path = "C:/Program Files/ETAS/BOA_V2/Bin/x64/Dll/Framework/" + _csi = ctypes.windll.LoadLibrary(path + "dll-csiBind") + _oci = ctypes.windll.LoadLibrary(path + "dll-ocdProxy") + +# Common (BOA) + +BOA_ResultCode = ctypes.c_uint32 +BOA_Handle = ctypes.c_int32 +BOA_Time = ctypes.c_int64 + +BOA_NO_VALUE = -1 +BOA_NO_HANDLE = BOA_Handle(BOA_NO_VALUE) +BOA_NO_TIME = BOA_Time(BOA_NO_VALUE) + + +class BOA_UuidBin(ctypes.Structure): + _fields_ = [("data", ctypes.c_uint8 * 16)] + + +class BOA_Version(ctypes.Structure): + _fields_ = [ + ("majorVersion", ctypes.c_uint8), + ("minorVersion", ctypes.c_uint8), + ("bugfix", ctypes.c_uint8), + ("build", ctypes.c_uint8), + ] + + +class BOA_UuidVersion(ctypes.Structure): + _fields_ = [("uuid", BOA_UuidBin), ("version", BOA_Version)] + + +class BOA_ServiceId(ctypes.Structure): + _fields_ = [("api", BOA_UuidVersion), ("access", BOA_UuidVersion)] + + +class BOA_ServiceIdParam(ctypes.Structure): + _fields_ = [ + ("id", BOA_ServiceId), + ("count", ctypes.c_uint32), + ("accessParam", ctypes.c_char * 128), + ] + + +# Connection Service Interface (CSI) + +# CSI - Search For Service (SFS) + +CSI_NodeType = ctypes.c_uint32 +CSI_NODE_MIN = CSI_NodeType(0) +CSI_NODE_MAX = CSI_NodeType(0x7FFF) + + +class CSI_NodeRange(ctypes.Structure): + _fields_ = [("min", CSI_NodeType), ("max", CSI_NodeType)] + + +class CSI_SubItem(ctypes.Structure): + _fields_ = [ + ("server", BOA_ServiceIdParam), + ("nodeType", CSI_NodeType), + ("uriName", ctypes.c_char * 128), + ("visibleName", ctypes.c_char * 4), + ("version", BOA_Version), + ("reserved2", ctypes.c_char * 88), + ("serverAffinity", BOA_UuidBin), + ("requiredAffinity0", BOA_UuidBin), + ("reserved", ctypes.c_int32 * 4), + ("requiredAffinity1", BOA_UuidBin), + ("count", ctypes.c_int32), + ("requiredAPI", BOA_ServiceId * 4), + ] + + +class CSI_Tree(ctypes.Structure): + pass + + +CSI_Tree._fields_ = [ + ("item", CSI_SubItem), + ("sibling", ctypes.POINTER(CSI_Tree)), + ("child", ctypes.POINTER(CSI_Tree)), + ("childrenProbed", ctypes.c_int), +] + +CSI_CreateProtocolTree = _csi.CSI_CreateProtocolTree +CSI_CreateProtocolTree.argtypes = [ + ctypes.c_char_p, + CSI_NodeRange, + ctypes.POINTER(ctypes.POINTER(CSI_Tree)), +] +CSI_CreateProtocolTree.restype = BOA_ResultCode + +CSI_DestroyProtocolTree = _csi.CSI_DestroyProtocolTree +CSI_DestroyProtocolTree.argtypes = [ + ctypes.POINTER(CSI_Tree), +] +CSI_DestroyProtocolTree.restype = BOA_ResultCode + +# Open Controller Interface (OCI) + +# OCI Common - Global Types + +OCI_NO_VALUE = BOA_NO_VALUE +OCI_NO_HANDLE = BOA_NO_HANDLE +OCI_Handle = BOA_Handle +OCI_Time = BOA_Time + +# OCI Common - Controller Handling + +OCI_ControllerHandle = OCI_Handle + +OCI_ControllerPropertiesMode = ctypes.c_uint32 +OCI_CONTROLLER_MODE_RUNNING = OCI_ControllerPropertiesMode(0) +OCI_CONTROLLER_MODE_SUSPENDED = OCI_ControllerPropertiesMode(1) + +OCI_SelfReceptionMode = ctypes.c_uint32 +OCI_SELF_RECEPTION_OFF = OCI_SelfReceptionMode(0) +OCI_SELF_RECEPTION_ON = OCI_SelfReceptionMode(1) + +# OCI Common - Event Handling + +# OCI Common - Error Management + +OCI_ErrorCode = BOA_ResultCode + +OCI_InternalErrorEvent = ctypes.c_uint32 +OCI_INTERNAL_GENERAL_ERROR = OCI_InternalErrorEvent(0) + + +class OCI_InternalErrorEventMessage(ctypes.Structure): + _fields_ = [ + ("timeStamp", OCI_Time), + ("tag", ctypes.c_uint32), + ("eventCode", OCI_InternalErrorEvent), + ("errorCode", OCI_ErrorCode), + ] + + +OCI_GetError = _oci.OCI_GetError +OCI_GetError.argtypes = [ + OCI_Handle, + OCI_ErrorCode, + ctypes.c_char_p, + ctypes.c_uint32, +] +OCI_GetError.restype = OCI_ErrorCode + +# OCI Common - Queue Handling + +OCI_QueueHandle = OCI_Handle + +OCI_QueueEvent = ctypes.c_uint32 +OCI_QUEUE_UNDERRUN = OCI_QueueEvent(0) +OCI_QUEUE_EMPTY = OCI_QueueEvent(1) +OCI_QUEUE_NOT_EMPTY = OCI_QueueEvent(2) +OCI_QUEUE_LOW_WATERMARK = OCI_QueueEvent(3) +OCI_QUEUE_HIGH_WATERMARK = OCI_QueueEvent(4) +OCI_QUEUE_FULL = OCI_QueueEvent(5) +OCI_QUEUE_OVERFLOW = OCI_QueueEvent(6) + + +class OCI_QueueEventMessage(ctypes.Structure): + _fields_ = [ + ("timeStamp", OCI_Time), + ("tag", ctypes.c_uint32), + ("eventCode", OCI_QueueEvent), + ("destination", ctypes.c_uint32), + ] + + +OCI_ResetQueue = _oci.OCI_ResetQueue +OCI_ResetQueue.argtypes = [OCI_QueueHandle] +OCI_ResetQueue.restype = OCI_ErrorCode + +# OCI Common - Timer Handling + +OCI_NO_TIME = BOA_NO_TIME + +OCI_TimeReferenceScale = ctypes.c_uint32 +OCI_TimeReferenceScaleUnknown = OCI_TimeReferenceScale(0) +OCI_TimeReferenceScaleTAI = OCI_TimeReferenceScale(1) +OCI_TimeReferenceScaleUTC = OCI_TimeReferenceScale(2) + +OCI_TimerEvent = ctypes.c_uint32 +OCI_TIMER_EVENT_SYNC_LOCK = OCI_TimerEvent(0) +OCI_TIMER_EVENT_SYNC_LOSS = OCI_TimerEvent(1) +OCI_TIMER_EVENT_LEAP_SECOND = OCI_TimerEvent(2) + + +class OCI_TimerCapabilities(ctypes.Structure): + _fields_ = [ + ("localClockID", ctypes.c_char * 40), + ("format", ctypes.c_uint32), + ("tickFrequency", ctypes.c_uint32), + ("ticksPerIncrement", ctypes.c_uint32), + ("localStratumLevel", ctypes.c_uint32), + ("localReferenceScale", OCI_TimeReferenceScale), + ("localTimeOriginIso8601", ctypes.c_char * 40), + ("syncSlave", ctypes.c_uint32), + ("syncMaster", ctypes.c_uint32), + ] + + +class OCI_TimerEventMessage(ctypes.Structure): + _fields_ = [ + ("timeStamp", OCI_Time), + ("tag", ctypes.c_uint32), + ("eventCode", OCI_TimerEvent), + ("destination", ctypes.c_uint32), + ] + + +OCI_GetTimerCapabilities = _oci.OCI_GetTimerCapabilities +OCI_GetTimerCapabilities.argtypes = [ + OCI_ControllerHandle, + ctypes.POINTER(OCI_TimerCapabilities), +] +OCI_GetTimerCapabilities.restype = OCI_ErrorCode + +# OCI CAN + +# OCI CAN - CAN-FD + +OCI_CANFDRxMode = ctypes.c_uint32 +OCI_CANFDRXMODE_CANFD_FRAMES_IGNORED = OCI_CANFDRxMode(1) +OCI_CANFDRXMODE_CANFD_FRAMES_USING_CAN_MESSAGE = OCI_CANFDRxMode(2) +OCI_CANFDRXMODE_CANFD_FRAMES_USING_CANFD_MESSAGE = OCI_CANFDRxMode(4) +OCI_CANFDRXMODE_CANFD_FRAMES_USING_CANFD_MESSAGE_PADDING = OCI_CANFDRxMode(8) + +OCI_CANRxMode = ctypes.c_uint32 +OCI_CAN_RXMODE_CAN_FRAMES_IGNORED = OCI_CANRxMode(1) +OCI_CAN_RXMODE_CAN_FRAMES_USING_CAN_MESSAGE = OCI_CANRxMode(2) + +OCI_CANFDTxConfig = ctypes.c_uint32 +OCI_CANFDTX_USE_CAN_FRAMES_ONLY = OCI_CANFDTxConfig(1) +OCI_CANFDTX_USE_CANFD_FRAMES_ONLY = OCI_CANFDTxConfig(2) +OCI_CANFDTX_USE_CAN_AND_CANFD_FRAMES = OCI_CANFDTxConfig(4) + + +class OCI_CANFDRxConfig(ctypes.Structure): + _fields_ = [ + ("canRxMode", OCI_CANRxMode), + ("canFdRxMode", OCI_CANFDRxMode), + ] + + +class OCI_CANFDConfiguration(ctypes.Structure): + _fields_ = [ + ("dataBitRate", ctypes.c_uint32), + ("dataSamplePoint", ctypes.c_uint32), + ("dataBTL_Cycles", ctypes.c_uint32), + ("dataSJW", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("txSecondarySamplePointOffset", ctypes.c_uint32), + ("canFdRxConfig", OCI_CANFDRxConfig), + ("canFdTxConfig", OCI_CANFDTxConfig), + ("txSecondarySamplePointFilterWindow", ctypes.c_uint16), + ("reserved", ctypes.c_uint16), + ] + + +class OCI_CANFDRxMessage(ctypes.Structure): + _fields_ = [ + ("timeStamp", OCI_Time), + ("tag", ctypes.c_uint32), + ("frameID", ctypes.c_uint32), + ("flags", ctypes.c_uint16), + ("res", ctypes.c_uint8), + ("size", ctypes.c_uint8), + ("res1", ctypes.c_uint8 * 4), + ("data", ctypes.c_uint8 * 64), + ] + + +class OCI_CANFDTxMessage(ctypes.Structure): + _fields_ = [ + ("frameID", ctypes.c_uint32), + ("flags", ctypes.c_uint16), + ("res", ctypes.c_uint8), + ("size", ctypes.c_uint8), + ("data", ctypes.c_uint8 * 64), + ] + + +# OCI CAN - Initialization + +OCI_CAN_THREE_SAMPLES_PER_BIT = 2 +OCI_CAN_SINGLE_SYNC_EDGE = 1 +OCI_CAN_MEDIA_HIGH_SPEED = 1 + +OCI_CAN_STATE_ACTIVE = 0x00000001 +OCI_CAN_STATE_PASSIVE = 0x00000002 +OCI_CAN_STATE_ERRLIMIT = 0x00000004 +OCI_CAN_STATE_BUSOFF = 0x00000008 + +OCI_CANBusParticipationMode = ctypes.c_uint32 +OCI_BUSMODE_PASSIVE = OCI_CANBusParticipationMode(1) +OCI_BUSMODE_ACTIVE = OCI_CANBusParticipationMode(2) + +OCI_CANBusTransmissionPolicies = ctypes.c_uint32 +OCI_CANTX_UNDEFINED = OCI_CANBusTransmissionPolicies(0) +OCI_CANTX_DONTCARE = OCI_CANBusTransmissionPolicies(0) +OCI_CANTX_FIFO = OCI_CANBusTransmissionPolicies(1) +OCI_CANTX_BESTEFFORT = OCI_CANBusTransmissionPolicies(2) + + +class OCI_CANConfiguration(ctypes.Structure): + _fields_ = [ + ("baudrate", ctypes.c_uint32), + ("samplePoint", ctypes.c_uint32), + ("samplesPerBit", ctypes.c_uint32), + ("BTL_Cycles", ctypes.c_uint32), + ("SJW", ctypes.c_uint32), + ("syncEdge", ctypes.c_uint32), + ("physicalMedia", ctypes.c_uint32), + ("selfReceptionMode", OCI_SelfReceptionMode), + ("busParticipationMode", OCI_CANBusParticipationMode), + ("canFDEnabled", ctypes.c_uint32), + ("canFDConfig", OCI_CANFDConfiguration), + ("canTxPolicy", OCI_CANBusTransmissionPolicies), + ] + + +class OCI_CANControllerProperties(ctypes.Structure): + _fields_ = [ + ("mode", OCI_ControllerPropertiesMode), + ] + + +class OCI_CANControllerCapabilities(ctypes.Structure): + _fields_ = [ + ("samplesPerBit", ctypes.c_uint32), + ("syncEdge", ctypes.c_uint32), + ("physicalMedia", ctypes.c_uint32), + ("reserved", ctypes.c_uint32), + ("busEvents", ctypes.c_uint32), + ("errorFrames", ctypes.c_uint32), + ("messageFlags", ctypes.c_uint32), + ("canFDSupport", ctypes.c_uint32), + ("canFDMaxDataSize", ctypes.c_uint32), + ("canFDMaxQualifiedDataRate", ctypes.c_uint32), + ("canFDMaxDataRate", ctypes.c_uint32), + ("canFDRxConfig_CANMode", ctypes.c_uint32), + ("canFDRxConfig_CANFDMode", ctypes.c_uint32), + ("canFDTxConfig_Mode", ctypes.c_uint32), + ("canBusParticipationMode", ctypes.c_uint32), + ("canTxPolicies", ctypes.c_uint32), + ] + + +class OCI_CANControllerStatus(ctypes.Structure): + _fields_ = [ + ("reserved", ctypes.c_uint32), + ("stateCode", ctypes.c_uint32), + ] + + +OCI_CreateCANControllerNoSearch = _oci.OCI_CreateCANControllerNoSearch +OCI_CreateCANControllerNoSearch.argtypes = [ + ctypes.c_char_p, + ctypes.POINTER(BOA_Version), + ctypes.POINTER(CSI_Tree), + ctypes.POINTER(OCI_ControllerHandle), +] +OCI_CreateCANControllerNoSearch.restype = OCI_ErrorCode + +OCI_OpenCANController = _oci.OCI_OpenCANController +OCI_OpenCANController.argtypes = [ + OCI_ControllerHandle, + ctypes.POINTER(OCI_CANConfiguration), + ctypes.POINTER(OCI_CANControllerProperties), +] +OCI_OpenCANController.restype = OCI_ErrorCode + +OCI_CloseCANController = _oci.OCI_CloseCANController +OCI_CloseCANController.argtypes = [OCI_ControllerHandle] +OCI_CloseCANController.restype = OCI_ErrorCode + +OCI_DestroyCANController = _oci.OCI_DestroyCANController +OCI_DestroyCANController.argtypes = [OCI_ControllerHandle] +OCI_DestroyCANController.restype = OCI_ErrorCode + +OCI_AdaptCANConfiguration = _oci.OCI_AdaptCANConfiguration +OCI_AdaptCANConfiguration.argtypes = [ + OCI_ControllerHandle, + ctypes.POINTER(OCI_CANConfiguration), +] +OCI_AdaptCANConfiguration.restype = OCI_ErrorCode + +OCI_GetCANControllerCapabilities = _oci.OCI_GetCANControllerCapabilities +OCI_GetCANControllerCapabilities.argtypes = [ + OCI_ControllerHandle, + ctypes.POINTER(OCI_CANControllerCapabilities), +] +OCI_GetCANControllerCapabilities.restype = OCI_ErrorCode + +OCI_GetCANControllerStatus = _oci.OCI_GetCANControllerStatus +OCI_GetCANControllerStatus.argtypes = [ + OCI_ControllerHandle, + ctypes.POINTER(OCI_CANControllerStatus), +] +OCI_GetCANControllerStatus.restype = OCI_ErrorCode + + +# OCI CAN - Filter + + +class OCI_CANRxFilter(ctypes.Structure): + _fields_ = [ + ("frameIDValue", ctypes.c_uint32), + ("frameIDMask", ctypes.c_uint32), + ("tag", ctypes.c_uint32), + ] + + +class OCI_CANRxFilterEx(ctypes.Structure): + _fields_ = [ + ("frameIDValue", ctypes.c_uint32), + ("frameIDMask", ctypes.c_uint32), + ("tag", ctypes.c_uint32), + ("flagsValue", ctypes.c_uint16), + ("flagsMask", ctypes.c_uint16), + ] + + +OCI_AddCANFrameFilter = _oci.OCI_AddCANFrameFilter +OCI_AddCANFrameFilter.argtypes = [ + OCI_QueueHandle, + ctypes.POINTER(OCI_CANRxFilter), + ctypes.c_uint32, +] +OCI_AddCANFrameFilter.restype = OCI_ErrorCode + +OCI_AddCANFrameFilterEx = _oci.OCI_AddCANFrameFilterEx +OCI_AddCANFrameFilterEx.argtypes = [ + OCI_QueueHandle, + ctypes.POINTER(ctypes.POINTER(OCI_CANRxFilterEx)), + ctypes.c_uint32, +] +OCI_AddCANFrameFilterEx.restype = OCI_ErrorCode + +OCI_RemoveCANFrameFilterEx = _oci.OCI_RemoveCANFrameFilterEx +OCI_RemoveCANFrameFilterEx.argtypes = [ + OCI_QueueHandle, + ctypes.POINTER(ctypes.POINTER(OCI_CANRxFilterEx)), + ctypes.c_uint32, +] +OCI_RemoveCANFrameFilterEx.restype = OCI_ErrorCode + +# OCI CAN - Messages + +OCI_CAN_MSG_FLAG_EXTENDED = 0x1 +OCI_CAN_MSG_FLAG_REMOTE_FRAME = 0x2 +OCI_CAN_MSG_FLAG_SELFRECEPTION = 0x4 +OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE = 0x8 +OCI_CAN_MSG_FLAG_FD_TRUNC_AND_PAD = 0x10 +OCI_CAN_MSG_FLAG_FD_ERROR_PASSIVE = 0x20 +OCI_CAN_MSG_FLAG_FD_DATA = 0x40 + +OCI_CANMessageDataType = ctypes.c_uint32 +OCI_CAN_RX_MESSAGE = OCI_CANMessageDataType(1) +OCI_CAN_TX_MESSAGE = OCI_CANMessageDataType(2) +OCI_CAN_ERROR_FRAME = OCI_CANMessageDataType(3) +OCI_CAN_BUS_EVENT = OCI_CANMessageDataType(4) +OCI_CAN_INTERNAL_ERROR_EVENT = OCI_CANMessageDataType(5) +OCI_CAN_QUEUE_EVENT = OCI_CANMessageDataType(6) +OCI_CAN_TIMER_EVENT = OCI_CANMessageDataType(7) +OCI_CANFDRX_MESSAGE = OCI_CANMessageDataType(8) +OCI_CANFDTX_MESSAGE = OCI_CANMessageDataType(9) + + +class OCI_CANTxMessage(ctypes.Structure): + _fields_ = [ + ("frameID", ctypes.c_uint32), + ("flags", ctypes.c_uint16), + ("res", ctypes.c_uint8), + ("dlc", ctypes.c_uint8), + ("data", ctypes.c_uint8 * 8), + ] + + +class OCI_CANRxMessage(ctypes.Structure): + _fields_ = [ + ("timeStamp", OCI_Time), + ("tag", ctypes.c_uint32), + ("frameID", ctypes.c_uint32), + ("flags", ctypes.c_uint16), + ("res", ctypes.c_uint8), + ("dlc", ctypes.c_uint8), + ("res1", ctypes.c_uint8 * 4), + ("data", ctypes.c_uint8 * 8), + ] + + +class OCI_CANErrorFrameMessage(ctypes.Structure): + _fields_ = [ + ("timeStamp", OCI_Time), + ("tag", ctypes.c_uint32), + ("frameID", ctypes.c_uint32), + ("flags", ctypes.c_uint16), + ("res", ctypes.c_uint8), + ("dlc", ctypes.c_uint8), + ("type", ctypes.c_uint32), + ("destination", ctypes.c_uint32), + ] + + +class OCI_CANEventMessage(ctypes.Structure): + _fields_ = [ + ("timeStamp", OCI_Time), + ("tag", ctypes.c_uint32), + ("eventCode", ctypes.c_uint32), + ("destination", ctypes.c_uint32), + ] + + +class OCI_CANMessageData(ctypes.Union): + _fields_ = [ + ("rxMessage", OCI_CANRxMessage), + ("txMessage", OCI_CANTxMessage), + ("errorFrameMessage", OCI_CANErrorFrameMessage), + ("canEventMessage", OCI_CANEventMessage), + ("internalErrorEventMessage", OCI_InternalErrorEventMessage), + ("timerEventMessage", OCI_TimerEventMessage), + ("queueEventMessage", OCI_QueueEventMessage), + ] + + +class OCI_CANMessageDataEx(ctypes.Union): + _fields_ = [ + ("rxMessage", OCI_CANRxMessage), + ("txMessage", OCI_CANTxMessage), + ("errorFrameMessage", OCI_CANErrorFrameMessage), + ("canEventMessage", OCI_CANEventMessage), + ("internalErrorEventMessage", OCI_InternalErrorEventMessage), + ("timerEventMessage", OCI_TimerEventMessage), + ("queueEventMessage", OCI_QueueEventMessage), + ("canFDRxMessage", OCI_CANFDRxMessage), + ("canFDTxMessage", OCI_CANFDTxMessage), + ] + + +class OCI_CANMessage(ctypes.Structure): + _fields_ = [ + ("type", OCI_CANMessageDataType), + ("reserved", ctypes.c_uint32), + ("data", OCI_CANMessageData), + ] + + +class OCI_CANMessageEx(ctypes.Structure): + _fields_ = [ + ("type", OCI_CANMessageDataType), + ("reserved", ctypes.c_uint32), + ("data", OCI_CANMessageDataEx), + ] + + +# OCI CAN - Queues + +OCI_CANRxCallbackFunctionSingleMsg = ctypes.CFUNCTYPE( + None, ctypes.c_void_p, ctypes.POINTER(OCI_CANMessage) +) + +OCI_CANRxCallbackFunctionSingleMsgEx = ctypes.CFUNCTYPE( + None, ctypes.c_void_p, ctypes.POINTER(OCI_CANMessageEx) +) + + +class OCI_CANRxCallbackSingleMsg(ctypes.Structure): + class _U(ctypes.Union): + _fields_ = [ + ("function", OCI_CANRxCallbackFunctionSingleMsg), + ("functionEx", OCI_CANRxCallbackFunctionSingleMsgEx), + ] + + _anonymous_ = ("u",) + _fields_ = [ + ("u", _U), + ("userData", ctypes.c_void_p), + ] + + +class OCI_CANRxQueueConfiguration(ctypes.Structure): + _fields_ = [ + ("onFrame", OCI_CANRxCallbackSingleMsg), + ("onEvent", OCI_CANRxCallbackSingleMsg), + ("selfReceptionMode", OCI_SelfReceptionMode), + ] + + +class OCI_CANTxQueueConfiguration(ctypes.Structure): + _fields_ = [ + ("reserved", ctypes.c_uint32), + ] + + +OCI_CreateCANRxQueue = _oci.OCI_CreateCANRxQueue +OCI_CreateCANRxQueue.argtypes = [ + OCI_ControllerHandle, + ctypes.POINTER(OCI_CANRxQueueConfiguration), + ctypes.POINTER(OCI_QueueHandle), +] +OCI_CreateCANRxQueue.restype = OCI_ErrorCode + +OCI_DestroyCANRxQueue = _oci.OCI_DestroyCANRxQueue +OCI_DestroyCANRxQueue.argtypes = [OCI_QueueHandle] +OCI_DestroyCANRxQueue.restype = OCI_ErrorCode + +OCI_CreateCANTxQueue = _oci.OCI_CreateCANTxQueue +OCI_CreateCANTxQueue.argtypes = [ + OCI_ControllerHandle, + ctypes.POINTER(OCI_CANTxQueueConfiguration), + ctypes.POINTER(OCI_QueueHandle), +] +OCI_CreateCANTxQueue.restype = OCI_ErrorCode + +OCI_DestroyCANTxQueue = _oci.OCI_DestroyCANTxQueue +OCI_DestroyCANTxQueue.argtypes = [OCI_QueueHandle] +OCI_DestroyCANTxQueue.restype = OCI_ErrorCode + +OCI_WriteCANData = _oci.OCI_WriteCANData +OCI_WriteCANData.argtypes = [ + OCI_QueueHandle, + OCI_Time, + ctypes.POINTER(OCI_CANMessage), + ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint32), +] +OCI_WriteCANData.restype = OCI_ErrorCode + +OCI_WriteCANDataEx = _oci.OCI_WriteCANDataEx +OCI_WriteCANDataEx.argtypes = [ + OCI_QueueHandle, + OCI_Time, + ctypes.POINTER(ctypes.POINTER(OCI_CANMessageEx)), + ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint32), +] +OCI_WriteCANDataEx.restype = OCI_ErrorCode + +OCI_ReadCANDataEx = _oci.OCI_ReadCANDataEx +OCI_ReadCANDataEx.argtypes = [ + OCI_QueueHandle, + OCI_Time, + ctypes.POINTER(ctypes.POINTER(OCI_CANMessageEx)), + ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_uint32), +] +OCI_ReadCANDataEx.restype = OCI_ErrorCode diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 361ea4097..757cf67b4 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -12,11 +12,11 @@ The available interfaces are: :maxdepth: 1 interfaces/canalystii + interfaces/etas interfaces/gs_usb interfaces/iscan interfaces/ixxat interfaces/kvaser - interfaces/udp_multicast interfaces/neovi interfaces/nican interfaces/nixnet @@ -27,6 +27,7 @@ The available interfaces are: interfaces/slcan interfaces/socketcan interfaces/systec + interfaces/udp_multicast interfaces/usb2can interfaces/vector interfaces/virtual @@ -47,4 +48,3 @@ The format of the entry point is ``interface_name=module:classname`` where The *Interface Names* are listed in :doc:`configuration`. - diff --git a/doc/interfaces/etas.rst b/doc/interfaces/etas.rst new file mode 100644 index 000000000..daee27356 --- /dev/null +++ b/doc/interfaces/etas.rst @@ -0,0 +1,33 @@ +ETAS +==== + +This interface adds support for CAN interfaces by `ETAS`_. +The ETAS BOA_ (Basic Open API) is used. +Install the "ETAS ECU and Bus Interfaces – Distribution Package". +Only Windows is supported. + +Bus +--- + +.. autoclass:: can.interfaces.etas.EtasBus + :members: + +Configuration file +------------------ + +The simplest configuration file would be:: + + [default] + interface = etas + channel = ETAS://ETH/ES910:abcd/CAN:1 + +Channels are the URIs used by the underlying API. + +To find available URIs, use :meth:`~can.interface.detect_available_configs`:: + + configs = can.interface.detect_available_configs(interfaces="etas") + for c in configs: + print(c) + +.. _ETAS: https://www.etas.com/ +.. _BOA: https://www.etas.com/de/downloadcenter/18102.php diff --git a/test/back2back_test.py b/test/back2back_test.py index 7d3274481..64fea8839 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -318,6 +318,26 @@ def test_unique_message_instances(self): super().test_unique_message_instances() +TEST_INTERFACE_ETAS = False +try: + bus_class = can.interface._get_class_for_interface("etas") + TEST_INTERFACE_ETAS = True +except can.exceptions.CanInterfaceNotImplementedError: + pass + + +@unittest.skipUnless(TEST_INTERFACE_ETAS, "skip testing of etas interface") +class BasicTestEtas(Back2BackTestCase): + + if TEST_INTERFACE_ETAS: + configs = can.interface.detect_available_configs(interfaces="etas") + + INTERFACE_1 = "etas" + CHANNEL_1 = configs[0]["channel"] + INTERFACE_2 = "etas" + CHANNEL_2 = configs[2]["channel"] + + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class SocketCanBroadcastChannel(unittest.TestCase): def setUp(self): From 7216d264758820320a9b01fb25e605e27566c3b3 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 26 Oct 2021 12:30:39 +0200 Subject: [PATCH 0734/1235] Fix the way CI status conditions are specified for Mergify Apparently [our setup does not work](https://github.com/hardbyte/python-can/runs/4006357753). Instead, we should do it [like in this repo](https://github.com/Mergifyio/mergify-engine/blob/main/.mergify.yml#L14-L19). --- .mergify.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index e2ded27f0..c9b184d08 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,7 +1,8 @@ queue_rules: - name: default conditions: - - "status-success=test,format" # "GitHub Actions works slightly differently [...], only the job name is used." + - "status-success=test" # "GitHub Actions works slightly differently [...], only the job name is used." + - "status-success=format" pull_request_rules: - name: Automatic merge passing PR on up to date branch with approving CR @@ -10,7 +11,8 @@ pull_request_rules: - "#approved-reviews-by>=1" - "#review-requested=0" - "#changes-requested-reviews-by=0" - - "status-success=test,format" + - "status-success=test" + - "status-success=format" - "label!=work-in-progress" actions: queue: From 5441c5a1809145396e5946769da8d6888455d2b7 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 26 Oct 2021 12:54:28 +0200 Subject: [PATCH 0735/1235] Update development.rst --- doc/development.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/development.rst b/doc/development.rst index 87ca353c1..03bf9a374 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -72,8 +72,12 @@ These steps are a guideline on how to add a new backend to python-can. - Register your backend bus class in ``BACKENDS`` in the file ``can.interfaces.__init__.py``. - Add docs where appropriate. At a minimum add to ``doc/interfaces.rst`` and add a new interface specific document in ``doc/interface/*``. - Also, don't forget to document your classes, methods and function with docstrings. + It should document the supported platforms and also the hardware/software it requires. + A small snippet of how to install the dependencies would also be useful to get people started without much friction. +- Also, don't forget to document your classes, methods and function with docstrings. - Add tests in ``test/*`` where appropriate. + To get started, have a look at ``back2back_test.py``: + Simply add a test case like ``BasicTestSocketCan`` and some basic tests will be executed for the new interface. Code Structure @@ -104,7 +108,7 @@ Creating a new Release - Update the library version in ``__init__.py`` using `semantic versioning `__. - Check if any deprecations are pending. - Run all tests and examples against available hardware. -- Update `CONTRIBUTORS.txt` with any new contributors. +- Update ``CONTRIBUTORS.txt`` with any new contributors. - For larger changes update ``doc/history.rst``. - Sanity check that documentation has stayed inline with code. - Create a temporary virtual environment. Run ``python setup.py install`` and ``tox``. From b2a5024e5ccf13762e8c601b66ee1a6a0753ad85 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 10 Nov 2021 21:51:54 +0100 Subject: [PATCH 0736/1235] Improve IO class hierarchy + Allow to use SizedRotatingLogger as a context manager (#1147) * Fix doc * Add test case for using SizedRotatingLogger as a context manager * Bump testing tool version * Improve type hierarchy and interaction * Update tests * Improve class hierarchy, documentation, typing and test for rotating loggers * use tmp_path fixture --- can/io/asc.py | 13 +- can/io/blf.py | 6 +- can/io/canutils.py | 10 +- can/io/csv.py | 97 ++++++------ can/io/generic.py | 13 +- can/io/logger.py | 118 ++++++++------ can/io/printer.py | 20 ++- can/typechecking.py | 2 +- test/test_rotating_loggers.py | 282 +++++++++++++++++----------------- tox.ini | 12 +- 10 files changed, 302 insertions(+), 271 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index f5cea85cb..347ecf3c6 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -7,7 +7,6 @@ """ from typing import cast, Any, Generator, IO, List, Optional, Union, Dict -from can import typechecking from datetime import datetime import time @@ -16,7 +15,8 @@ from ..message import Message from ..listener import Listener from ..util import channel2int -from .generic import BaseIOHandler +from .generic import BaseIOHandler, FileIOMessageWriter +from ..typechecking import AcceptedIOType CAN_MSG_EXT = 0x80000000 @@ -37,7 +37,7 @@ class ASCReader(BaseIOHandler): def __init__( self, - file: Union[typechecking.FileLike, typechecking.StringPathLike], + file: AcceptedIOType, base: str = "hex", relative_timestamp: bool = True, ) -> None: @@ -238,7 +238,7 @@ def __iter__(self) -> Generator[Message, None, None]: self.stop() -class ASCWriter(BaseIOHandler, Listener): +class ASCWriter(FileIOMessageWriter, Listener): """Logs CAN data to an ASCII log file (.asc). The measurement starts with the timestamp of the first registered message. @@ -275,7 +275,7 @@ class ASCWriter(BaseIOHandler, Listener): def __init__( self, - file: Union[typechecking.FileLike, typechecking.StringPathLike], + file: AcceptedIOType, channel: int = 1, ) -> None: """ @@ -286,8 +286,6 @@ def __init__( have a channel set """ super().__init__(file, mode="w") - if not self.file: - raise ValueError("The given file cannot be None") self.channel = channel @@ -304,7 +302,6 @@ def __init__( def stop(self) -> None: # This is guaranteed to not be None since we raise ValueError in __init__ - self.file = cast(IO[Any], self.file) if not self.file.closed: self.file.write("End TriggerBlock\n") super().stop() diff --git a/can/io/blf.py b/can/io/blf.py index 90296e81d..d53e1296f 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -23,7 +23,7 @@ from ..listener import Listener from ..util import len2dlc, dlc2len, channel2int from ..typechecking import AcceptedIOType -from .generic import BaseIOHandler +from .generic import BaseIOHandler, FileIOMessageWriter class BLFParseError(Exception): @@ -139,7 +139,7 @@ class BLFReader(BaseIOHandler): silently ignored. """ - def __init__(self, file): + def __init__(self, file: AcceptedIOType) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in binary @@ -347,7 +347,7 @@ def _parse_data(self, data): pos = next_pos -class BLFWriter(BaseIOHandler, Listener): +class BLFWriter(FileIOMessageWriter, Listener): """ Logs CAN data to a Binary Logging File compatible with Vector's tools. """ diff --git a/can/io/canutils.py b/can/io/canutils.py index b60b333c6..5aa4ad53b 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -8,8 +8,8 @@ from can.message import Message from can.listener import Listener -from .generic import BaseIOHandler - +from .generic import BaseIOHandler, FileIOMessageWriter +from ..typechecking import AcceptedIOType log = logging.getLogger("can.io.canutils") @@ -32,7 +32,7 @@ class CanutilsLogReader(BaseIOHandler): ``(0.0) vcan0 001#8d00100100820100`` """ - def __init__(self, file): + def __init__(self, file: AcceptedIOType) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text @@ -105,7 +105,7 @@ def __iter__(self): self.stop() -class CanutilsLogWriter(BaseIOHandler, Listener): +class CanutilsLogWriter(FileIOMessageWriter, Listener): """Logs CAN data to an ASCII log file (.log). This class is is compatible with "candump -L". @@ -114,7 +114,7 @@ class CanutilsLogWriter(BaseIOHandler, Listener): It the first message does not have a timestamp, it is set to zero. """ - def __init__(self, file, channel="vcan0", append=False): + def __init__(self, file: AcceptedIOType, channel="vcan0", append=False): """ :param file: a path-like object or as file-like object to write to If this is a file-like object, is has to opened in text diff --git a/can/io/csv.py b/can/io/csv.py index 35cfcc697..6e39e0096 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -13,10 +13,55 @@ from can.message import Message from can.listener import Listener -from .generic import BaseIOHandler +from .generic import BaseIOHandler, FileIOMessageWriter +from ..typechecking import AcceptedIOType -class CSVWriter(BaseIOHandler, Listener): +class CSVReader(BaseIOHandler): + """Iterator over CAN messages from a .csv file that was + generated by :class:`~can.CSVWriter` or that uses the same + format as described there. Assumes that there is a header + and thus skips the first line. + + Any line separator is accepted. + """ + + def __init__(self, file: AcceptedIOType) -> None: + """ + :param file: a path-like object or as file-like object to read from + If this is a file-like object, is has to opened in text + read mode, not binary read mode. + """ + super().__init__(file, mode="r") + + def __iter__(self): + # skip the header line + try: + next(self.file) + except StopIteration: + # don't crash on a file with only a header + return + + for line in self.file: + + timestamp, arbitration_id, extended, remote, error, dlc, data = line.split( + "," + ) + + yield Message( + timestamp=float(timestamp), + is_remote_frame=(remote == "1"), + is_extended_id=(extended == "1"), + is_error_frame=(error == "1"), + arbitration_id=int(arbitration_id, base=16), + dlc=int(dlc), + data=b64decode(data), + ) + + self.stop() + + +class CSVWriter(FileIOMessageWriter, Listener): """Writes a comma separated text file with a line for each message. Includes a header line. @@ -37,7 +82,7 @@ class CSVWriter(BaseIOHandler, Listener): Each line is terminated with a platform specific line separator. """ - def __init__(self, file, append=False): + def __init__(self, file: AcceptedIOType, append: bool = False) -> None: """ :param file: a path-like object or a file-like object to write to. If this is a file-like object, is has to open in text @@ -54,7 +99,7 @@ def __init__(self, file, append=False): if not append: self.file.write("timestamp,arbitration_id,extended,remote,error,dlc,data\n") - def on_message_received(self, msg): + def on_message_received(self, msg: Message) -> None: row = ",".join( [ repr(msg.timestamp), # cannot use str() here because that is rounding @@ -68,47 +113,3 @@ def on_message_received(self, msg): ) self.file.write(row) self.file.write("\n") - - -class CSVReader(BaseIOHandler): - """Iterator over CAN messages from a .csv file that was - generated by :class:`~can.CSVWriter` or that uses the same - format as described there. Assumes that there is a header - and thus skips the first line. - - Any line separator is accepted. - """ - - def __init__(self, file): - """ - :param file: a path-like object or as file-like object to read from - If this is a file-like object, is has to opened in text - read mode, not binary read mode. - """ - super().__init__(file, mode="r") - - def __iter__(self): - # skip the header line - try: - next(self.file) - except StopIteration: - # don't crash on a file with only a header - return - - for line in self.file: - - timestamp, arbitration_id, extended, remote, error, dlc, data = line.split( - "," - ) - - yield Message( - timestamp=float(timestamp), - is_remote_frame=(remote == "1"), - is_extended_id=(extended == "1"), - is_error_frame=(error == "1"), - arbitration_id=int(arbitration_id, base=16), - dlc=int(dlc), - data=b64decode(data), - ) - - self.stop() diff --git a/can/io/generic.py b/can/io/generic.py index 3ce9aa643..44caa80c6 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -30,7 +30,9 @@ class BaseIOHandler(ContextManager, metaclass=ABCMeta): file: Optional[can.typechecking.FileLike] - def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None: + def __init__( + self, file: Optional[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 @@ -77,6 +79,15 @@ class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): file: Union[TextIO, BinaryIO] + def __init__( + self, file: Union[can.typechecking.FileLike, TextIO, BinaryIO], mode: str = "rt" + ) -> None: + # Not possible with the type signature, but be verbose for user friendliness + if file is None: + raise ValueError("The given file cannot be None") + + super().__init__(file, mode) + # pylint: disable=too-few-public-methods class MessageReader(BaseIOHandler, Iterable, metaclass=ABCMeta): diff --git a/can/io/logger.py b/can/io/logger.py index 91e3066b4..ec77d7ac3 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -6,7 +6,15 @@ import pathlib from abc import ABC, abstractmethod from datetime import datetime -from typing import Any, cast, Callable, Optional +from typing import ( + Any, + Optional, + Callable, + cast, + Type, +) +from types import TracebackType + from pkg_resources import iter_entry_points @@ -86,64 +94,58 @@ def __new__( # type: ignore ) from None -class BaseRotatingLogger(Listener, ABC): +class BaseRotatingLogger(Listener, BaseIOHandler, ABC): """ Base class for rotating CAN loggers. This class is not meant to be - instantiated directly. Subclasses must implement the `should_rollover` + instantiated directly. Subclasses must implement the :attr:`should_rollover` and `do_rollover` methods according to their rotation strategy. The rotation behavior can be further customized by the user by setting - the `namer` and `rotator´ attributes after instantiating the subclass. + the :attr:`namer` and :attr:`rotator` attributes after instantiating the subclass. These attributes as well as the methods `rotation_filename` and `rotate` and the corresponding docstrings are carried over from the python builtin `BaseRotatingHandler`. - :attr Optional[Callable] namer: - If this attribute is set to a callable, the rotation_filename() method + Subclasses must set the `_writer` attribute upon initialization. + + :attr namer: + If this attribute is set to a callable, the :meth:`rotation_filename` method delegates to this callable. The parameters passed to the callable are - those passed to rotation_filename(). - :attr Optional[Callable] rotator: - If this attribute is set to a callable, the rotate() method delegates + those passed to :meth:`rotation_filename`. + :attr rotator: + If this attribute is set to a callable, the :meth:`rotate` method delegates to this callable. The parameters passed to the callable are those - passed to rotate(). - :attr int rollover_count: + passed to :meth:`rotate`. + :attr rollover_count: An integer counter to track the number of rollovers. - :attr FileIOMessageWriter writer: - This attribute holds an instance of a writer class which manages the - actual file IO. """ - supported_writers = { - ".asc": ASCWriter, - ".blf": BLFWriter, - ".csv": CSVWriter, - ".log": CanutilsLogWriter, - ".txt": Printer, - } namer: Optional[Callable[[StringPathLike], StringPathLike]] = None rotator: Optional[Callable[[StringPathLike, StringPathLike], None]] = None rollover_count: int = 0 - _writer: Optional[FileIOMessageWriter] = None def __init__(self, *args: Any, **kwargs: Any) -> None: + Listener.__init__(self) + BaseIOHandler.__init__(self, None) + self.writer_args = args self.writer_kwargs = kwargs + self._writer: FileIOMessageWriter = None # type: ignore # Expected to be set by the subclass + @property def writer(self) -> FileIOMessageWriter: - if not self._writer: - raise ValueError("Attempt to access writer failed.") - + """This attribute holds an instance of a writer class which manages the actual file IO.""" return self._writer def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: """Modify the filename of a log file when rotating. This is provided so that a custom filename can be provided. - The default implementation calls the 'namer' attribute of the + The default implementation calls the :attr:`namer` attribute of the handler, if it's callable, passing the default name to - it. If the attribute isn't callable (the default is None), the name + it. If the attribute isn't callable (the default is `None`), the name is returned unchanged. :param default_name: @@ -157,17 +159,17 @@ def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: def rotate(self, source: StringPathLike, dest: StringPathLike) -> None: """When rotating, rotate the current log. - The default implementation calls the 'rotator' attribute of the + The default implementation calls the :attr:`rotator` attribute of the handler, if it's callable, passing the source and dest arguments to - it. If the attribute isn't callable (the default is None), the source + it. If the attribute isn't callable (the default is `None`), the source is simply renamed to the destination. :param source: The source filename. This is normally the base - filename, e.g. 'test.log' + filename, e.g. `"test.log"` :param dest: The destination filename. This is normally - what the source is rotated to, e.g. 'test_#001.log'. + what the source is rotated to, e.g. `"test_#001.log"`. """ if not callable(self.rotator): if os.path.exists(source): @@ -187,8 +189,8 @@ def on_message_received(self, msg: Message) -> None: self.writer.on_message_received(msg) - def get_new_writer(self, filename: StringPathLike) -> None: - """Instantiate a new writer. + def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: + """Instantiate a new writer after stopping the old one. :param filename: Path-like object that specifies the location and name of the log file. @@ -196,17 +198,18 @@ def get_new_writer(self, filename: StringPathLike) -> None: :return: An instance of a writer class. """ - suffix = pathlib.Path(filename).suffix.lower() - try: - writer_class = self.supported_writers[suffix] - except KeyError: - raise ValueError( - f'Log format with suffix"{suffix}" is ' - f"not supported by {self.__class__.__name__}." - ) + # Close the old writer first + if self._writer is not None: + self._writer.stop() + + logger = Logger(filename, *self.writer_args, **self.writer_kwargs) + if isinstance(logger, FileIOMessageWriter): + return logger + elif isinstance(logger, Printer) and logger.file is not None: + return cast(FileIOMessageWriter, logger) else: - self._writer = writer_class( - filename, *self.writer_args, **self.writer_kwargs + raise Exception( + "The Logger corresponding to the arguments is not a FileIOMessageWriter or can.Printer" ) def stop(self) -> None: @@ -217,6 +220,17 @@ def stop(self) -> None: """ self.writer.stop() + def __enter__(self) -> "BaseRotatingLogger": + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> bool: + return self._writer.__exit__(exc_type, exc_val, exc_tb) + @abstractmethod def should_rollover(self, msg: Message) -> bool: """Determine if the rollover conditions are met.""" @@ -234,8 +248,8 @@ class SizedRotatingLogger(BaseRotatingLogger): by adding a timestamp and the rollover count. A new log file is then created and written to. - This behavior can be customized by setting the ´namer´ and `rotator` - attribute. + This behavior can be customized by setting the :attr:`namer` and + :attr:`rotator` attribute. Example:: @@ -257,9 +271,13 @@ class SizedRotatingLogger(BaseRotatingLogger): * .blf :class:`can.BLFWriter` * .csv: :class:`can.CSVWriter` * .log :class:`can.CanutilsLogWriter` - * .txt :class:`can.Printer` + * .txt :class:`can.Printer` (if pointing to a file) - The log files may be incomplete until `stop()` is called due to buffering. + .. note:: + The :class:`can.SqliteWriter` is not supported yet. + + The log files on disk may be incomplete due to buffering until + :meth:`~can.Listener.stop` is called. """ def __init__( @@ -277,12 +295,12 @@ def __init__( The size threshold at which a new log file shall be created. If set to 0, no rollover will be performed. """ - super(SizedRotatingLogger, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.base_filename = os.path.abspath(base_filename) self.max_bytes = max_bytes - self.get_new_writer(self.base_filename) + self._writer = self._get_new_writer(self.base_filename) def should_rollover(self, msg: Message) -> bool: if self.max_bytes <= 0: @@ -301,7 +319,7 @@ def do_rollover(self) -> None: dfn = self.rotation_filename(self._default_name()) self.rotate(sfn, dfn) - self.get_new_writer(self.base_filename) + self._writer = self._get_new_writer(self.base_filename) def _default_name(self) -> StringPathLike: """Generate the default rotation filename.""" diff --git a/can/io/printer.py b/can/io/printer.py index 27fc2caf2..ec003e0eb 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -4,8 +4,12 @@ import logging -from can.listener import Listener +from typing import Optional + +from ..message import Message +from ..listener import Listener from .generic import BaseIOHandler +from ..typechecking import AcceptedIOType log = logging.getLogger("can.io.printer") @@ -16,24 +20,26 @@ class Printer(BaseIOHandler, Listener): any messages it receives to the terminal (stdout). A message is turned into a string using :meth:`~can.Message.__str__`. - :attr bool write_to_file: `True` if this instance prints to a file instead of - standard out + :attr write_to_file: `True` if this instance prints to a file instead of + standard out """ - def __init__(self, file=None, append=False): + def __init__( + self, file: Optional[AcceptedIOType] = None, append: bool = False + ) -> None: """ :param file: An optional path-like object or a file-like object to "print" to instead of writing to standard out (stdout). If this is a file-like object, is has to be 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 + :param append: If set to `True` messages, are appended to the file, + else the file is truncated """ self.write_to_file = file is not None mode = "a" if append else "w" super().__init__(file, mode=mode) - def on_message_received(self, msg): + def on_message_received(self, msg: Message) -> None: if self.write_to_file: self.file.write(str(msg) + "\n") else: diff --git a/can/typechecking.py b/can/typechecking.py index 1aaf03ac4..627e1a86a 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -29,7 +29,7 @@ # Used by the IO module FileLike = typing.IO[typing.Any] StringPathLike = typing.Union[str, "os.PathLike[str]"] -AcceptedIOType = typing.Optional[typing.Union[FileLike, StringPathLike]] +AcceptedIOType = typing.Union[FileLike, StringPathLike] BusConfig = typing.NewType("BusConfig", typing.Dict[str, typing.Any]) diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index 0fff84677..dede154ab 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -# coding: utf-8 """ Test rotating loggers """ + import os from pathlib import Path import tempfile @@ -17,12 +17,16 @@ class TestBaseRotatingLogger: @staticmethod - def _get_instance(*args, **kwargs): + def _get_instance(path, *args, **kwargs) -> can.io.BaseRotatingLogger: class SubClass(can.io.BaseRotatingLogger): """Subclass that implements abstract methods for testing.""" - def should_rollover(self, msg): - ... + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._writer = can.Printer(file=path / "__unused.txt") + + def should_rollover(self, msg: can.Message) -> bool: + return False def do_rollover(self): ... @@ -34,7 +38,6 @@ def test_import(self): def test_attributes(self): assert issubclass(can.io.BaseRotatingLogger, can.Listener) - assert hasattr(can.io.BaseRotatingLogger, "supported_writers") assert hasattr(can.io.BaseRotatingLogger, "namer") assert hasattr(can.io.BaseRotatingLogger, "rotator") assert hasattr(can.io.BaseRotatingLogger, "rollover_count") @@ -42,49 +45,35 @@ def test_attributes(self): assert hasattr(can.io.BaseRotatingLogger, "rotation_filename") assert hasattr(can.io.BaseRotatingLogger, "rotate") assert hasattr(can.io.BaseRotatingLogger, "on_message_received") - assert hasattr(can.io.BaseRotatingLogger, "get_new_writer") assert hasattr(can.io.BaseRotatingLogger, "stop") assert hasattr(can.io.BaseRotatingLogger, "should_rollover") assert hasattr(can.io.BaseRotatingLogger, "do_rollover") - def test_supported_writers(self): - supported_writers = can.io.BaseRotatingLogger.supported_writers - assert supported_writers[".asc"] == can.ASCWriter - assert supported_writers[".blf"] == can.BLFWriter - assert supported_writers[".csv"] == can.CSVWriter - assert supported_writers[".log"] == can.CanutilsLogWriter - assert supported_writers[".txt"] == can.Printer - - def test_get_new_writer(self): - logger_instance = self._get_instance() - - # access to non existing writer shall raise ValueError - with pytest.raises(ValueError): - _ = logger_instance.writer + def test_get_new_writer(self, tmp_path): + logger_instance = self._get_instance(tmp_path) - with tempfile.TemporaryDirectory() as temp_dir: - logger_instance.get_new_writer(os.path.join(temp_dir, "file.ASC")) - assert isinstance(logger_instance.writer, can.ASCWriter) - logger_instance.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.ASC") + assert isinstance(writer, can.ASCWriter) + writer.stop() - logger_instance.get_new_writer(os.path.join(temp_dir, "file.BLF")) - assert isinstance(logger_instance.writer, can.BLFWriter) - logger_instance.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.BLF") + assert isinstance(writer, can.BLFWriter) + writer.stop() - logger_instance.get_new_writer(os.path.join(temp_dir, "file.CSV")) - assert isinstance(logger_instance.writer, can.CSVWriter) - logger_instance.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.CSV") + assert isinstance(writer, can.CSVWriter) + writer.stop() - logger_instance.get_new_writer(os.path.join(temp_dir, "file.LOG")) - assert isinstance(logger_instance.writer, can.CanutilsLogWriter) - logger_instance.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.LOG") + assert isinstance(writer, can.CanutilsLogWriter) + writer.stop() - logger_instance.get_new_writer(os.path.join(temp_dir, "file.TXT")) - assert isinstance(logger_instance.writer, can.Printer) - logger_instance.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.TXT") + assert isinstance(writer, can.Printer) + writer.stop() - def test_rotation_filename(self): - logger_instance = self._get_instance() + def test_rotation_filename(self, tmp_path): + logger_instance = self._get_instance(tmp_path) default_name = "default" assert logger_instance.rotation_filename(default_name) == "default" @@ -92,61 +81,61 @@ def test_rotation_filename(self): logger_instance.namer = lambda x: x + "_by_namer" assert logger_instance.rotation_filename(default_name) == "default_by_namer" - def test_rotate(self): - logger_instance = self._get_instance() + def test_rotate_without_rotator(self, tmp_path): + logger_instance = self._get_instance(tmp_path) - # test without rotator - with tempfile.TemporaryDirectory() as temp_dir: - source = os.path.join(temp_dir, "source.txt") - dest = os.path.join(temp_dir, "dest.txt") + source = str(tmp_path / "source.txt") + dest = str(tmp_path / "dest.txt") - assert os.path.exists(source) is False - assert os.path.exists(dest) is False + assert os.path.exists(source) is False + assert os.path.exists(dest) is False - logger_instance.get_new_writer(source) - logger_instance.stop() + logger_instance._get_new_writer(source) + logger_instance.stop() + + assert os.path.exists(source) is True + assert os.path.exists(dest) is False - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + logger_instance.rotate(source, dest) - logger_instance.rotate(source, dest) + assert os.path.exists(source) is False + assert os.path.exists(dest) is True - assert os.path.exists(source) is False - assert os.path.exists(dest) is True + def test_rotate_with_rotator(self, tmp_path): + logger_instance = self._get_instance(tmp_path) - # test with rotator rotator_func = Mock() logger_instance.rotator = rotator_func - with tempfile.TemporaryDirectory() as temp_dir: - source = os.path.join(temp_dir, "source.txt") - dest = os.path.join(temp_dir, "dest.txt") - assert os.path.exists(source) is False - assert os.path.exists(dest) is False + source = str(tmp_path / "source.txt") + dest = str(tmp_path / "dest.txt") - logger_instance.get_new_writer(source) - logger_instance.stop() + assert os.path.exists(source) is False + assert os.path.exists(dest) is False - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + logger_instance._get_new_writer(source) + logger_instance.stop() - logger_instance.rotate(source, dest) - rotator_func.assert_called_with(source, dest) + assert os.path.exists(source) is True + assert os.path.exists(dest) is False - # assert that no rotation was performed since rotator_func - # does not do anything - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + logger_instance.rotate(source, dest) + rotator_func.assert_called_with(source, dest) - def test_stop(self): - """Test if stop() method of writer is called.""" - logger_instance = self._get_instance() + # assert that no rotation was performed since rotator_func + # does not do anything + assert os.path.exists(source) is True + assert os.path.exists(dest) is False - with tempfile.TemporaryDirectory() as temp_dir: - logger_instance.get_new_writer(os.path.join(temp_dir, "file.ASC")) + def test_stop(self, tmp_path): + """Test if stop() method of writer is called.""" + with self._get_instance(tmp_path) as logger_instance: + logger_instance._writer = logger_instance._get_new_writer( + tmp_path / "file.ASC" + ) # replace stop method of writer with Mock - org_stop = logger_instance.writer.stop + original_stop = logger_instance.writer.stop mock_stop = Mock() logger_instance.writer.stop = mock_stop @@ -154,48 +143,47 @@ def test_stop(self): mock_stop.assert_called() # close file.ASC to enable cleanup of temp_dir - org_stop() + original_stop() - def test_on_message_received(self): - logger_instance = self._get_instance() + def test_on_message_received(self, tmp_path): + logger_instance = self._get_instance(tmp_path) - with tempfile.TemporaryDirectory() as temp_dir: - logger_instance.get_new_writer(os.path.join(temp_dir, "file.ASC")) + logger_instance._writer = logger_instance._get_new_writer(tmp_path / "file.ASC") - # Test without rollover - should_rollover = Mock(return_value=False) - do_rollover = Mock() - writers_on_message_received = Mock() + # Test without rollover + should_rollover = Mock(return_value=False) + do_rollover = Mock() + writers_on_message_received = Mock() - logger_instance.should_rollover = should_rollover - logger_instance.do_rollover = do_rollover - logger_instance.writer.on_message_received = writers_on_message_received + logger_instance.should_rollover = should_rollover + logger_instance.do_rollover = do_rollover + logger_instance.writer.on_message_received = writers_on_message_received - msg = generate_message(0x123) - logger_instance.on_message_received(msg) + msg = generate_message(0x123) + logger_instance.on_message_received(msg) - should_rollover.assert_called_with(msg) - do_rollover.assert_not_called() - writers_on_message_received.assert_called_with(msg) + should_rollover.assert_called_with(msg) + do_rollover.assert_not_called() + writers_on_message_received.assert_called_with(msg) - # Test with rollover - should_rollover = Mock(return_value=True) - do_rollover = Mock() - writers_on_message_received = Mock() + # Test with rollover + should_rollover = Mock(return_value=True) + do_rollover = Mock() + writers_on_message_received = Mock() - logger_instance.should_rollover = should_rollover - logger_instance.do_rollover = do_rollover - logger_instance.writer.on_message_received = writers_on_message_received + logger_instance.should_rollover = should_rollover + logger_instance.do_rollover = do_rollover + logger_instance.writer.on_message_received = writers_on_message_received - msg = generate_message(0x123) - logger_instance.on_message_received(msg) + msg = generate_message(0x123) + logger_instance.on_message_received(msg) - should_rollover.assert_called_with(msg) - do_rollover.assert_called() - writers_on_message_received.assert_called_with(msg) + should_rollover.assert_called_with(msg) + do_rollover.assert_called() + writers_on_message_received.assert_called_with(msg) - # stop writer to enable cleanup of temp_dir - logger_instance.stop() + # stop writer to enable cleanup of temp_dir + logger_instance.stop() class TestSizedRotatingLogger: @@ -205,64 +193,74 @@ def test_import(self): def test_attributes(self): assert issubclass(can.SizedRotatingLogger, can.io.BaseRotatingLogger) - assert hasattr(can.SizedRotatingLogger, "supported_writers") assert hasattr(can.SizedRotatingLogger, "namer") assert hasattr(can.SizedRotatingLogger, "rotator") assert hasattr(can.SizedRotatingLogger, "should_rollover") assert hasattr(can.SizedRotatingLogger, "do_rollover") - def test_create_instance(self): + def test_create_instance(self, tmp_path): base_filename = "mylogfile.ASC" max_bytes = 512 - with tempfile.TemporaryDirectory() as temp_dir: - logger_instance = can.SizedRotatingLogger( - base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes - ) - assert Path(logger_instance.base_filename).name == base_filename - assert logger_instance.max_bytes == max_bytes - assert logger_instance.rollover_count == 0 - assert isinstance(logger_instance.writer, can.ASCWriter) + logger_instance = can.SizedRotatingLogger( + base_filename=tmp_path / base_filename, max_bytes=max_bytes + ) + assert Path(logger_instance.base_filename).name == base_filename + assert logger_instance.max_bytes == max_bytes + assert logger_instance.rollover_count == 0 + assert isinstance(logger_instance.writer, can.ASCWriter) - logger_instance.stop() + logger_instance.stop() - def test_should_rollover(self): + def test_should_rollover(self, tmp_path): base_filename = "mylogfile.ASC" max_bytes = 512 - with tempfile.TemporaryDirectory() as temp_dir: - logger_instance = can.SizedRotatingLogger( - base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes - ) - msg = generate_message(0x123) - do_rollover = Mock() - logger_instance.do_rollover = do_rollover + logger_instance = can.SizedRotatingLogger( + base_filename=tmp_path / base_filename, max_bytes=max_bytes + ) + msg = generate_message(0x123) + do_rollover = Mock() + logger_instance.do_rollover = do_rollover - logger_instance.writer.file.tell = Mock(return_value=511) - assert logger_instance.should_rollover(msg) is False - logger_instance.on_message_received(msg) - do_rollover.assert_not_called() + logger_instance.writer.file.tell = Mock(return_value=511) + assert logger_instance.should_rollover(msg) is False + logger_instance.on_message_received(msg) + do_rollover.assert_not_called() + + logger_instance.writer.file.tell = Mock(return_value=512) + assert logger_instance.should_rollover(msg) is True + logger_instance.on_message_received(msg) + do_rollover.assert_called() + + logger_instance.stop() - logger_instance.writer.file.tell = Mock(return_value=512) - assert logger_instance.should_rollover(msg) is True + def test_logfile_size(self, tmp_path): + base_filename = "mylogfile.ASC" + max_bytes = 1024 + msg = generate_message(0x123) + + logger_instance = can.SizedRotatingLogger( + base_filename=tmp_path / base_filename, max_bytes=max_bytes + ) + for _ in range(128): logger_instance.on_message_received(msg) - do_rollover.assert_called() - logger_instance.stop() + for file_path in os.listdir(tmp_path): + assert os.path.getsize(tmp_path / file_path) <= 1100 - def test_logfile_size(self): + logger_instance.stop() + + def test_logfile_size_context_manager(self, tmp_path): base_filename = "mylogfile.ASC" max_bytes = 1024 msg = generate_message(0x123) - with tempfile.TemporaryDirectory() as temp_dir: - logger_instance = can.SizedRotatingLogger( - base_filename=os.path.join(temp_dir, base_filename), max_bytes=max_bytes - ) + with can.SizedRotatingLogger( + base_filename=tmp_path / base_filename, max_bytes=max_bytes + ) as logger_instance: for _ in range(128): logger_instance.on_message_received(msg) - for file_path in os.listdir(temp_dir): - assert os.path.getsize(os.path.join(temp_dir, file_path)) <= 1100 - - logger_instance.stop() + for file_path in os.listdir(tmp_path): + assert os.path.getsize(os.path.join(tmp_path, file_path)) <= 1100 diff --git a/tox.ini b/tox.ini index c8e6b483d..9964fb1e6 100644 --- a/tox.ini +++ b/tox.ini @@ -2,13 +2,13 @@ [testenv] deps = - pytest==6.2.*,>=6.2.4 - pytest-timeout==1.4.* - pytest-cov==2.12.* - coverage==5.5 - codecov==2.1.10 + pytest==6.2.*,>=6.2.5 + pytest-timeout==2.0.1 + pytest-cov==3.0.0 + coverage==6.0.2 + codecov==2.1.12 hypothesis~=6.24.0 - pyserial~=3.0 + pyserial~=3.5 parameterized~=0.8 commands = From 87effed64621ffcc59a9dcbca67759684825ce43 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 11 Nov 2021 10:23:48 +0100 Subject: [PATCH 0737/1235] Specific Exceptions: Adapting systec interface (#1158) * Improve error handling with existing classes * Format code with black * Correct exception handling at startup * Throw the correct exceptions --- can/interfaces/systec/__init__.py | 3 - can/interfaces/systec/exceptions.py | 157 +++++++++++++++------------- can/interfaces/systec/ucan.py | 10 ++ can/interfaces/systec/ucanbus.py | 126 +++++++++++++--------- test/test_systec.py | 2 +- 5 files changed, 174 insertions(+), 124 deletions(-) diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py index 4ecd39b4c..be7004b6a 100644 --- a/can/interfaces/systec/__init__.py +++ b/can/interfaces/systec/__init__.py @@ -1,4 +1 @@ -""" -""" - from can.interfaces.systec.ucanbus import UcanBus diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index 72ec92cfa..733326194 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -1,92 +1,107 @@ +from typing import Dict + +from abc import ABC, abstractmethod + from .constants import ReturnCode from can import CanError -class UcanException(CanError): - """ Base class for USB can errors. """ +class UcanException(CanError, ABC): + """Base class for USB can errors.""" def __init__(self, result, func, arguments): - self.result = result.value + self.result = result self.func = func self.arguments = arguments - self.return_msgs = {} - super().__init__() - def __str__(self): - message = self.return_msgs.get(self.result, "unknown") - return f"Function {self.func.__name__} returned {self.result}: {message}" + message = self._error_message_mapping.get(result, "unknown") + super().__init__( + message=f"Function {func.__name__} (called with {arguments}): {message}", + error_code=result.value, + ) + + @property + @abstractmethod + def _error_message_mapping(self) -> Dict[ReturnCode, str]: + ... class UcanError(UcanException): - """ Exception class for errors from USB-CAN-library. """ + """Exception class for errors from USB-CAN-library.""" - def __init__(self, result, func, arguments): - super().__init__(result, func, arguments) - self.return_msgs = { - ReturnCode.ERR_RESOURCE: "could not created a resource (memory, handle, ...)", - ReturnCode.ERR_MAXMODULES: "the maximum number of opened modules is reached", - ReturnCode.ERR_HWINUSE: "the specified module is already in use", - ReturnCode.ERR_ILLVERSION: "the software versions of the module and library are incompatible", - ReturnCode.ERR_ILLHW: "the module with the specified device number is not connected " - "(or used by an other application)", - ReturnCode.ERR_ILLHANDLE: "wrong USB-CAN-Handle handed over to the function", - ReturnCode.ERR_ILLPARAM: "wrong parameter handed over to the function", - ReturnCode.ERR_BUSY: "instruction can not be processed at this time", - ReturnCode.ERR_TIMEOUT: "no answer from module", - ReturnCode.ERR_IOFAILED: "a request to the driver failed", - ReturnCode.ERR_DLL_TXFULL: "a CAN message did not fit into the transmit buffer", - ReturnCode.ERR_MAXINSTANCES: "maximum number of applications is reached", - ReturnCode.ERR_CANNOTINIT: "CAN interface is not yet initialized", - ReturnCode.ERR_DISCONECT: "USB-CANmodul was disconnected", - ReturnCode.ERR_NOHWCLASS: "the needed device class does not exist", - ReturnCode.ERR_ILLCHANNEL: "illegal CAN channel", - ReturnCode.ERR_RESERVED1: "reserved", - ReturnCode.ERR_ILLHWTYPE: "the API function can not be used with this hardware", - } + _ERROR_MESSAGES = { + ReturnCode.ERR_RESOURCE: "could not created a resource (memory, handle, ...)", + ReturnCode.ERR_MAXMODULES: "the maximum number of opened modules is reached", + ReturnCode.ERR_HWINUSE: "the specified module is already in use", + ReturnCode.ERR_ILLVERSION: "the software versions of the module and library are incompatible", + ReturnCode.ERR_ILLHW: "the module with the specified device number is not connected " + "(or used by an other application)", + ReturnCode.ERR_ILLHANDLE: "wrong USB-CAN-Handle handed over to the function", + ReturnCode.ERR_ILLPARAM: "wrong parameter handed over to the function", + ReturnCode.ERR_BUSY: "instruction can not be processed at this time", + ReturnCode.ERR_TIMEOUT: "no answer from module", + ReturnCode.ERR_IOFAILED: "a request to the driver failed", + ReturnCode.ERR_DLL_TXFULL: "a CAN message did not fit into the transmit buffer", + ReturnCode.ERR_MAXINSTANCES: "maximum number of applications is reached", + ReturnCode.ERR_CANNOTINIT: "CAN interface is not yet initialized", + ReturnCode.ERR_DISCONECT: "USB-CANmodul was disconnected", + ReturnCode.ERR_NOHWCLASS: "the needed device class does not exist", + ReturnCode.ERR_ILLCHANNEL: "illegal CAN channel", + ReturnCode.ERR_RESERVED1: "reserved", + ReturnCode.ERR_ILLHWTYPE: "the API function can not be used with this hardware", + } + + @property + def _error_message_mapping(self) -> Dict[ReturnCode, str]: + return UcanError._ERROR_MESSAGES class UcanCmdError(UcanException): - """ Exception class for errors from firmware in USB-CANmodul.""" + """Exception class for errors from firmware in USB-CANmodul.""" - def __init__(self, result, func, arguments): - super().__init__(result, func, arguments) - self.return_msgs = { - ReturnCode.ERRCMD_NOTEQU: "the received response does not match to the transmitted command", - ReturnCode.ERRCMD_REGTST: "no access to the CAN controller", - ReturnCode.ERRCMD_ILLCMD: "the module could not interpret the command", - ReturnCode.ERRCMD_EEPROM: "error while reading the EEPROM", - ReturnCode.ERRCMD_RESERVED1: "reserved", - ReturnCode.ERRCMD_RESERVED2: "reserved", - ReturnCode.ERRCMD_RESERVED3: "reserved", - ReturnCode.ERRCMD_ILLBDR: "illegal baud rate value specified in BTR0/BTR1 for systec " - "USB-CANmoduls", - ReturnCode.ERRCMD_NOTINIT: "CAN channel is not initialized", - ReturnCode.ERRCMD_ALREADYINIT: "CAN channel is already initialized", - ReturnCode.ERRCMD_ILLSUBCMD: "illegal sub-command specified", - ReturnCode.ERRCMD_ILLIDX: "illegal index specified (e.g. index for cyclic CAN messages)", - ReturnCode.ERRCMD_RUNNING: "cyclic CAN message(s) can not be defined because transmission of " - "cyclic CAN messages is already running", - } + _ERROR_MESSAGES = { + ReturnCode.ERRCMD_NOTEQU: "the received response does not match to the transmitted command", + ReturnCode.ERRCMD_REGTST: "no access to the CAN controller", + ReturnCode.ERRCMD_ILLCMD: "the module could not interpret the command", + ReturnCode.ERRCMD_EEPROM: "error while reading the EEPROM", + ReturnCode.ERRCMD_RESERVED1: "reserved", + ReturnCode.ERRCMD_RESERVED2: "reserved", + ReturnCode.ERRCMD_RESERVED3: "reserved", + ReturnCode.ERRCMD_ILLBDR: "illegal baud rate value specified in BTR0/BTR1 for systec " + "USB-CANmoduls", + ReturnCode.ERRCMD_NOTINIT: "CAN channel is not initialized", + ReturnCode.ERRCMD_ALREADYINIT: "CAN channel is already initialized", + ReturnCode.ERRCMD_ILLSUBCMD: "illegal sub-command specified", + ReturnCode.ERRCMD_ILLIDX: "illegal index specified (e.g. index for cyclic CAN messages)", + ReturnCode.ERRCMD_RUNNING: "cyclic CAN message(s) can not be defined because transmission of " + "cyclic CAN messages is already running", + } + + @property + def _error_message_mapping(self) -> Dict[ReturnCode, str]: + return UcanCmdError._ERROR_MESSAGES class UcanWarning(UcanException): - """ Exception class for warnings, the function has been executed anyway. """ + """Exception class for warnings, the function has been executed anyway.""" - def __init__(self, result, func, arguments): - super().__init__(result, func, arguments) - self.return_msgs = { - ReturnCode.WARN_NODATA: "no CAN messages received", - ReturnCode.WARN_SYS_RXOVERRUN: "overrun in receive buffer of the kernel driver", - ReturnCode.WARN_DLL_RXOVERRUN: "overrun in receive buffer of the USB-CAN-library", - ReturnCode.WARN_RESERVED1: "reserved", - ReturnCode.WARN_RESERVED2: "reserved", - ReturnCode.WARN_FW_TXOVERRUN: "overrun in transmit buffer of the firmware (but this CAN message " - "was successfully stored in buffer of the ibrary)", - ReturnCode.WARN_FW_RXOVERRUN: "overrun in receive buffer of the firmware (but this CAN message " - "was successfully read)", - ReturnCode.WARN_FW_TXMSGLOST: "reserved", - ReturnCode.WARN_NULL_PTR: "pointer is NULL", - ReturnCode.WARN_TXLIMIT: "not all CAN messages could be stored to the transmit buffer in " - "USB-CAN-library", - ReturnCode.WARN_BUSY: "reserved", - } + _ERROR_MESSAGES = { + ReturnCode.WARN_NODATA: "no CAN messages received", + ReturnCode.WARN_SYS_RXOVERRUN: "overrun in receive buffer of the kernel driver", + ReturnCode.WARN_DLL_RXOVERRUN: "overrun in receive buffer of the USB-CAN-library", + ReturnCode.WARN_RESERVED1: "reserved", + ReturnCode.WARN_RESERVED2: "reserved", + ReturnCode.WARN_FW_TXOVERRUN: "overrun in transmit buffer of the firmware (but this CAN message " + "was successfully stored in buffer of the ibrary)", + ReturnCode.WARN_FW_RXOVERRUN: "overrun in receive buffer of the firmware (but this CAN message " + "was successfully read)", + ReturnCode.WARN_FW_TXMSGLOST: "reserved", + ReturnCode.WARN_NULL_PTR: "pointer is NULL", + ReturnCode.WARN_TXLIMIT: "not all CAN messages could be stored to the transmit buffer in " + "USB-CAN-library", + ReturnCode.WARN_BUSY: "reserved", + } + + @property + def _error_message_mapping(self) -> Dict[ReturnCode, str]: + return UcanWarning._ERROR_MESSAGES diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index 3f90e6cc2..6e150f7b1 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -4,6 +4,8 @@ from ctypes import byref from ctypes import c_wchar_p as LPWSTR +from ...exceptions import CanInterfaceNotImplementedError + from .constants import * from .structures import * from .exceptions import * @@ -110,6 +112,7 @@ def check_result(result, func, arguments): return result +_UCAN_INITIALIZED = False if os.name != "nt": log.warning("SYSTEC ucan library does not work on %s platform.", sys.platform) else: @@ -310,6 +313,8 @@ def check_result(result, func, arguments): UcanEnableCyclicCanMsg.argtypes = [Handle, BYTE, DWORD] UcanEnableCyclicCanMsg.errcheck = check_result + _UCAN_INITIALIZED = True + except Exception as ex: log.warning("Cannot load SYSTEC ucan library: %s.", ex) @@ -323,6 +328,11 @@ class UcanServer: _connect_control_ref = None def __init__(self): + if not _UCAN_INITIALIZED: + raise CanInterfaceNotImplementedError( + "The interface could not be loaded on the current platform" + ) + self._handle = Handle(INVALID_HANDLE) self._is_initialized = False self._hw_is_initialized = False diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 2d3a23777..c1068494f 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -2,9 +2,11 @@ from threading import Event from can import BusABC, BusState, Message +from ...exceptions import CanError, CanInitializationError, CanOperationError from .constants import * from .structures import * +from .exceptions import UcanException from .ucan import UcanServer log = logging.getLogger("can.systec") @@ -86,13 +88,20 @@ def __init__(self, channel, can_filters=None, **kwargs): :raises ValueError: If invalid input parameter were passed. - :raises can.CanError: + :raises can.CanInterfaceNotImplementedError: + If the platform is not supported. + + :raises can.CanInitializationError: If hardware or CAN interface initialization failed. """ try: self._ucan = Ucan() - except Exception: - raise ImportError("The SYSTEC ucan library has not been initialized.") + except CanError as error: + raise error + except Exception as exception: + raise CanInitializationError( + "The SYSTEC ucan library has not been initialized." + ) from exception self.channel = int(channel) device_number = int(kwargs.get("device_number", ANY_MODULE)) @@ -100,7 +109,7 @@ def __init__(self, channel, can_filters=None, **kwargs): # configuration options bitrate = kwargs.get("bitrate", 500000) if bitrate not in self.BITRATES: - raise ValueError("Invalid bitrate {}".format(bitrate)) + raise ValueError(f"Invalid bitrate {bitrate}") state = kwargs.get("state", BusState.ACTIVE) if state is BusState.ACTIVE or state is BusState.PASSIVE: @@ -121,21 +130,29 @@ def __init__(self, channel, can_filters=None, **kwargs): if kwargs.get("tx_buffer_entries"): self._params["tx_buffer_entries"] = int(kwargs.get("tx_buffer_entries")) - self._ucan.init_hardware(device_number=device_number) - self._ucan.init_can(self.channel, **self._params) - hw_info_ex, _, _ = self._ucan.get_hardware_info() - self.channel_info = "%s, S/N %s, CH %s, BTR %s" % ( - self._ucan.get_product_code_message(hw_info_ex.product_code), - hw_info_ex.serial, - self.channel, - self._ucan.get_baudrate_message(self.BITRATES[bitrate]), - ) + try: + self._ucan.init_hardware(device_number=device_number) + self._ucan.init_can(self.channel, **self._params) + hw_info_ex, _, _ = self._ucan.get_hardware_info() + self.channel_info = "%s, S/N %s, CH %s, BTR %s" % ( + self._ucan.get_product_code_message(hw_info_ex.product_code), + hw_info_ex.serial, + self.channel, + self._ucan.get_baudrate_message(self.BITRATES[bitrate]), + ) + except UcanException as exception: + raise CanInitializationError() from exception + self._is_filtered = False super().__init__(channel=channel, can_filters=can_filters, **kwargs) def _recv_internal(self, timeout): - message, _ = self._ucan.read_can_msg(self.channel, 1, timeout) + try: + message, _ = self._ucan.read_can_msg(self.channel, 1, timeout) + except UcanException as exception: + raise CanOperationError() from exception + if not message: return None, False @@ -164,21 +181,23 @@ def send(self, msg, timeout=None): :param float timeout: Transmit timeout in seconds (value 0 switches off the "auto delete") - :raises can.CanError: + :raises can.CanOperationError: If the message could not be sent. - """ - if timeout is not None and timeout >= 0: - self._ucan.set_tx_timeout(self.channel, int(timeout * 1000)) - - message = CanMsg( - msg.arbitration_id, - MsgFrameFormat.MSG_FF_STD - | (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) - | (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), - msg.data, - ) - self._ucan.write_can_msg(self.channel, [message]) + try: + if timeout is not None and timeout >= 0: + self._ucan.set_tx_timeout(self.channel, int(timeout * 1000)) + + message = CanMsg( + msg.arbitration_id, + MsgFrameFormat.MSG_FF_STD + | (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) + | (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), + msg.data, + ) + self._ucan.write_can_msg(self.channel, [message]) + except UcanException as exception: + raise CanOperationError() from exception @staticmethod def _detect_available_configs(): @@ -206,16 +225,19 @@ def _detect_available_configs(): return configs def _apply_filters(self, filters): - if filters and len(filters) == 1: - can_id = filters[0]["can_id"] - can_mask = filters[0]["can_mask"] - self._ucan.set_acceptance(self.channel, can_mask, can_id) - self._is_filtered = True - log.info("Hardware filtering on ID 0x%X, mask 0x%X", can_id, can_mask) - else: - self._ucan.set_acceptance(self.channel) - self._is_filtered = False - log.info("Hardware filtering has been disabled") + try: + if filters and len(filters) == 1: + can_id = filters[0]["can_id"] + can_mask = filters[0]["can_mask"] + self._ucan.set_acceptance(self.channel, can_mask, can_id) + self._is_filtered = True + log.info("Hardware filtering on ID 0x%X, mask 0x%X", can_id, can_mask) + else: + self._ucan.set_acceptance(self.channel) + self._is_filtered = False + log.info("Hardware filtering has been disabled") + except UcanException as exception: + raise CanOperationError() from exception def flush_tx_buffer(self): """ @@ -225,7 +247,10 @@ def flush_tx_buffer(self): If flushing of the transmit buffer failed. """ log.info("Flushing transmit buffer") - self._ucan.reset_can(self.channel, ResetFlags.RESET_ONLY_TX_BUFF) + try: + self._ucan.reset_can(self.channel, ResetFlags.RESET_ONLY_TX_BUFF) + except UcanException as exception: + raise CanOperationError() from exception @staticmethod def create_filter(extended, from_id, to_id, rtr_only, rtr_too): @@ -270,15 +295,18 @@ def state(self, new_state): if self._state is not BusState.ERROR and ( new_state is BusState.ACTIVE or new_state is BusState.PASSIVE ): - # close the CAN channel - self._ucan.shutdown(self.channel, False) - # set mode - if new_state is BusState.ACTIVE: - self._params["mode"] &= ~Mode.MODE_LISTEN_ONLY - else: - self._params["mode"] |= Mode.MODE_LISTEN_ONLY - # reinitialize CAN channel - self._ucan.init_can(self.channel, **self._params) + try: + # close the CAN channel + self._ucan.shutdown(self.channel, False) + # set mode + if new_state is BusState.ACTIVE: + self._params["mode"] &= ~Mode.MODE_LISTEN_ONLY + else: + self._params["mode"] |= Mode.MODE_LISTEN_ONLY + # reinitialize CAN channel + self._ucan.init_can(self.channel, **self._params) + except UcanException as exception: + raise CanOperationError() from exception def shutdown(self): """ @@ -286,5 +314,5 @@ def shutdown(self): """ try: self._ucan.shutdown() - except Exception as ex: - log.error(ex) + except Exception as exception: + log.error(exception) diff --git a/test/test_systec.py b/test/test_systec.py index 6910653fc..5e8b30dcf 100644 --- a/test/test_systec.py +++ b/test/test_systec.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 import unittest from unittest.mock import Mock, patch @@ -32,6 +31,7 @@ def setUp(self): ucan.UcanDeinitHardware = Mock() ucan.UcanWriteCanMsgEx = Mock() ucan.UcanResetCanEx = Mock() + ucan._UCAN_INITIALIZED = True # Fake this self.bus = can.Bus(bustype="systec", channel=0, bitrate=125000) def test_bus_creation(self): From 9319968a7c5904692789f16a5ebec29411f5de05 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:34:59 +0200 Subject: [PATCH 0738/1235] Remove duplicated documentation --- can/interfaces/slcan.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 30f346697..99f754a42 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -1,10 +1,5 @@ """ Interface for slcan compatible interfaces (win32/linux). - -.. note:: - - Linux users can use slcand or socketcan as well. - """ from typing import Any, Optional, Tuple From 7a69650a916661ac9c49104f855a13b31a8b5a2b Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 19:00:20 +0200 Subject: [PATCH 0739/1235] Cleanups and use new exceptions --- can/interfaces/slcan.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 99f754a42..f3022cf89 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -3,13 +3,15 @@ """ from typing import Any, Optional, Tuple -from can import typechecking import io import time import logging from can import BusABC, Message +from ..exceptions import CanInterfaceNotImplementedError, CanOperationError +from can import typechecking + logger = logging.getLogger(__name__) @@ -62,11 +64,11 @@ def __init__( """ :raise ValueError: if both *bitrate* and *btr* are set - :param channel: - port of underlying serial or usb device (e.g. /dev/ttyUSB0, COM8, ...) - Must not be empty. - :param ttyBaudrate: - baudrate of underlying serial or usb device + :param str channel: + port of underlying serial or usb device (e.g. ``/dev/ttyUSB0``, ``COM8``, ...) + Must not be empty. Can also end with ``@115200`` (or similarly) to specify the baudrate. + :param int ttyBaudrate: + baudrate of underlying serial or usb device (Ignored if set via the ``channel`` parameter) :param bitrate: Bitrate in bit/s :param btr: @@ -78,6 +80,8 @@ def __init__( :param rtscts: turn hardware handshake (RTS/CTS) on and off """ + if serial is None: + raise CanInterfaceNotImplementedError("The serial module is not installed") if not channel: # if None or empty raise TypeError("Must specify a serial port.") @@ -115,11 +119,8 @@ def set_bitrate(self, bitrate: int) -> None: if bitrate in self._BITRATES: self._write(self._BITRATES[bitrate]) else: - raise ValueError( - "Invalid bitrate, choose one of " - + (", ".join(str(k) for k in self._BITRATES.keys())) - + "." - ) + bitrates = ", ".join(str(k) for k in self._BITRATES.keys()) + raise ValueError(f"Invalid bitrate, choose one of {bitrates}.") self.open() def set_bitrate_reg(self, btr: str) -> None: @@ -215,6 +216,7 @@ def _recv_internal( dlc = int(string[9]) extended = True remote = True + if canId is not None: msg = Message( arbitration_id=canId, @@ -254,6 +256,8 @@ def fileno(self) -> int: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" ) + except Exception as exception: + raise CanOperationError("Cannot fetch fileno") from exception def get_version( self, timeout: Optional[float] @@ -299,10 +303,10 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: """Get serial number of the slcan interface. :param timeout: - seconds to wait for serial number or None to wait indefinitely + seconds to wait for serial number or ``None`` to wait indefinitely :return: - None on timeout or a str object. + ``None`` on timeout or a :class:`~builtin.str` object. """ cmd = "N" self._write(cmd) From ec008930547294ab81d22cc9a6c30445df905643 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:01:53 +0000 Subject: [PATCH 0740/1235] Format code with black --- can/interfaces/slcan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index f3022cf89..ac90a2600 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -59,7 +59,7 @@ def __init__( btr: Optional[str] = None, sleep_after_open: float = _SLEEP_AFTER_SERIAL_OPEN, rtscts: bool = False, - **kwargs: Any + **kwargs: Any, ) -> None: """ :raise ValueError: if both *bitrate* and *btr* are set From c151e7c5bc452f010fc5cc98566dfeba29a47bb8 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 16:37:56 +0200 Subject: [PATCH 0741/1235] Specific Exceptions: Adapting cantace interface Part of #1046. Also publicly exposes a new exception helper: `error_check` that was previously part of the `usb2can` interface. --- can/exceptions.py | 19 ++++ can/interfaces/cantact.py | 100 ++++++++++-------- .../usb2can/usb2canabstractionlayer.py | 10 +- 3 files changed, 76 insertions(+), 53 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index 7d6374735..e8731737c 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -15,7 +15,11 @@ :class:`ValueError`. This should always be documented for the function at hand. """ + +from contextlib import contextmanager + from typing import Optional +from typing import Type class CanError(Exception): @@ -96,3 +100,18 @@ class CanTimeoutError(CanError, TimeoutError): - Some message could not be sent after the timeout elapsed - No message was read within the given time """ + + +@contextmanager +def error_check( + error_message: Optional[str] = None, + exception_type: Type[CanError] = CanOperationError, +) -> None: + """Catches any exceptions and turns them into the new type while preserving the stack trace.""" + try: + yield + except Exception as error: + if error_message is None: + raise exception_type(str(error)) from error + else: + raise exception_type(error_message) from error diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 9f8bf6314..45f68d1df 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -7,14 +7,16 @@ from unittest.mock import Mock from can import BusABC, Message +from ..exceptions import CanInitializationError, error_check logger = logging.getLogger(__name__) try: import cantact except ImportError: + cantact = None logger.warning( - "The CANtact module is not installed. Install it using `python3 -m pip install cantact`" + "The CANtact module is not installed. Install it using `python -m pip install cantact`" ) @@ -26,7 +28,7 @@ def _detect_available_configs(): try: interface = cantact.Interface() except (NameError, SystemError): - # couldn't import cantact, so no configurations are available + logger.debug("Could not import cantact, so no configurations are available") return [] channels = [] @@ -42,7 +44,7 @@ def __init__( monitor=False, bit_timing=None, _testing=False, - **kwargs + **kwargs, ): """ :param int channel: @@ -58,36 +60,45 @@ def __init__( if _testing: self.interface = MockInterface() else: - self.interface = cantact.Interface() + if cantact is None: + raise CanInitializationError( + "The CANtact module is not installed. Install it using `python -m pip install cantact`" + ) + with error_check( + "Cannot create the cantact.Interface", CanInitializationError + ): + self.interface = cantact.Interface() self.channel = int(channel) - self.channel_info = "CANtact: ch:%s" % channel - - # configure the interface - if bit_timing is None: - # use bitrate - self.interface.set_bitrate(int(channel), int(bitrate)) - else: - # use custom bit timing - self.interface.set_bit_timing( - int(channel), - int(bit_timing.brp), - int(bit_timing.tseg1), - int(bit_timing.tseg2), - int(bit_timing.sjw), - ) - self.interface.set_enabled(int(channel), True) - self.interface.set_monitor(int(channel), monitor) - self.interface.start() + self.channel_info = f"CANtact: ch:{channel}" + + # Configure the interface + with error_check("Cannot setup the cantact.Interface", CanInitializationError): + if bit_timing is None: + # use bitrate + self.interface.set_bitrate(int(channel), int(bitrate)) + else: + # use custom bit timing + self.interface.set_bit_timing( + int(channel), + int(bit_timing.brp), + int(bit_timing.tseg1), + int(bit_timing.tseg2), + int(bit_timing.sjw), + ) + self.interface.set_enabled(int(channel), True) + self.interface.set_monitor(int(channel), monitor) + self.interface.start() super().__init__( channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs ) def _recv_internal(self, timeout): - frame = self.interface.recv(int(timeout * 1000)) + with error_check("Cannot receive message"): + frame = self.interface.recv(int(timeout * 1000)) if frame is None: - # timeout occured + # timeout occurred return None, False msg = Message( @@ -103,31 +114,33 @@ def _recv_internal(self, timeout): return msg, False def send(self, msg, timeout=None): - self.interface.send( - self.channel, - msg.arbitration_id, - bool(msg.is_extended_id), - bool(msg.is_remote_frame), - msg.dlc, - msg.data, - ) + with error_check("Cannot send message"): + self.interface.send( + self.channel, + msg.arbitration_id, + bool(msg.is_extended_id), + bool(msg.is_remote_frame), + msg.dlc, + msg.data, + ) def shutdown(self): - self.interface.stop() + with error_check("Cannot shutdown interface"): + self.interface.stop() def mock_recv(timeout): if timeout > 0: - frame = {} - frame["id"] = 0x123 - frame["extended"] = False - frame["timestamp"] = time.time() - frame["loopback"] = False - frame["rtr"] = False - frame["dlc"] = 8 - frame["data"] = [1, 2, 3, 4, 5, 6, 7, 8] - frame["channel"] = 0 - return frame + return { + "id": 0x123, + "extended": False, + "timestamp": time.time(), + "loopback": False, + "rtr": False, + "dlc": 8, + "data": [1, 2, 3, 4, 5, 6, 7, 8], + "channel": 0, + } else: # simulate timeout when timeout = 0 return None @@ -144,7 +157,6 @@ class MockInterface: set_bit_timing = Mock() set_enabled = Mock() set_monitor = Mock() - start = Mock() stop = Mock() send = Mock() channel_count = Mock(return_value=1) diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index f3d7ad0ee..8a3ae34ca 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -6,9 +6,9 @@ from ctypes import * from enum import IntEnum import logging -from contextlib import contextmanager import can +from ...exceptions import error_check log = logging.getLogger("can.usb2can") @@ -102,14 +102,6 @@ class CanalMsg(Structure): ] -@contextmanager -def error_check(error_message: str) -> None: - try: - yield - except Exception as error: - raise can.CanOperationError(error_message) from error - - class Usb2CanAbstractionLayer: """A low level wrapper around the usb2can library. From 42d47b6c418db540f10042318b8cc8b55de379b6 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 16:41:41 +0200 Subject: [PATCH 0742/1235] Fix _detect_available_configs --- can/interfaces/cantact.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 45f68d1df..4f26e009f 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -27,8 +27,10 @@ class CantactBus(BusABC): def _detect_available_configs(): try: interface = cantact.Interface() - except (NameError, SystemError): - logger.debug("Could not import cantact, so no configurations are available") + except (NameError, SystemError, AttributeError): + logger.debug( + "Could not import or instantiate cantact, so no configurations are available" + ) return [] channels = [] From 4e8b8deaa575c5ce7718d89b45cef006d5f2b5ea Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:13:00 +0200 Subject: [PATCH 0743/1235] Use CanInterfaceNotImplementedError when the CANtact module is missing --- can/interfaces/cantact.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 4f26e009f..9d36fccb6 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -7,7 +7,7 @@ from unittest.mock import Mock from can import BusABC, Message -from ..exceptions import CanInitializationError, error_check +from ..exceptions import CanInitializationError, CanInterfaceNotImplementedError, error_check logger = logging.getLogger(__name__) @@ -63,7 +63,7 @@ def __init__( self.interface = MockInterface() else: if cantact is None: - raise CanInitializationError( + raise CanInterfaceNotImplementedError( "The CANtact module is not installed. Install it using `python -m pip install cantact`" ) with error_check( From 38889ec126180aab23e2733f45535220ad96bcf7 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 15:14:29 +0000 Subject: [PATCH 0744/1235] Format code with black --- can/interfaces/cantact.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 9d36fccb6..5eab7b3f3 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -7,7 +7,11 @@ from unittest.mock import Mock from can import BusABC, Message -from ..exceptions import CanInitializationError, CanInterfaceNotImplementedError, error_check +from ..exceptions import ( + CanInitializationError, + CanInterfaceNotImplementedError, + error_check, +) logger = logging.getLogger(__name__) From 23046fa939599137f48ed669f4707247ecfeb364 Mon Sep 17 00:00:00 2001 From: Felix Nieuwenhuizen Date: Thu, 11 Nov 2021 18:35:13 +0100 Subject: [PATCH 0745/1235] etas interface: timestamps relative to epoch --- can/interfaces/etas/__init__.py | 23 ++++++++++++++++++++--- can/interfaces/etas/boa.py | 7 +++++++ test/back2back_test.py | 5 +++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 8324bf6ea..9b5060e77 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -1,3 +1,4 @@ +import time from typing import Any, List, Optional, Tuple import can @@ -122,9 +123,23 @@ def __init__( # Common timerCapabilities = OCI_TimerCapabilities() - OCI_GetTimerCapabilities(self.ctrl, ctypes.byref(timerCapabilities)) + ec = OCI_GetTimerCapabilities(self.ctrl, ctypes.byref(timerCapabilities)) + if ec != 0x0: + raise can.exceptions.CanInitializationError( + f"OCI_GetTimerCapabilities failed with error 0x{ec:X}" + ) self.tickFrequency = timerCapabilities.tickFrequency # clock ticks per second + # all timestamps are hardware timestamps relative to powerup + # calculate an offset to make them relative to epoch + now = OCI_Time() + ec = OCI_GetTimerValue(self.ctrl, ctypes.byref(now)) + if ec != 0x0: + raise can.exceptions.CanInitializationError( + f"OCI_GetTimerValue failed with error 0x{ec:X}" + ) + self.timeOffset = time.time() - (float(now.value) / self.tickFrequency) + self.channel_info = channel def _recv_internal( @@ -163,7 +178,8 @@ def _recv_internal( if m.type == OCI_CANFDRX_MESSAGE.value: msg = can.Message( timestamp=float(m.data.canFDRxMessage.timeStamp) - / self.tickFrequency, + / self.tickFrequency + + self.timeOffset, arbitration_id=m.data.canFDRxMessage.frameID, is_extended_id=bool( m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_EXTENDED @@ -187,7 +203,8 @@ def _recv_internal( ) elif m.type == OCI_CAN_RX_MESSAGE.value: msg = can.Message( - timestamp=float(m.data.rxMessage.timeStamp) / self.tickFrequency, + timestamp=float(m.data.rxMessage.timeStamp) / self.tickFrequency + + self.timeOffset, arbitration_id=m.data.rxMessage.frameID, is_extended_id=bool( m.data.rxMessage.flags & OCI_CAN_MSG_FLAG_EXTENDED diff --git a/can/interfaces/etas/boa.py b/can/interfaces/etas/boa.py index 5e55b29c1..116479079 100644 --- a/can/interfaces/etas/boa.py +++ b/can/interfaces/etas/boa.py @@ -231,6 +231,13 @@ class OCI_TimerEventMessage(ctypes.Structure): ] OCI_GetTimerCapabilities.restype = OCI_ErrorCode +OCI_GetTimerValue = _oci.OCI_GetTimerValue +OCI_GetTimerValue.argtypes = [ + OCI_ControllerHandle, + ctypes.POINTER(OCI_Time), +] +OCI_GetTimerValue.restype = OCI_ErrorCode + # OCI CAN # OCI CAN - CAN-FD diff --git a/test/back2back_test.py b/test/back2back_test.py index 64fea8839..479274343 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -337,6 +337,11 @@ class BasicTestEtas(Back2BackTestCase): INTERFACE_2 = "etas" CHANNEL_2 = configs[2]["channel"] + def test_unique_message_instances(self): + self.skipTest( + "creating a second instance of a channel with differing self-reception settings is not supported" + ) + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class SocketCanBroadcastChannel(unittest.TestCase): From 270224cbdb523c65bd66f6a9536c1985b7be2c99 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 11 Nov 2021 21:50:28 +0100 Subject: [PATCH 0746/1235] Fix formatter and bump ci tools (#1167) * Bump CI tool versions * Fix many linter issues * Format code with black --- can/__init__.py | 2 +- can/interfaces/canalystii.py | 2 +- can/interfaces/cantact.py | 2 +- can/interfaces/ics_neovi/neovi_bus.py | 4 +-- can/interfaces/ixxat/canlib.py | 4 +-- can/interfaces/ixxat/exceptions.py | 6 ++-- can/interfaces/kvaser/canlib.py | 6 +--- can/interfaces/neousys/neousys.py | 6 ++-- can/interfaces/nixnet.py | 2 +- can/interfaces/seeedstudio/seeedstudio.py | 2 +- can/interfaces/socketcan/utils.py | 2 +- can/interfaces/systec/structures.py | 2 +- can/interfaces/udp_multicast/bus.py | 9 ++--- can/interfaces/vector/canlib.py | 23 +++++++------ can/interfaces/virtual.py | 2 +- can/io/asc.py | 30 +++++++++-------- can/io/blf.py | 4 +-- can/io/generic.py | 1 + can/io/logger.py | 8 +++-- can/io/sqlite.py | 40 +++++++++-------------- can/listener.py | 2 +- can/message.py | 36 ++++++++++---------- can/notifier.py | 2 +- can/util.py | 2 +- can/viewer.py | 28 ++++++++-------- doc/conf.py | 2 +- examples/serial_com.py | 2 +- requirements-lint.txt | 6 ++-- setup.py | 4 +-- test/test_socketcan_loopback.py | 2 +- 30 files changed, 117 insertions(+), 126 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 544d4fe75..618ef347f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -12,7 +12,7 @@ log = logging.getLogger("can") -rc: Dict[str, Any] = dict() +rc: Dict[str, Any] = {} from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 256ac582c..f75a06c30 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -53,7 +53,7 @@ def __init__( elif isinstance(channel, int): self.channels = [channel] else: # Sequence[int] - self.channels = [c for c in channel] + self.channels = list(channel) self.rx_queue = collections.deque( maxlen=rx_queue_size diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 5eab7b3f3..3c538550c 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -39,7 +39,7 @@ def _detect_available_configs(): channels = [] for i in range(0, interface.channel_count()): - channels.append({"interface": "cantact", "channel": "ch:%d" % i}) + channels.append({"interface": "cantact", "channel": f"ch:{i}"}) return channels def __init__( diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index ec08e80ef..cdbf2a978 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -226,8 +226,8 @@ def channel_to_netid(channel_name_or_id): channel = getattr(ics, netid) else: raise ValueError( - "channel must be an integer or " "a valid ICS channel name" - ) + "channel must be an integer or a valid ICS channel name" + ) from None return channel @staticmethod diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 13709c71c..ced840ff3 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -604,12 +604,12 @@ def _inWaiting(self): return 1 def flush_tx_buffer(self): - """ Flushes the transmit buffer on the IXXAT """ + """Flushes the transmit buffer on the IXXAT""" # TODO #64: no timeout? _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) def _recv_internal(self, timeout): - """ Read a message from IXXAT device. """ + """Read a message from IXXAT device.""" # TODO: handling CAN error messages? data_received = False diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 3bc0e1111..babe08e3b 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -21,15 +21,15 @@ class VCITimeout(CanTimeoutError): - """ Wraps the VCI_E_TIMEOUT error """ + """Wraps the VCI_E_TIMEOUT error""" class VCIError(CanOperationError): - """ Try to display errors that occur within the wrapped C library nicely. """ + """Try to display errors that occur within the wrapped C library nicely.""" class VCIRxQueueEmptyError(VCIError): - """ Wraps the VCI_E_RXQUEUE_EMPTY error """ + """Wraps the VCI_E_RXQUEUE_EMPTY error""" def __init__(self): super().__init__("Receive queue is empty") diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 9d6d6b64a..4886c70fb 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -713,11 +713,7 @@ def get_channel_info(channel): ctypes.sizeof(number), ) - return "%s, S/N %d (#%d)" % ( - name.value.decode("ascii"), - serial.value, - number.value + 1, - ) + return f"{name.value.decode('ascii')}, S/N {serial.value} (#{number.value + 1})" init_kvaser_library() diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index c996c8298..d4a89fd03 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -47,7 +47,7 @@ class NeousysCanSetup(Structure): - """ C CAN Setup struct """ + """C CAN Setup struct""" _fields_ = [ ("bitRate", c_uint), @@ -58,7 +58,7 @@ class NeousysCanSetup(Structure): class NeousysCanMsg(Structure): - """ C CAN Message struct """ + """C CAN Message struct""" _fields_ = [ ("id", c_uint), @@ -75,7 +75,7 @@ class NeousysCanMsg(Structure): # valid:1~4, Resynchronization Jump Width in time quanta # valid:1~1023, CAN_CLK divider used to determine time quanta class NeousysCanBitClk(Structure): - """ C CAN BIT Clock struct """ + """C CAN BIT Clock struct""" _fields_ = [ ("syncPropPhase1Seg", c_ushort), diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index f50daf4c9..a39884017 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -30,7 +30,7 @@ ) except ImportError: logger.error("Error, NIXNET python module cannot be loaded.") - raise ImportError() + raise else: logger.error("NI-XNET interface is only available on Windows systems") raise NotImplementedError("NiXNET is not supported on not Win32 platforms") diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 7cfa6d670..f250e95a5 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -113,7 +113,7 @@ def __init__( "could not create the serial device" ) from error - super(SeeedBus, self).__init__(channel=channel, *args, **kwargs) + super().__init__(channel=channel, *args, **kwargs) self.init_frame() def shutdown(self): diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 1316d153c..b718bb69e 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -11,7 +11,7 @@ from typing import cast, Iterable, Optional from can.interfaces.socketcan.constants import CAN_EFF_FLAG -import can.typechecking as typechecking +from can import typechecking log = logging.getLogger(__name__) diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index fbebdcdbd..841474b80 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -268,7 +268,7 @@ class HardwareInfoEx(Structure): ] def __init__(self): - super(HardwareInfoEx, self).__init__(sizeof(HardwareInfoEx)) + super().__init__(sizeof(HardwareInfoEx)) def __eq__(self, other): if not isinstance(other, HardwareInfoEx): diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 86a78a1ff..df08af880 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -121,13 +121,13 @@ def _recv_internal(self, timeout: Optional[float]): return can_message, False - def send(self, message: can.Message, timeout: Optional[float] = None) -> None: - if not self.is_fd and message.is_fd: + def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: + if not self.is_fd and msg.is_fd: raise can.CanOperationError( "cannot send FD message over bus with CAN FD disabled" ) - data = pack_message(message) + data = pack_message(msg) self._multicast.send(data, timeout) def fileno(self) -> int: @@ -186,8 +186,9 @@ def __init__( sock = self._create_socket(address_family) except OSError as error: log.info( - f"could not connect to the multicast IP network of candidate %s; reason: {error}", + "could not connect to the multicast IP network of candidate %s; reason: %s", connection_candidates, + error, ) if sock is not None: self._socket = sock diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 22d99eda9..e9f299e74 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -38,8 +38,6 @@ ) from can.typechecking import AutoDetectedConfig, CanFilters -from .exceptions import VectorError - # Define Module Logger # ==================== LOG = logging.getLogger(__name__) @@ -140,7 +138,8 @@ def __init__( """ if os.name != "nt" and not kwargs.get("_testing", False): raise CanInterfaceNotImplementedError( - f'The Vector interface is only supported on Windows, but you are running "{os.name}"' + f"The Vector interface is only supported on Windows, " + f'but you are running "{os.name}"' ) if xldriver is None: @@ -163,7 +162,7 @@ def __init__( self._app_name = app_name.encode() if app_name is not None else b"" self.channel_info = "Application %s: %s" % ( app_name, - ", ".join("CAN %d" % (ch + 1) for ch in self.channels), + ", ".join(f"CAN {ch + 1}" for ch in self.channels), ) if serial is not None: @@ -358,8 +357,8 @@ def _apply_filters(self, filters) -> None: if can_filter.get("extended") else xldefine.XL_AcceptanceFilter.XL_CAN_STD, ) - except VectorOperationError as exc: - LOG.warning("Could not set filters: %s", exc) + except VectorOperationError as exception: + LOG.warning("Could not set filters: %s", exception) # go to fallback else: self._is_filtered = True @@ -400,8 +399,8 @@ def _recv_internal( else: msg = self._recv_can() - except VectorOperationError as exc: - if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY: + except VectorOperationError as exception: + if exception.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY: raise else: if msg: @@ -435,7 +434,7 @@ def _recv_canfd(self) -> Optional[Message]: data_struct = xl_can_rx_event.tagData.canTxOkMsg else: self.handle_canfd_event(xl_can_rx_event) - return + return None msg_id = data_struct.canId dlc = dlc2len(data_struct.dlc) @@ -475,7 +474,7 @@ def _recv_can(self) -> Optional[Message]: if xl_event.tag != xldefine.XL_EventTags.XL_RECEIVE_MSG: self.handle_can_event(xl_event) - return + return None msg_id = xl_event.tagData.msg.id dlc = xl_event.tagData.msg.dlc @@ -520,8 +519,8 @@ def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: when `event.tag` is not `XL_CAN_EV_TAG_RX_OK` or `XL_CAN_EV_TAG_TX_OK`. Subclasses can implement this method. - :param event: `XLcanRxEvent` that could have a `XL_CAN_EV_TAG_RX_ERROR`, `XL_CAN_EV_TAG_TX_ERROR`, - `XL_TIMER` or `XL_CAN_EV_TAG_CHIP_STATE` tag. + :param event: `XLcanRxEvent` that could have a `XL_CAN_EV_TAG_RX_ERROR`, + `XL_CAN_EV_TAG_TX_ERROR`, `XL_TIMER` or `XL_CAN_EV_TAG_CHIP_STATE` tag. """ def send(self, msg: Message, timeout: Optional[float] = None): diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index f6d071723..6c73b7ba8 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -67,7 +67,7 @@ def __init__( # the channel identifier may be an arbitrary object self.channel_id = channel - self.channel_info = "Virtual bus channel {}".format(self.channel_id) + self.channel_info = f"Virtual bus channel {self.channel_id}" self.receive_own_messages = receive_own_messages self._open = True diff --git a/can/io/asc.py b/can/io/asc.py index 347ecf3c6..d708de6e7 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -6,7 +6,7 @@ - under `test/data/logfile.asc` """ -from typing import cast, Any, Generator, IO, List, Optional, Union, Dict +from typing import cast, Any, Generator, IO, List, Optional, Dict from datetime import datetime import time @@ -73,8 +73,10 @@ def _extract_header(self): elif lower_case.startswith("base"): try: _, base, _, timestamp_format = line.split() - except ValueError: - raise Exception("Unsupported header string format: {}".format(line)) + except ValueError as exception: + raise Exception( + f"Unsupported header string format: {line}" + ) from exception self.base = base self._converted_base = self._check_base(self.base) self.timestamps_format = timestamp_format @@ -135,8 +137,8 @@ def _process_classic_can_frame( # Error Frame msg_kwargs["is_error_frame"] = True else: - abr_id_str, dir, rest_of_message = line.split(None, 2) - msg_kwargs["is_rx"] = dir == "Rx" + abr_id_str, direction, rest_of_message = line.split(None, 2) + msg_kwargs["is_rx"] = direction == "Rx" self._extract_can_id(abr_id_str, msg_kwargs) if rest_of_message[0].lower() == "r": @@ -164,10 +166,10 @@ def _process_classic_can_frame( return Message(**msg_kwargs) def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Message: - channel, dir, rest_of_message = line.split(None, 2) + channel, direction, rest_of_message = line.split(None, 2) # See ASCWriter msg_kwargs["channel"] = int(channel) - 1 - msg_kwargs["is_rx"] = dir == "Rx" + msg_kwargs["is_rx"] = direction == "Rx" # CAN FD error frame if rest_of_message.strip()[:10].lower() == "errorframe": @@ -291,7 +293,7 @@ def __init__( # write start of file header now = datetime.now().strftime(self.FORMAT_START_OF_FILE_DATE) - self.file.write("date %s\n" % now) + self.file.write(f"date {now}\n") self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") @@ -327,7 +329,7 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: formatted_date = time.strftime( self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp) ) - self.file.write("Begin Triggerblock %s\n" % formatted_date) + self.file.write(f"Begin Triggerblock {formatted_date}\n") self.header_written = True self.log_event("Start of measurement") # caution: this is a recursive call! # Use last known timestamp if unknown @@ -342,15 +344,15 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: def on_message_received(self, msg: Message) -> None: if msg.is_error_frame: - self.log_event("{} ErrorFrame".format(self.channel), msg.timestamp) + self.log_event(f"{self.channel} ErrorFrame", msg.timestamp) return if msg.is_remote_frame: - dtype = "r {:x}".format(msg.dlc) # New after v8.5 + dtype = f"r {msg.dlc:x}" # New after v8.5 data: List[str] = [] else: - dtype = "d {:x}".format(msg.dlc) - data = ["{:02X}".format(byte) for byte in msg.data] - arb_id = "{:X}".format(msg.arbitration_id) + dtype = f"d {msg.dlc:x}" + data = [f"{byte:02X}" for byte in msg.data] + arb_id = f"{msg.arbitration_id:X}" if msg.is_extended_id: arb_id += "x" channel = channel2int(msg.channel) diff --git a/can/io/blf.py b/can/io/blf.py index d53e1296f..9bb54d984 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -228,7 +228,7 @@ def _parse_data(self, data): if pos + 8 > max_pos: # Not enough data in container return - raise BLFParseError("Could not find next object") + raise BLFParseError("Could not find next object") from None header = unpack_obj_header_base(data, pos) # print(header) signature, _, header_version, obj_size, obj_type = header @@ -258,7 +258,7 @@ def _parse_data(self, data): factor = 1e-5 if flags == 1 else 1e-9 timestamp = timestamp * factor + start_timestamp - if obj_type == CAN_MESSAGE or obj_type == CAN_MESSAGE2: + if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): channel, flags, dlc, can_id, can_data = unpack_can_msg(data, pos) yield Message( timestamp=timestamp, diff --git a/can/io/generic.py b/can/io/generic.py index 44caa80c6..1606c444d 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -43,6 +43,7 @@ def __init__( # file is None or some file-like object self.file = cast(Optional[can.typechecking.FileLike], file) else: + # pylint: disable=consider-using-with # file is some path-like object self.file = open(cast(can.typechecking.StringPathLike, file), mode) diff --git a/can/io/logger.py b/can/io/logger.py index ec77d7ac3..1fc2a8487 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -132,7 +132,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.writer_args = args self.writer_kwargs = kwargs - self._writer: FileIOMessageWriter = None # type: ignore # Expected to be set by the subclass + # Expected to be set by the subclass + self._writer: FileIOMessageWriter = None # type: ignore @property def writer(self) -> FileIOMessageWriter: @@ -209,7 +210,8 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: return cast(FileIOMessageWriter, logger) else: raise Exception( - "The Logger corresponding to the arguments is not a FileIOMessageWriter or can.Printer" + "The Logger corresponding to the arguments is not a FileIOMessageWriter or " + "can.Printer" ) def stop(self) -> None: @@ -283,8 +285,8 @@ class SizedRotatingLogger(BaseRotatingLogger): def __init__( self, base_filename: StringPathLike, - max_bytes: int = 0, *args: Any, + max_bytes: int = 0, **kwargs: Any, ) -> None: """ diff --git a/can/io/sqlite.py b/can/io/sqlite.py index c947ab3e3..8d184bce1 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -46,9 +46,7 @@ def __init__(self, file, table_name="messages"): self.table_name = table_name def __iter__(self): - for frame_data in self._cursor.execute( - "SELECT * FROM {}".format(self.table_name) - ): + for frame_data in self._cursor.execute(f"SELECT * FROM {self.table_name}"): yield SqliteReader._assemble_message(frame_data) @staticmethod @@ -66,7 +64,7 @@ def _assemble_message(frame_data): def __len__(self): # this might not run in constant time - result = self._cursor.execute("SELECT COUNT(*) FROM {}".format(self.table_name)) + result = self._cursor.execute(f"SELECT COUNT(*) FROM {self.table_name}") return int(result.fetchone()[0]) def read_all(self): @@ -74,9 +72,7 @@ def read_all(self): :rtype: Generator[can.Message] """ - result = self._cursor.execute( - "SELECT * FROM {}".format(self.table_name) - ).fetchall() + result = self._cursor.execute(f"SELECT * FROM {self.table_name}").fetchall() return (SqliteReader._assemble_message(frame) for frame in result) def stop(self): @@ -164,20 +160,16 @@ def _create_db(self): # create table structure self._conn.cursor().execute( - """ - CREATE TABLE IF NOT EXISTS {} - ( - ts REAL, - arbitration_id INTEGER, - extended INTEGER, - remote INTEGER, - error INTEGER, - dlc INTEGER, - data BLOB - ) - """.format( - self.table_name - ) + f"""CREATE TABLE IF NOT EXISTS {self.table_name} + ( + ts REAL, + arbitration_id INTEGER, + extended INTEGER, + remote INTEGER, + error INTEGER, + dlc INTEGER, + data BLOB + )""" ) self._conn.commit() @@ -209,9 +201,9 @@ def _db_writer_thread(self): or len(messages) > self.MAX_BUFFER_SIZE_BEFORE_WRITES ): break - else: - # just go on - msg = self.get_message(self.GET_MESSAGE_TIMEOUT) + + # just go on + msg = self.get_message(self.GET_MESSAGE_TIMEOUT) count = len(messages) if count > 0: diff --git a/can/listener.py b/can/listener.py index 815c7ecef..8ed4f7b77 100644 --- a/can/listener.py +++ b/can/listener.py @@ -142,7 +142,7 @@ class AsyncBufferedReader(Listener): def __init__(self, **kwargs: Any) -> None: self.buffer: "asyncio.Queue[Message]" - if "loop" in kwargs.keys(): + if "loop" in kwargs: warnings.warn( "The 'loop' argument is deprecated since python-can 4.0.0 " "and has no effect starting with Python 3.10", diff --git a/can/message.py b/can/message.py index 11df695be..29c97a39b 100644 --- a/can/message.py +++ b/can/message.py @@ -109,11 +109,11 @@ def __init__( # pylint: disable=too-many-locals, too-many-arguments self._check() def __str__(self) -> str: - field_strings = ["Timestamp: {0:>15.6f}".format(self.timestamp)] + field_strings = [f"Timestamp: {self.timestamp:>15.6f}"] if self.is_extended_id: - arbitration_id_string = "ID: {0:08x}".format(self.arbitration_id) + arbitration_id_string = f"ID: {self.arbitration_id:08x}" else: - arbitration_id_string = "ID: {0:04x}".format(self.arbitration_id) + arbitration_id_string = f"ID: {self.arbitration_id:04x}" field_strings.append(arbitration_id_string.rjust(12, " ")) flag_string = " ".join( @@ -130,22 +130,22 @@ def __str__(self) -> str: field_strings.append(flag_string) - field_strings.append("DLC: {0:2d}".format(self.dlc)) + field_strings.append("DLC: {self.dlc:2d}") data_strings = [] if self.data is not None: for index in range(0, min(self.dlc, len(self.data))): - data_strings.append("{0:02x}".format(self.data[index])) + data_strings.append(f"{self.data[index]:02x}") if data_strings: # if not empty field_strings.append(" ".join(data_strings).ljust(24, " ")) else: field_strings.append(" " * 24) if (self.data is not None) and (self.data.isalnum()): - field_strings.append("'{}'".format(self.data.decode("utf-8", "replace"))) + field_strings.append(f"'{self.data.decode('utf-8', 'replace')}'") if self.channel is not None: try: - field_strings.append("Channel: {}".format(self.channel)) + field_strings.append(f"Channel: {self.channel}") except UnicodeEncodeError: pass @@ -160,32 +160,32 @@ def __bool__(self) -> bool: def __repr__(self) -> str: args = [ - "timestamp={}".format(self.timestamp), - "arbitration_id={:#x}".format(self.arbitration_id), - "is_extended_id={}".format(self.is_extended_id), + f"timestamp={self.timestamp}", + f"arbitration_id={self.arbitration_id:#x}", + f"is_extended_id={self.is_extended_id}", ] if not self.is_rx: args.append("is_rx=False") if self.is_remote_frame: - args.append("is_remote_frame={}".format(self.is_remote_frame)) + args.append(f"is_remote_frame={self.is_remote_frame}") if self.is_error_frame: - args.append("is_error_frame={}".format(self.is_error_frame)) + args.append(f"is_error_frame={self.is_error_frame}") if self.channel is not None: - args.append("channel={!r}".format(self.channel)) + args.append(f"channel={self.channel!r}") - data = ["{:#02x}".format(byte) for byte in self.data] - args += ["dlc={}".format(self.dlc), "data=[{}]".format(", ".join(data))] + data = [f"{byte:#02x}" for byte in self.data] + args += [f"dlc={self.dlc}", f"data=[{', '.join(data)}]"] if self.is_fd: args.append("is_fd=True") - args.append("bitrate_switch={}".format(self.bitrate_switch)) - args.append("error_state_indicator={}".format(self.error_state_indicator)) + args.append(f"bitrate_switch={self.bitrate_switch}") + args.append(f"error_state_indicator={self.error_state_indicator}") - return "can.Message({})".format(", ".join(args)) + return f"can.Message({', '.join(args)})" def __format__(self, format_spec: Optional[str]) -> str: if not format_spec: diff --git a/can/notifier.py b/can/notifier.py index 3b80c7461..2554fb4fc 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -76,7 +76,7 @@ def add_bus(self, bus: BusABC) -> None: reader_thread = threading.Thread( target=self._rx_thread, args=(bus,), - name='can.notifier for bus "{}"'.format(bus.channel_info), + name=f'can.notifier for bus "{bus.channel_info}"', ) reader_thread.daemon = True reader_thread.start() diff --git a/can/util.py b/can/util.py index 57ccbdf55..e43a4d09d 100644 --- a/can/util.py +++ b/can/util.py @@ -339,7 +339,7 @@ def _rename_kwargs( ) kwargs[new] = value else: - warnings.warn("{} is deprecated".format(alias), DeprecationWarning) + warnings.warn(f"{alias} is deprecated", DeprecationWarning) def time_perfcounter_correlation() -> Tuple[float, float]: diff --git a/can/viewer.py b/can/viewer.py index 7e290bc4e..a92d18cdd 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -167,7 +167,7 @@ def unpack_data(cmd: int, cmd_to_struct: Dict, data: bytes) -> List[float]: return values - raise ValueError("Unknown command: 0x{:02X}".format(cmd)) + raise ValueError(f"Unknown command: 0x{cmd:02X}") def draw_can_bus_message(self, msg, sorting=False): # Use the CAN-Bus ID as the key in the dict @@ -233,12 +233,10 @@ def draw_can_bus_message(self, msg, sorting=False): self.draw_line( self.ids[key]["row"], 8, - "{0:.6f}".format(self.ids[key]["msg"].timestamp - self.start_time), + f"{self.ids[key]['msg'].timestamp - self.start_time:.6f}", color, ) - self.draw_line( - self.ids[key]["row"], 23, "{0:.6f}".format(self.ids[key]["dt"]), color - ) + self.draw_line(self.ids[key]["row"], 23, f"{self.ids[key]['dt']:.6f}", color) self.draw_line(self.ids[key]["row"], 35, arbitration_id_string, color) self.draw_line(self.ids[key]["row"], 47, str(msg.dlc), color) for i, b in enumerate(msg.data): @@ -247,7 +245,7 @@ def draw_can_bus_message(self, msg, sorting=False): # Data does not fit self.draw_line(self.ids[key]["row"], col - 4, "...", color) break - text = "{:02X}".format(b) + text = f"{b:02X}" self.draw_line(self.ids[key]["row"], col, text, color) if self.data_structs: @@ -257,7 +255,7 @@ def draw_can_bus_message(self, msg, sorting=False): msg.arbitration_id, self.data_structs, msg.data ): if isinstance(x, float): - values_list.append("{0:.6f}".format(x)) + values_list.append(f"{x:.6f}") else: values_list.append(str(x)) values_string = " ".join(values_list) @@ -292,8 +290,8 @@ def draw_header(self): def redraw_screen(self): # Trigger a complete redraw self.draw_header() - for key in self.ids: - self.draw_can_bus_message(self.ids[key]["msg"]) + for key, ids in self.ids.items(): + self.draw_can_bus_message(ids["msg"]) class SmartFormatter(argparse.HelpFormatter): @@ -305,12 +303,12 @@ def _format_usage(self, usage, actions, groups, prefix): return super()._format_usage(usage, actions, groups, "Usage: ") def _format_args(self, action, default_metavar): - if action.nargs != argparse.REMAINDER and action.nargs != argparse.ONE_OR_MORE: + if action.nargs not in (argparse.REMAINDER, argparse.ONE_OR_MORE): return super()._format_args(action, default_metavar) # Use the metavar if "REMAINDER" or "ONE_OR_MORE" is set get_metavar = self._metavar_formatter(action, default_metavar) - return "%s" % get_metavar(1) + return str(get_metavar(1)) def _format_action_invocation(self, action): if not action.option_strings or action.nargs == 0: @@ -323,9 +321,9 @@ def _format_action_invocation(self, action): args_string = self._format_args(action, default) for i, option_string in enumerate(action.option_strings): if i == len(action.option_strings) - 1: - parts.append("%s %s" % (option_string, args_string)) + parts.append(f"{option_string} {args_string}") else: - parts.append("%s" % option_string) + parts.append(str(option_string)) return ", ".join(parts) def _split_lines(self, text, width): @@ -371,7 +369,7 @@ def parse_args(args): "--version", action="version", help="Show program's version number and exit", - version="%(prog)s (version {version})".format(version=__version__), + version=f"%(prog)s (version {__version__})", ) # Copied from: can/logger.py @@ -493,7 +491,7 @@ def parse_args(args): ] = {} if parsed_args.decode: if os.path.isfile(parsed_args.decode[0]): - with open(parsed_args.decode[0], "r") as f: + with open(parsed_args.decode[0], "r", encoding="utf-8") as f: structs = f.readlines() else: structs = parsed_args.decode diff --git a/doc/conf.py b/doc/conf.py index 568a5641d..14390d5ad 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -25,7 +25,7 @@ # built documents. # # The short X.Y version. -version = can.__version__.split("-")[0] +version = can.__version__.split("-", maxsplit=1)[0] release = can.__version__ # General information about the project. diff --git a/examples/serial_com.py b/examples/serial_com.py index 60aeec4ce..1fbc997b2 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -42,7 +42,7 @@ def receive(bus, stop_event): while not stop_event.is_set(): rx_msg = bus.recv(1) if rx_msg is not None: - print("rx: {}".format(rx_msg)) + print(f"rx: {rx_msg}") print("Stopped receiving messages") diff --git a/requirements-lint.txt b/requirements-lint.txt index 1c4967679..9aefb7415 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ -pylint==2.7.4 -black==20.8b1 -mypy==0.812 +pylint==2.11.1 +black==21.10b0 +mypy==0.910 mypy-extensions==0.4.3 diff --git a/setup.py b/setup.py index 6e2dcd95a..31318ac06 100644 --- a/setup.py +++ b/setup.py @@ -15,12 +15,12 @@ logging.basicConfig(level=logging.WARNING) -with open("can/__init__.py", "r") as fd: +with open("can/__init__.py", "r", encoding="utf-8") as fd: version = re.search( r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE ).group(1) -with open("README.rst", "r") as f: +with open("README.rst", "r", encoding="utf-8") as f: long_description = f.read() # Dependencies diff --git a/test/test_socketcan_loopback.py b/test/test_socketcan_loopback.py index 9dce11dc5..17b83b268 100644 --- a/test/test_socketcan_loopback.py +++ b/test/test_socketcan_loopback.py @@ -13,7 +13,7 @@ @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class LocalLoopbackSocketCan(unittest.TestCase): - """ test local_loopback functionality""" + """test local_loopback functionality""" BITRATE = 500000 TIMEOUT = 0.1 From 0a2aa84c6b8b09b751d8c6c587714fd913601de5 Mon Sep 17 00:00:00 2001 From: Felix Nieuwenhuizen Date: Sun, 14 Nov 2021 16:21:44 +0100 Subject: [PATCH 0747/1235] incorporate review comments by felixdivo --- can/interfaces/etas/__init__.py | 62 ++++++++++++++++----------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 9b5060e77..c55763491 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -1,14 +1,16 @@ +import ctypes import time -from typing import Any, List, Optional, Tuple +from typing import Dict, List, Optional, Tuple import can +from ...exceptions import CanInitializationError, CanOperationError from .boa import * class EtasBus(can.BusABC): def __init__( self, - channel: Any, + channel: str, can_filters: Optional[can.typechecking.CanFilters] = None, receive_own_messages: bool = False, bitrate: int = 1000000, @@ -24,7 +26,7 @@ def __init__( ctypes.c_char_p(b""), nodeRange, ctypes.byref(self.tree) ) if ec != 0x0: - raise can.exceptions.CanInitializationError( + raise CanInitializationError( f"CSI_CreateProtocolTree failed with error 0x{ec:X}" ) @@ -40,7 +42,7 @@ def __init__( ctypes.byref(self.ctrl), ) if ec != 0x0: - raise can.exceptions.CanInitializationError( + raise CanInitializationError( f"OCI_CreateCANControllerNoSearch failed with error 0x{ec:X}" ) @@ -80,7 +82,7 @@ def __init__( self.ctrl, ctypes.byref(ctrlConf), ctypes.byref(ctrlProp) ) if ec != 0x0 and ec != 0x40004000: # accept BOA_WARN_PARAM_ADAPTED - raise can.exceptions.CanInitializationError( + raise CanInitializationError( f"OCI_OpenCANController failed with error 0x{ec:X}" ) @@ -100,7 +102,7 @@ def __init__( self.ctrl, ctypes.byref(rxQConf), ctypes.byref(self.rxQueue) ) if ec != 0x0: - raise can.exceptions.CanInitializationError( + raise CanInitializationError( f"OCI_CreateCANRxQueue failed with error 0x{ec:X}" ) @@ -116,7 +118,7 @@ def __init__( self.ctrl, ctypes.byref(txQConf), ctypes.byref(self.txQueue) ) if ec != 0x0: - raise can.exceptions.CanInitializationError( + raise CanInitializationError( f"OCI_CreateCANTxQueue failed with error 0x{ec:X}" ) @@ -125,17 +127,17 @@ def __init__( timerCapabilities = OCI_TimerCapabilities() ec = OCI_GetTimerCapabilities(self.ctrl, ctypes.byref(timerCapabilities)) if ec != 0x0: - raise can.exceptions.CanInitializationError( + raise CanInitializationError( f"OCI_GetTimerCapabilities failed with error 0x{ec:X}" ) self.tickFrequency = timerCapabilities.tickFrequency # clock ticks per second - # all timestamps are hardware timestamps relative to powerup + # all timestamps are hardware timestamps relative to the CAN device powerup # calculate an offset to make them relative to epoch now = OCI_Time() ec = OCI_GetTimerValue(self.ctrl, ctypes.byref(now)) if ec != 0x0: - raise can.exceptions.CanInitializationError( + raise CanInitializationError( f"OCI_GetTimerValue failed with error 0x{ec:X}" ) self.timeOffset = time.time() - (float(now.value) / self.tickFrequency) @@ -153,7 +155,7 @@ def _recv_internal( count = ctypes.c_uint32() remaining = ctypes.c_uint32() if timeout is not None: - t = OCI_Time(int(timeout * self.tickFrequency)) + t = OCI_Time(round(timeout * self.tickFrequency)) else: t = OCI_NO_TIME ec = OCI_ReadCANDataEx( @@ -167,9 +169,7 @@ def _recv_internal( if ec != 0x0: text = ctypes.create_string_buffer(500) OCI_GetError(self.ctrl, ec, text, 500) - raise can.exceptions.CanOperationError( - f"OCI_ReadCANDataEx failed with error 0x{ec:X}" - ) + raise CanOperationError(f"OCI_ReadCANDataEx failed with error 0x{ec:X}") msg = None @@ -260,15 +260,13 @@ def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: ec = OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, canMessages, 1, None) if ec != 0x0: - raise can.exceptions.CanOperationError( - f"OCI_WriteCANDataEx failed with error 0x{ec:X}" - ) + raise CanOperationError(f"OCI_WriteCANDataEx failed with error 0x{ec:X}") def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: if self._oci_filters: ec = OCI_RemoveCANFrameFilterEx(self.rxQueue, self._oci_filters, 1) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"OCI_RemoveCANFrameFilterEx failed with error 0x{ec:X}" ) @@ -277,6 +275,7 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None filters = [{"can_id": 0x0, "can_mask": 0x0}] self._oci_filters = (ctypes.POINTER(OCI_CANRxFilterEx) * len(filters))() + for i, filter in enumerate(filters): f = OCI_CANRxFilterEx() f.frameIDValue = filter["can_id"] @@ -298,23 +297,21 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None self.rxQueue, self._oci_filters, len(self._oci_filters) ) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"OCI_AddCANFrameFilterEx failed with error 0x{ec:X}" ) def flush_tx_buffer(self) -> None: ec = OCI_ResetQueue(self.txQueue) if ec != 0x0: - raise can.exceptions.CanOperationError( - f"OCI_ResetQueue failed with error 0x{ec:X}" - ) + raise CanOperationError(f"OCI_ResetQueue failed with error 0x{ec:X}") def shutdown(self) -> None: # Cleanup TX if self.txQueue: ec = OCI_DestroyCANTxQueue(self.txQueue) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"OCI_DestroyCANTxQueue failed with error 0x{ec:X}" ) self.txQueue = None @@ -323,7 +320,7 @@ def shutdown(self) -> None: if self.rxQueue: ec = OCI_DestroyCANRxQueue(self.rxQueue) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"OCI_DestroyCANRxQueue failed with error 0x{ec:X}" ) self.rxQueue = None @@ -332,12 +329,12 @@ def shutdown(self) -> None: if self.ctrl: ec = OCI_CloseCANController(self.ctrl) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"OCI_CloseCANController failed with error 0x{ec:X}" ) ec = OCI_DestroyCANController(self.ctrl) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"OCI_DestroyCANController failed with error 0x{ec:X}" ) self.ctrl = None @@ -345,7 +342,7 @@ def shutdown(self) -> None: if self.tree: ec = CSI_DestroyProtocolTree(self.tree) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"CSI_DestroyProtocolTree failed with error 0x{ec:X}" ) self.tree = None @@ -355,7 +352,7 @@ def state(self) -> can.BusState: status = OCI_CANControllerStatus() ec = OCI_GetCANControllerStatus(self.ctrl, ctypes.byref(status)) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"OCI_GetCANControllerStatus failed with error 0x{ec:X}" ) if status.stateCode & OCI_CAN_STATE_ACTIVE: @@ -365,13 +362,14 @@ def state(self) -> can.BusState: @state.setter def state(self, new_state: can.BusState) -> None: + # disabled, OCI_AdaptCANConfiguration does not allow changing the bus mode # if new_state == can.BusState.ACTIVE: # self.ctrlConf.busParticipationMode = OCI_BUSMODE_ACTIVE # else: # self.ctrlConf.busParticipationMode = OCI_BUSMODE_PASSIVE # ec = OCI_AdaptCANConfiguration(self.ctrl, ctypes.byref(self.ctrlConf)) # if ec != 0x0: - # raise can.exceptions.CanOperationError(f"OCI_AdaptCANConfiguration failed with error 0x{ec:X}") + # raise CanOperationError(f"OCI_AdaptCANConfiguration failed with error 0x{ec:X}") raise NotImplementedError("Setting state is not implemented.") def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: @@ -379,11 +377,11 @@ def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: tree = ctypes.POINTER(CSI_Tree)() ec = CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(tree)) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"CSI_CreateProtocolTree failed with error 0x{ec:X}" ) - nodes = [] + nodes: Dict[str, str] = [] def _findNodes(tree, prefix): uri = f"{prefix}/{tree.contents.item.uriName.decode()}" @@ -402,7 +400,7 @@ def _findNodes(tree, prefix): ec = CSI_DestroyProtocolTree(tree) if ec != 0x0: - raise can.exceptions.CanOperationError( + raise CanOperationError( f"CSI_DestroyProtocolTree failed with error 0x{ec:X}" ) From 6afeb8bdf4e864ca6b26160d7c051435ab6d0d4e Mon Sep 17 00:00:00 2001 From: Felix Nieuwenhuizen Date: Sun, 14 Nov 2021 17:18:48 +0100 Subject: [PATCH 0748/1235] refactor send and recv methods --- can/interfaces/etas/__init__.py | 113 ++++++++++++++------------------ 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index c55763491..847e2724b 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -147,24 +147,22 @@ def __init__( def _recv_internal( self, timeout: Optional[float] ) -> Tuple[Optional[can.Message], bool]: - canMessages = (ctypes.POINTER(OCI_CANMessageEx) * 1)() - - m = OCI_CANMessageEx() - canMessages[0].contents = m + ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)() + ociMsg = OCI_CANMessageEx() + ociMsgs[0] = ctypes.pointer(ociMsg) count = ctypes.c_uint32() - remaining = ctypes.c_uint32() - if timeout is not None: + if timeout is not None: # wait for specified time t = OCI_Time(round(timeout * self.tickFrequency)) - else: + else: # wait indefinitely t = OCI_NO_TIME ec = OCI_ReadCANDataEx( self.rxQueue, t, - canMessages, + ociMsgs, 1, ctypes.byref(count), - ctypes.byref(remaining), + None, ) if ec != 0x0: text = ctypes.create_string_buffer(500) @@ -174,52 +172,44 @@ def _recv_internal( msg = None if count.value != 0: - m = canMessages[0].contents - if m.type == OCI_CANFDRX_MESSAGE.value: + if ociMsg.type == OCI_CANFDRX_MESSAGE.value: + ociRxMsg = ociMsg.data.canFDRxMessage msg = can.Message( - timestamp=float(m.data.canFDRxMessage.timeStamp) - / self.tickFrequency + timestamp=float(ociRxMsg.timeStamp) / self.tickFrequency + self.timeOffset, - arbitration_id=m.data.canFDRxMessage.frameID, - is_extended_id=bool( - m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_EXTENDED - ), + arbitration_id=ociRxMsg.frameID, + is_extended_id=bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_EXTENDED), is_remote_frame=bool( - m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME + ociRxMsg.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME ), # is_error_frame=False, # channel=None, - dlc=m.data.canFDRxMessage.size, - data=m.data.canFDRxMessage.data[0 : m.data.canFDRxMessage.size], + dlc=ociRxMsg.size, + data=ociRxMsg.data[0 : ociRxMsg.size], is_fd=True, - is_rx=not bool( - m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION - ), + is_rx=not bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION), bitrate_switch=bool( - m.data.canFDRxMessage.flags & OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE + ociRxMsg.flags & OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE ), # error_state_indicator=False, # check=False, ) - elif m.type == OCI_CAN_RX_MESSAGE.value: + elif ociMsg.type == OCI_CAN_RX_MESSAGE.value: + ociRxMsg = ociMsg.data.rxMessage msg = can.Message( - timestamp=float(m.data.rxMessage.timeStamp) / self.tickFrequency + timestamp=float(ociRxMsg.timeStamp) / self.tickFrequency + self.timeOffset, - arbitration_id=m.data.rxMessage.frameID, - is_extended_id=bool( - m.data.rxMessage.flags & OCI_CAN_MSG_FLAG_EXTENDED - ), + arbitration_id=ociRxMsg.frameID, + is_extended_id=bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_EXTENDED), is_remote_frame=bool( - m.data.rxMessage.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME + ociRxMsg.flags & OCI_CAN_MSG_FLAG_REMOTE_FRAME ), # is_error_frame=False, # channel=None, - dlc=m.data.rxMessage.dlc, - data=m.data.rxMessage.data[0 : m.data.rxMessage.dlc], + dlc=ociRxMsg.dlc, + data=ociRxMsg.data[0 : ociRxMsg.dlc], # is_fd=False, - is_rx=not bool( - m.data.rxMessage.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION - ), + is_rx=not bool(ociRxMsg.flags & OCI_CAN_MSG_FLAG_SELFRECEPTION), # bitrate_switch=False, # error_state_indicator=False, # check=False, @@ -228,37 +218,34 @@ def _recv_internal( return (msg, True) def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: - canMessages = (ctypes.POINTER(OCI_CANMessageEx) * 1)() + ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)() + ociMsg = OCI_CANMessageEx() + ociMsgs[0] = ctypes.pointer(ociMsg) - m = OCI_CANMessageEx() + if msg.is_fd: + ociMsg.type = OCI_CANFDTX_MESSAGE + ociTxMsg = ociMsg.data.canFDTxMessage + ociTxMsg.size = msg.dlc + else: + ociMsg.type = OCI_CAN_TX_MESSAGE + ociTxMsg = ociMsg.data.txMessage + ociTxMsg.dlc = msg.dlc + + # set fields common to CAN / CAN-FD + ociTxMsg.frameID = msg.arbitration_id + ociTxMsg.flags = 0 + if msg.is_extended_id: + ociTxMsg.flags |= OCI_CAN_MSG_FLAG_EXTENDED + if msg.is_remote_frame: + ociTxMsg.flags |= OCI_CAN_MSG_FLAG_REMOTE_FRAME + ociTxMsg.data = tuple(msg.data) if msg.is_fd: - m.type = OCI_CANFDTX_MESSAGE - m.data.canFDTxMessage.frameID = msg.arbitration_id - m.data.canFDTxMessage.flags = 0 - if msg.is_extended_id: - m.data.canFDTxMessage.flags |= OCI_CAN_MSG_FLAG_EXTENDED - if msg.is_remote_frame: - m.data.canFDTxMessage.flags |= OCI_CAN_MSG_FLAG_REMOTE_FRAME - m.data.canFDTxMessage.flags |= OCI_CAN_MSG_FLAG_FD_DATA + ociTxMsg.flags |= OCI_CAN_MSG_FLAG_FD_DATA if msg.bitrate_switch: - m.data.canFDTxMessage.flags |= OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE - m.data.canFDTxMessage.size = msg.dlc - m.data.canFDTxMessage.data = tuple(msg.data) - else: - m.type = OCI_CAN_TX_MESSAGE - m.data.txMessage.frameID = msg.arbitration_id - m.data.txMessage.flags = 0 - if msg.is_extended_id: - m.data.txMessage.flags |= OCI_CAN_MSG_FLAG_EXTENDED - if msg.is_remote_frame: - m.data.txMessage.flags |= OCI_CAN_MSG_FLAG_REMOTE_FRAME - m.data.txMessage.dlc = msg.dlc - m.data.txMessage.data = tuple(msg.data) - - canMessages[0].contents = m - - ec = OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, canMessages, 1, None) + ociTxMsg.flags |= OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE + + ec = OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, ociMsgs, 1, None) if ec != 0x0: raise CanOperationError(f"OCI_WriteCANDataEx failed with error 0x{ec:X}") From 74140378fa3be6eaafdcacc8877872158fad28d1 Mon Sep 17 00:00:00 2001 From: Felix Nieuwenhuizen Date: Sun, 14 Nov 2021 18:59:53 +0100 Subject: [PATCH 0749/1235] use ctypes.errcheck for error checking --- can/interfaces/etas/__init__.py | 118 ++++++-------------------------- can/interfaces/etas/boa.py | 62 ++++++++++++----- 2 files changed, 64 insertions(+), 116 deletions(-) diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 847e2724b..3a203a50d 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -3,7 +3,7 @@ from typing import Dict, List, Optional, Tuple import can -from ...exceptions import CanInitializationError, CanOperationError +from ...exceptions import CanInitializationError from .boa import * @@ -22,29 +22,17 @@ def __init__( nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX) self.tree = ctypes.POINTER(CSI_Tree)() - ec = CSI_CreateProtocolTree( - ctypes.c_char_p(b""), nodeRange, ctypes.byref(self.tree) - ) - if ec != 0x0: - raise CanInitializationError( - f"CSI_CreateProtocolTree failed with error 0x{ec:X}" - ) + CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(self.tree)) oci_can_v = BOA_Version(1, 4, 0, 0) - # Common - self.ctrl = OCI_ControllerHandle() - ec = OCI_CreateCANControllerNoSearch( + OCI_CreateCANControllerNoSearch( channel.encode(), ctypes.byref(oci_can_v), self.tree, ctypes.byref(self.ctrl), ) - if ec != 0x0: - raise CanInitializationError( - f"OCI_CreateCANControllerNoSearch failed with error 0x{ec:X}" - ) ctrlConf = OCI_CANConfiguration() ctrlConf.baudrate = bitrate @@ -98,13 +86,9 @@ def __init__( else: rxQConf.selfReceptionMode = OCI_SELF_RECEPTION_OFF self.rxQueue = OCI_QueueHandle() - ec = OCI_CreateCANRxQueue( + OCI_CreateCANRxQueue( self.ctrl, ctypes.byref(rxQConf), ctypes.byref(self.rxQueue) ) - if ec != 0x0: - raise CanInitializationError( - f"OCI_CreateCANRxQueue failed with error 0x{ec:X}" - ) self._oci_filters = None self.filters = can_filters @@ -114,32 +98,20 @@ def __init__( txQConf = OCI_CANTxQueueConfiguration() txQConf.reserved = 0 self.txQueue = OCI_QueueHandle() - ec = OCI_CreateCANTxQueue( + OCI_CreateCANTxQueue( self.ctrl, ctypes.byref(txQConf), ctypes.byref(self.txQueue) ) - if ec != 0x0: - raise CanInitializationError( - f"OCI_CreateCANTxQueue failed with error 0x{ec:X}" - ) # Common timerCapabilities = OCI_TimerCapabilities() - ec = OCI_GetTimerCapabilities(self.ctrl, ctypes.byref(timerCapabilities)) - if ec != 0x0: - raise CanInitializationError( - f"OCI_GetTimerCapabilities failed with error 0x{ec:X}" - ) + OCI_GetTimerCapabilities(self.ctrl, ctypes.byref(timerCapabilities)) self.tickFrequency = timerCapabilities.tickFrequency # clock ticks per second # all timestamps are hardware timestamps relative to the CAN device powerup # calculate an offset to make them relative to epoch now = OCI_Time() - ec = OCI_GetTimerValue(self.ctrl, ctypes.byref(now)) - if ec != 0x0: - raise CanInitializationError( - f"OCI_GetTimerValue failed with error 0x{ec:X}" - ) + OCI_GetTimerValue(self.ctrl, ctypes.byref(now)) self.timeOffset = time.time() - (float(now.value) / self.tickFrequency) self.channel_info = channel @@ -156,7 +128,7 @@ def _recv_internal( t = OCI_Time(round(timeout * self.tickFrequency)) else: # wait indefinitely t = OCI_NO_TIME - ec = OCI_ReadCANDataEx( + OCI_ReadCANDataEx( self.rxQueue, t, ociMsgs, @@ -164,10 +136,6 @@ def _recv_internal( ctypes.byref(count), None, ) - if ec != 0x0: - text = ctypes.create_string_buffer(500) - OCI_GetError(self.ctrl, ec, text, 500) - raise CanOperationError(f"OCI_ReadCANDataEx failed with error 0x{ec:X}") msg = None @@ -245,17 +213,11 @@ def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: if msg.bitrate_switch: ociTxMsg.flags |= OCI_CAN_MSG_FLAG_FD_DATA_BIT_RATE - ec = OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, ociMsgs, 1, None) - if ec != 0x0: - raise CanOperationError(f"OCI_WriteCANDataEx failed with error 0x{ec:X}") + OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, ociMsgs, 1, None) def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: if self._oci_filters: - ec = OCI_RemoveCANFrameFilterEx(self.rxQueue, self._oci_filters, 1) - if ec != 0x0: - raise CanOperationError( - f"OCI_RemoveCANFrameFilterEx failed with error 0x{ec:X}" - ) + OCI_RemoveCANFrameFilterEx(self.rxQueue, self._oci_filters, 1) # "accept all" filter if filters is None: @@ -280,68 +242,36 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None f.flagsMask |= OCI_CAN_MSG_FLAG_EXTENDED self._oci_filters[i].contents = f - ec = OCI_AddCANFrameFilterEx( - self.rxQueue, self._oci_filters, len(self._oci_filters) - ) - if ec != 0x0: - raise CanOperationError( - f"OCI_AddCANFrameFilterEx failed with error 0x{ec:X}" - ) + OCI_AddCANFrameFilterEx(self.rxQueue, self._oci_filters, len(self._oci_filters)) def flush_tx_buffer(self) -> None: - ec = OCI_ResetQueue(self.txQueue) - if ec != 0x0: - raise CanOperationError(f"OCI_ResetQueue failed with error 0x{ec:X}") + OCI_ResetQueue(self.txQueue) def shutdown(self) -> None: # Cleanup TX if self.txQueue: - ec = OCI_DestroyCANTxQueue(self.txQueue) - if ec != 0x0: - raise CanOperationError( - f"OCI_DestroyCANTxQueue failed with error 0x{ec:X}" - ) + OCI_DestroyCANTxQueue(self.txQueue) self.txQueue = None # Cleanup RX if self.rxQueue: - ec = OCI_DestroyCANRxQueue(self.rxQueue) - if ec != 0x0: - raise CanOperationError( - f"OCI_DestroyCANRxQueue failed with error 0x{ec:X}" - ) + OCI_DestroyCANRxQueue(self.rxQueue) self.rxQueue = None # Cleanup common if self.ctrl: - ec = OCI_CloseCANController(self.ctrl) - if ec != 0x0: - raise CanOperationError( - f"OCI_CloseCANController failed with error 0x{ec:X}" - ) - ec = OCI_DestroyCANController(self.ctrl) - if ec != 0x0: - raise CanOperationError( - f"OCI_DestroyCANController failed with error 0x{ec:X}" - ) + OCI_CloseCANController(self.ctrl) + OCI_DestroyCANController(self.ctrl) self.ctrl = None if self.tree: - ec = CSI_DestroyProtocolTree(self.tree) - if ec != 0x0: - raise CanOperationError( - f"CSI_DestroyProtocolTree failed with error 0x{ec:X}" - ) + CSI_DestroyProtocolTree(self.tree) self.tree = None @property def state(self) -> can.BusState: status = OCI_CANControllerStatus() - ec = OCI_GetCANControllerStatus(self.ctrl, ctypes.byref(status)) - if ec != 0x0: - raise CanOperationError( - f"OCI_GetCANControllerStatus failed with error 0x{ec:X}" - ) + OCI_GetCANControllerStatus(self.ctrl, ctypes.byref(status)) if status.stateCode & OCI_CAN_STATE_ACTIVE: return can.BusState.ACTIVE elif status.stateCode & OCI_CAN_STATE_PASSIVE: @@ -362,11 +292,7 @@ def state(self, new_state: can.BusState) -> None: def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX) tree = ctypes.POINTER(CSI_Tree)() - ec = CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(tree)) - if ec != 0x0: - raise CanOperationError( - f"CSI_CreateProtocolTree failed with error 0x{ec:X}" - ) + CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(tree)) nodes: Dict[str, str] = [] @@ -385,10 +311,6 @@ def _findNodes(tree, prefix): _findNodes(tree, "ETAS:/") - ec = CSI_DestroyProtocolTree(tree) - if ec != 0x0: - raise CanOperationError( - f"CSI_DestroyProtocolTree failed with error 0x{ec:X}" - ) + CSI_DestroyProtocolTree(tree) return nodes diff --git a/can/interfaces/etas/boa.py b/can/interfaces/etas/boa.py index 116479079..3efb20f6c 100644 --- a/can/interfaces/etas/boa.py +++ b/can/interfaces/etas/boa.py @@ -1,5 +1,7 @@ import ctypes +from ...exceptions import CanInitializationError, CanOperationError + try: # try to load libraries from the system default paths _csi = ctypes.windll.LoadLibrary("dll-csiBind") @@ -15,6 +17,26 @@ _csi = ctypes.windll.LoadLibrary(path + "dll-csiBind") _oci = ctypes.windll.LoadLibrary(path + "dll-ocdProxy") + +# define helper functions to use with errcheck + + +def errcheck_init(result, func, _arguments): + # unfortunately, we can't use OCI_GetError here + # because we don't always have a handle to use + # text = ctypes.create_string_buffer(500) + # OCI_GetError(self.ctrl, ec, text, 500) + if result != 0x0: + raise CanInitializationError(f"{func.__name__} failed with error 0x{result:X}") + return result + + +def errcheck_oper(result, func, _arguments): + if result != 0x0: + raise CanOperationError(f"{func.__name__} failed with error 0x{result:X}") + return result + + # Common (BOA) BOA_ResultCode = ctypes.c_uint32 @@ -103,12 +125,14 @@ class CSI_Tree(ctypes.Structure): ctypes.POINTER(ctypes.POINTER(CSI_Tree)), ] CSI_CreateProtocolTree.restype = BOA_ResultCode +CSI_CreateProtocolTree.errcheck = errcheck_init CSI_DestroyProtocolTree = _csi.CSI_DestroyProtocolTree CSI_DestroyProtocolTree.argtypes = [ ctypes.POINTER(CSI_Tree), ] CSI_DestroyProtocolTree.restype = BOA_ResultCode +CSI_DestroyProtocolTree.errcheck = errcheck_oper # Open Controller Interface (OCI) @@ -158,6 +182,7 @@ class OCI_InternalErrorEventMessage(ctypes.Structure): ctypes.c_uint32, ] OCI_GetError.restype = OCI_ErrorCode +OCI_GetError.errcheck = errcheck_oper # OCI Common - Queue Handling @@ -185,6 +210,7 @@ class OCI_QueueEventMessage(ctypes.Structure): OCI_ResetQueue = _oci.OCI_ResetQueue OCI_ResetQueue.argtypes = [OCI_QueueHandle] OCI_ResetQueue.restype = OCI_ErrorCode +OCI_ResetQueue.errcheck = errcheck_oper # OCI Common - Timer Handling @@ -230,6 +256,7 @@ class OCI_TimerEventMessage(ctypes.Structure): ctypes.POINTER(OCI_TimerCapabilities), ] OCI_GetTimerCapabilities.restype = OCI_ErrorCode +OCI_GetTimerCapabilities.errcheck = errcheck_init OCI_GetTimerValue = _oci.OCI_GetTimerValue OCI_GetTimerValue.argtypes = [ @@ -237,6 +264,7 @@ class OCI_TimerEventMessage(ctypes.Structure): ctypes.POINTER(OCI_Time), ] OCI_GetTimerValue.restype = OCI_ErrorCode +OCI_GetTimerValue.errcheck = errcheck_oper # OCI CAN @@ -384,6 +412,7 @@ class OCI_CANControllerStatus(ctypes.Structure): ctypes.POINTER(OCI_ControllerHandle), ] OCI_CreateCANControllerNoSearch.restype = OCI_ErrorCode +OCI_CreateCANControllerNoSearch.errcheck = errcheck_init OCI_OpenCANController = _oci.OCI_OpenCANController OCI_OpenCANController.argtypes = [ @@ -392,14 +421,18 @@ class OCI_CANControllerStatus(ctypes.Structure): ctypes.POINTER(OCI_CANControllerProperties), ] OCI_OpenCANController.restype = OCI_ErrorCode +# no .errcheck, since we tolerate OCI_WARN_PARAM_ADAPTED warning +# OCI_OpenCANController.errcheck = errcheck_init OCI_CloseCANController = _oci.OCI_CloseCANController OCI_CloseCANController.argtypes = [OCI_ControllerHandle] OCI_CloseCANController.restype = OCI_ErrorCode +OCI_CloseCANController.errcheck = errcheck_oper OCI_DestroyCANController = _oci.OCI_DestroyCANController OCI_DestroyCANController.argtypes = [OCI_ControllerHandle] OCI_DestroyCANController.restype = OCI_ErrorCode +OCI_DestroyCANController.errcheck = errcheck_oper OCI_AdaptCANConfiguration = _oci.OCI_AdaptCANConfiguration OCI_AdaptCANConfiguration.argtypes = [ @@ -407,6 +440,7 @@ class OCI_CANControllerStatus(ctypes.Structure): ctypes.POINTER(OCI_CANConfiguration), ] OCI_AdaptCANConfiguration.restype = OCI_ErrorCode +OCI_AdaptCANConfiguration.errcheck = errcheck_oper OCI_GetCANControllerCapabilities = _oci.OCI_GetCANControllerCapabilities OCI_GetCANControllerCapabilities.argtypes = [ @@ -414,6 +448,7 @@ class OCI_CANControllerStatus(ctypes.Structure): ctypes.POINTER(OCI_CANControllerCapabilities), ] OCI_GetCANControllerCapabilities.restype = OCI_ErrorCode +OCI_GetCANControllerCapabilities.errcheck = errcheck_init OCI_GetCANControllerStatus = _oci.OCI_GetCANControllerStatus OCI_GetCANControllerStatus.argtypes = [ @@ -421,6 +456,7 @@ class OCI_CANControllerStatus(ctypes.Structure): ctypes.POINTER(OCI_CANControllerStatus), ] OCI_GetCANControllerStatus.restype = OCI_ErrorCode +OCI_GetCANControllerStatus.errcheck = errcheck_oper # OCI CAN - Filter @@ -444,14 +480,6 @@ class OCI_CANRxFilterEx(ctypes.Structure): ] -OCI_AddCANFrameFilter = _oci.OCI_AddCANFrameFilter -OCI_AddCANFrameFilter.argtypes = [ - OCI_QueueHandle, - ctypes.POINTER(OCI_CANRxFilter), - ctypes.c_uint32, -] -OCI_AddCANFrameFilter.restype = OCI_ErrorCode - OCI_AddCANFrameFilterEx = _oci.OCI_AddCANFrameFilterEx OCI_AddCANFrameFilterEx.argtypes = [ OCI_QueueHandle, @@ -459,6 +487,7 @@ class OCI_CANRxFilterEx(ctypes.Structure): ctypes.c_uint32, ] OCI_AddCANFrameFilterEx.restype = OCI_ErrorCode +OCI_AddCANFrameFilterEx.errcheck = errcheck_oper OCI_RemoveCANFrameFilterEx = _oci.OCI_RemoveCANFrameFilterEx OCI_RemoveCANFrameFilterEx.argtypes = [ @@ -467,6 +496,7 @@ class OCI_CANRxFilterEx(ctypes.Structure): ctypes.c_uint32, ] OCI_RemoveCANFrameFilterEx.restype = OCI_ErrorCode +OCI_RemoveCANFrameFilterEx.errcheck = errcheck_oper # OCI CAN - Messages @@ -623,10 +653,12 @@ class OCI_CANTxQueueConfiguration(ctypes.Structure): ctypes.POINTER(OCI_QueueHandle), ] OCI_CreateCANRxQueue.restype = OCI_ErrorCode +OCI_CreateCANRxQueue.errcheck = errcheck_init OCI_DestroyCANRxQueue = _oci.OCI_DestroyCANRxQueue OCI_DestroyCANRxQueue.argtypes = [OCI_QueueHandle] OCI_DestroyCANRxQueue.restype = OCI_ErrorCode +OCI_DestroyCANRxQueue.errcheck = errcheck_oper OCI_CreateCANTxQueue = _oci.OCI_CreateCANTxQueue OCI_CreateCANTxQueue.argtypes = [ @@ -635,20 +667,12 @@ class OCI_CANTxQueueConfiguration(ctypes.Structure): ctypes.POINTER(OCI_QueueHandle), ] OCI_CreateCANTxQueue.restype = OCI_ErrorCode +OCI_CreateCANTxQueue.errcheck = errcheck_init OCI_DestroyCANTxQueue = _oci.OCI_DestroyCANTxQueue OCI_DestroyCANTxQueue.argtypes = [OCI_QueueHandle] OCI_DestroyCANTxQueue.restype = OCI_ErrorCode - -OCI_WriteCANData = _oci.OCI_WriteCANData -OCI_WriteCANData.argtypes = [ - OCI_QueueHandle, - OCI_Time, - ctypes.POINTER(OCI_CANMessage), - ctypes.c_uint32, - ctypes.POINTER(ctypes.c_uint32), -] -OCI_WriteCANData.restype = OCI_ErrorCode +OCI_DestroyCANTxQueue.errcheck = errcheck_oper OCI_WriteCANDataEx = _oci.OCI_WriteCANDataEx OCI_WriteCANDataEx.argtypes = [ @@ -659,6 +683,7 @@ class OCI_CANTxQueueConfiguration(ctypes.Structure): ctypes.POINTER(ctypes.c_uint32), ] OCI_WriteCANDataEx.restype = OCI_ErrorCode +OCI_WriteCANDataEx.errcheck = errcheck_oper OCI_ReadCANDataEx = _oci.OCI_ReadCANDataEx OCI_ReadCANDataEx.argtypes = [ @@ -670,3 +695,4 @@ class OCI_CANTxQueueConfiguration(ctypes.Structure): ctypes.POINTER(ctypes.c_uint32), ] OCI_ReadCANDataEx.restype = OCI_ErrorCode +OCI_ReadCANDataEx.errcheck = errcheck_oper From 4ac9008be62cf94f47f53fb506ef33d111142693 Mon Sep 17 00:00:00 2001 From: MattWoodhead Date: Tue, 16 Nov 2021 03:55:27 +0000 Subject: [PATCH 0750/1235] Add changed byte highlighting to viewer.py (#1159) * Added optional byte highlighting to viewer Added optional byte highlighting that can be toggled using the shortcut key 'h' * Some black formatting changes * Final black formatting * Fixed viewer bug Fix for the following error since the introduction of #1151 / #1142 Traceback (most recent call last): File "/usr/lib/python3.7/runpy.py", line 193, in _run_module_as_main "__main__", mod_spec) File "/usr/lib/python3.7/runpy.py", line 85, in _run_code exec(code, run_globals) File "/home/pi/python-can/can/viewer.py", line 582, in main() File "/home/pi/python-can/can/viewer.py", line 573, in main bus = _create_bus(parsed_args, **additional_config, app_name="python-can viewer") File "/home/pi/python-can/can/logger.py", line 105, in _create_bus if parsed_args.app_name: AttributeError: 'Namespace' object has no attribute 'app_name' * Update scripts.rst update viewer documentations * Update scripts.rst * Update scripts.rst arrange keyboard shortcuts into bulleted list * Update scripts.rst * Update can/viewer.py Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> * Better description. Add extra length to other lines in the help text so the border lines up. * Update test_viewer.py Add test coverage for some of the new byte highlighting functions. * Use _create_base_argument_parser in viewer.py Change standard arguments definition to be defined using logger._create_base_argument_parser instead of manually defining them in this module. Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/viewer.py | 110 ++++++++++-------- .../viewer_changed_bytes_highlighting.png | Bin 0 -> 20391 bytes doc/scripts.rst | 16 ++- test/test_viewer.py | 7 +- 4 files changed, 85 insertions(+), 48 deletions(-) create mode 100644 doc/images/viewer_changed_bytes_highlighting.png diff --git a/can/viewer.py b/can/viewer.py index a92d18cdd..8fd3fc294 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -31,7 +31,12 @@ import can from can import __version__ -from .logger import _create_bus, _parse_filters, _append_filter_argument +from .logger import ( + _create_bus, + _parse_filters, + _append_filter_argument, + _create_base_argument_parser, +) logger = logging.getLogger("can.serial") @@ -53,11 +58,14 @@ def __init__(self, stdscr, bus, data_structs, testing=False): self.bus = bus self.data_structs = data_structs - # Initialise the ID dictionary, start timestamp, scroll and variable for pausing the viewer + # Initialise the ID dictionary, Previous values dict, start timestamp, + # scroll and variables for pausing the viewer and enabling byte highlighting self.ids = {} self.start_time = None self.scroll = 0 self.paused = False + self.highlight_changed_bytes = False + self.previous_values = {} # Get the window dimensions - used for resizing the window self.y, self.x = self.stdscr.getmaxyx() @@ -70,6 +78,8 @@ def __init__(self, stdscr, bus, data_structs, testing=False): # Used to color error frames red curses.init_pair(1, curses.COLOR_RED, -1) + # Used to color changed bytes + curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLUE) if not testing: # pragma: no cover self.run() @@ -103,6 +113,14 @@ def run(self): self.scroll = 0 self.draw_header() + # Toggle byte change highlighting pressing 'h' + elif key == ord("h"): + self.highlight_changed_bytes = not self.highlight_changed_bytes + if not self.highlight_changed_bytes: + # empty the previous values dict when leaving higlighting mode + self.previous_values.clear() + self.draw_header() + # Sort by pressing 's' elif key == ord("s"): # Sort frames based on the CAN-Bus ID @@ -239,14 +257,36 @@ def draw_can_bus_message(self, msg, sorting=False): self.draw_line(self.ids[key]["row"], 23, f"{self.ids[key]['dt']:.6f}", color) self.draw_line(self.ids[key]["row"], 35, arbitration_id_string, color) self.draw_line(self.ids[key]["row"], 47, str(msg.dlc), color) + + try: + previous_byte_values = self.previous_values[key] + except KeyError: # no row of previous values exists for the current message ID + # initialise a row to store the values for comparison next time + self.previous_values[key] = dict() + previous_byte_values = self.previous_values[key] for i, b in enumerate(msg.data): col = 52 + i * 3 if col > self.x - 2: # Data does not fit self.draw_line(self.ids[key]["row"], col - 4, "...", color) break + if self.highlight_changed_bytes: + try: + if b != previous_byte_values[i]: + # set colour to highlight a changed value + data_color = curses.color_pair(2) + else: + data_color = color + except KeyError: + # previous entry for byte didnt exist - default to rest of line colour + data_color = color + finally: + # write the new value to the previous values dict for next time + previous_byte_values[i] = b + else: + data_color = color text = f"{b:02X}" - self.draw_line(self.ids[key]["row"], col, text, color) + self.draw_line(self.ids[key]["row"], col, text, data_color) if self.data_structs: try: @@ -284,7 +324,12 @@ def draw_header(self): self.draw_line(0, 35, "ID", curses.A_BOLD) self.draw_line(0, 47, "DLC", curses.A_BOLD) self.draw_line(0, 52, "Data", curses.A_BOLD) - if self.data_structs: # Only draw if the dictionary is not empty + + # Indicate that byte change highlighting is enabled + if self.highlight_changed_bytes: + self.draw_line(0, 57, "(changed)", curses.color_pair(2)) + # Only draw if the dictionary is not empty + if self.data_structs: self.draw_line(0, 77, "Parsed values", curses.A_BOLD) def redraw_screen(self): @@ -345,20 +390,25 @@ def parse_args(args): "python -m can.viewer", description="A simple CAN viewer terminal application written in Python", epilog="R|Shortcuts: " - "\n +---------+-------------------------+" - "\n | Key | Description |" - "\n +---------+-------------------------+" - "\n | ESQ/q | Exit the viewer |" - "\n | c | Clear the stored frames |" - "\n | s | Sort the stored frames |" - "\n | SPACE | Pause the viewer |" - "\n | UP/DOWN | Scroll the viewer |" - "\n +---------+-------------------------+", + "\n +---------+-------------------------------+" + "\n | Key | Description |" + "\n +---------+-------------------------------+" + "\n | ESQ/q | Exit the viewer |" + "\n | c | Clear the stored frames |" + "\n | s | Sort the stored frames |" + "\n | h | Toggle highlight byte changes |" + "\n | SPACE | Pause the viewer |" + "\n | UP/DOWN | Scroll the viewer |" + "\n +---------+-------------------------------+", formatter_class=SmartFormatter, add_help=False, allow_abbrev=False, ) + # Generate the standard arguments: + # Channel, bitrate, data_bitrate, interface, app_name, CAN-FD support + _create_base_argument_parser(parser) + optional = parser.add_argument_group("Optional arguments") optional.add_argument( @@ -372,31 +422,6 @@ def parse_args(args): version=f"%(prog)s (version {__version__})", ) - # Copied from: can/logger.py - optional.add_argument( - "-b", - "--bitrate", - type=int, - help="""Bitrate to use for the given CAN interface""", - ) - - optional.add_argument("--fd", help="Activate CAN-FD support", action="store_true") - - optional.add_argument( - "--data_bitrate", - type=int, - help="Bitrate to use for the data phase in case of CAN-FD.", - ) - - optional.add_argument( - "-c", - "--channel", - help="""Most backend interfaces require some sort of channel. - For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" - with the socketcan interfaces valid channel examples include: "can0", "vcan0". - (default: use default for the specified interface)""", - ) - optional.add_argument( "-d", "--decode", @@ -441,14 +466,6 @@ def parse_args(args): _append_filter_argument(optional, "-f") - optional.add_argument( - "-i", - "--interface", - dest="interface", - help="R|Specify the backend CAN interface to use.", - choices=sorted(can.VALID_INTERFACES), - ) - optional.add_argument( "-v", action="count", @@ -486,6 +503,7 @@ def parse_args(args): # In order to convert from raw integer value the real units are multiplied with the values and # similarly the values # are divided by the value in order to convert from real units to raw integer values. + data_structs: Dict[ Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None] ] = {} diff --git a/doc/images/viewer_changed_bytes_highlighting.png b/doc/images/viewer_changed_bytes_highlighting.png new file mode 100644 index 0000000000000000000000000000000000000000..53e83848843514827c17396f6d0187eaac776ad2 GIT binary patch literal 20391 zcmZs?1yoyIw=P^t3&jf*m$W#=iaROpUfhbiQylW*ZpDfhcXzkq?(PJ42p;mM-}jw! z?)~psV`S{?oxPK+x#n7PKF@qsn394d1}ZV?t5>fuq@~1EUcGu9@#+~R2DG8T1j*K@6*Wy#qn}&jV?ON_-jJ3(6wrr-g=_I*J%=K+% zyD{{$MY2}wb!)yKdwsRL@XKTZeRqA%@&l_2s~GLF_KQrGhe>;VUYBngdC)MZCchft z#Q*wJ!<1&6_4FJ~#aI;`{pH=47hi!1&FLv#Qb9gYhiB%sc}m6cM+$fgH}SXt{e6?8 z^Mw%y8yh>0e#Y)VG$f`5lo}cudca)WS!SYjvGoC42cPIe82r*1v)RLWQq|yZDKRlI z7q{>PM2}Gl_=DIfMw(QAzeyE6Ld-={tNDa{@m$L=0le@-PWN_aIiI2H&yO-~yWafE znC~;mMB?jSu%o9)!=3#NthSiYP_sb8xX?2?V`F29W{0iU75eR{X=6yOYteWN>V&<( z+v=FDm#6cALmum-MkZ^t9>5tNE_vBm$!F<|om#w>t-(xG6 z5#om(L{s>kk8yAOX?EU^eC2+*EiNY~N4Vv1x?JymzZFt#x5me)(=bYSJ0%Gqipfx* zUA1#__$(&&fgxC&Li+C?G2hdn5XTSAy37y{9-KsOzy4Sg&Zin~M zyU>wj7VdBtHQD1_^iI9%af7DkRnn$!Qq&YJ@oP@V1p4N2q4@qQVE>8zkco4&4yX7x z74nw!4rN+VrML`{AL$U3ThhQyVwQ#pI|O}*SOWhjf*xliekI%uT}HCWMLW)NV8Cjx zfxnAEpLDqFMI@})o?p@`!`5Gf(@#l$dE3`L;3^g!Qqj`nROfTv%Y0;Xb#XPc@fNs8 z#h%*chAJ?_$FZ)+RzX(O-%HRJ)PlUz;kep3ZP!NbYZOuUS--_)$U-?`2Nx`c-?JJJ{%EN9MZrYw?0T1ZEV ziG0?2F0>;!eRbbYV!wH^R4b(@&lN05$rRfu_=EUxD6Fa&q+ZiT?gF_%%EHDTtIQC8 zE0HpO0{QEpIZbF}t#_lUT6Ci$;>y06Q{|*~kF0*vR=?s>!#A>o*q89yOjU_r_UvDf z4DNdfVly+N-8Os(dV72OiKeFg>6ju#v`O-hwe+2vbtQhG*9(=9zH4QgKkGi<-rSO8%S*Y{xnOm^JRVEj)dYUwrBQyJUB@w89X|b&&RlE+ z*0X<2H1JtQDj{Bn>RX9)SLH+0*3{x03E7dolyyE)$eLf|&9`*SO%yr)<+uXh{En?q zKoE_ClQY%=q9_+b(4}-GYH2`xd1{S^WvijlPk_;K#itHLFt#~o|b1>+ZE&B-dXKm2G^xol&cTojK%6yWuztMVo>FoxX)ayzn|K`r83#FO z>F)Nsb!!&`w;w-<)|RT@)LW?h#ECSfxpu0F!qv#zH8UqNw;M6$#ilJG1U9B~P%rH8wvn6N3N4iSs@A2vqy!y%tPI0*UyJGmw>p82K7ZaeD+RM&5I+@r6!+xUi zTJ(tNqs7Ttqx3qQ&*i6NR^;COJ2U3?!A&~A#+e&uMK_w0@fuBl*A99U%q#Rl! z2d38;8=vCm#VwT+U|`LTi(>|vxY}GW)i22L0!a)toeX!qoLd7^1bek<{Nrl3F$Vaj z;QHt&eU?u2fQcI$_E@mi0f(1Q0X$?_=-)svw~J}n0@fOM4*=x-U9k_2ery6v^h559 zdezMP+TPT(B(RYr#jUwB;Aa0H>Sy_R^UFLpwDFGl0Jrf@w6Z4Ttw%U+>6qy zEmHvVBg+o?lZ@_6<<4sJwO_pI-cT{Spu1HBt^1W6CQ_l#+2|dl?$V1v{7Z$pQ+)IJYr0p~< zBdZhQc$jYPRCAG7SQDv(BqObY@G~@YJ7#SX9^l0|!v1~yzz&~}@W$2$kFxOgx6|-! zU~ra+w#miK?RaQtsN4MS2QH0-q@;lZJ6kBse@{P<{O`|wfdBsgFKK|^ZSr~Ca4IHx z__XNgDowzk?OAWmy`e^Qtorg~{FlA$2l*!%$1(%74u`^4=OhAn){<|9Z>TH^5~UY{ z-OSwU243BC;73|=O zb9ld!1^t})a>R8~WFv9z?hdrP-@nMN-!8p>#!6##I`Z&7JY?Bgq7-zVC9~}YhBi=w z7iq4GkB*k~)GSWOJHhtW#(EY-bbSk*k80kQ$IPTQXF)6{^2_er#0+j*TAa_<20r*J zx5a#b^nE_8l%YF&(s(Vuy>$iB@neY!+V9gM-oOHer9~a*o}yA%QYX^s#KLbMMgf%Q?DA}~hG$e#pa;$X*6wSj z5T!LD^h7JZQ_hg76wSb_be=GS;)z!7V+!Id{@^o@iHOEas5PPm-!g)RFtvijqc_~f z9ED~?_?+pyZIC2fb|Dz#vr9jYfVZB7#nmVKbA{bJKEp=-xK!2fwRMC#m(jWL4HNQ4 za^c2j1l#v4jnSV9%Ltc-*sw6EywIu(KT>~r%yC5MsP|aZOASD8q*T&a>Z4oc_-CBn zJYuoB*@6I7(2nQCwQ1V9+I-*arrzaaEd7Mx_dikTx>z`5u8SwkCc7nk6N>(0Kn+I* zrwkHE$LYKA;FnQbAK1|~-kFGRuxvZ<{z0Y(2!PLuvHYg=&8swmyKa)+6w2ibtsCBU zccOA*_bH;E5^>%w$x`Zu1C#CZc9)52-9R zRV$%|#os~5&S{7wdaC=YadBaFaqC?~gI0WsCegWD-!X`nTPA^fA!g1!@yQX9y!_Zh z&wmOxNH@adyMfL+8=Yg(keR)iEtIE!n-sr}MEhFrkNXDeGc>uNK}Zm~dd})=n)xWk z65YG+(nI9k^2;(8?r+NhwiCW&ia;o}w|?SzW8~WqiR{1t0_S=3hg9ZU0`*?XeR%HAjYtUp*T>DY#h zod>YoyG&i(ZCX4Sx>Qc|zNGHlCnxLo*nbm*a!lZPi+)H25I~QdgNb*)HObuW<(B0T z4=9oj0Kr3splgqGi>`DZ*v!A}oDzf)F_ad|HXU{LRv>Y1Pfny}KB9KNXJ0q~wEFl< zdWdFOV62798)(+vi)K^ko%1X?<=pLXl};O)~rAqjU(XKUeJ3ept1QL^5tVHy|AnBGeN zuKOxf_K?yk0UkyA(WGc|Ck~Fz$)de(=ps+z>2KdLV8_1cc16OfcVE1%I{J}Ul)ds0D^_g=v;DaEzs!0Q za+V7ca_#w$Ldl{m$mE5!jT~^U!3`ZS$2zidVc7|b&1*VN%J!(Iv-sqxY- z-*aA$7ma4S_1eXkT#1{)-A`WQx6ga#dqR3sNLi^Nv;IyArZ?Mzca7Vn6G_|6>K_|< zkk2+hP*mHF3LEad*I&G4?9iVIr^kqQ{O)X)okvBa$08+Y+jqO4%G%OQXu4z<8Kc)k zq;5-3$9QQ}cF1evbE_rr)Jqd-ek$P+y<&k9v=;CaKw#vvZ|H4MVS5iDyH_5;+dbRx zEA!}_8MpO@adA7L2@qNpb)RP(XMfmK6$;t6$#nk}M9of2Euph>5xQ1~$`qfwYlaqk7!?HKw zp`~c=6wOT}GSURH`8dMt?rpY_0d8>JW`v`8Kw%!{lQV@{QE;Yl>xJ&I<^?Yk3J$(C z*2k5FvZQ(m^f$@%L0T_ouen%U0UH?Sb>Ay6IE^+s%p$3Fb89{mu5tqVC7-6&UeGui zZoj8*^qy$VS7NN!c%Fc4STNAH))fEMl}z7PyJZemyy8dZ0EnHSONNew+1nRiC?#3E|=C zHs{L-hJhP{HlM`SC}Y>Upoi422EP~pG8C@Om+xoa~$k6UYs|=65DTESDeW7IS*OZjvF{*5Qtg*&c|q)kNIp=6o5m7|d>V zT!HW_K7}m#z^MaQgm)z#H7VvHu9NGz!7I)gbi^d zb}S$RLhG1Q@(Sf@H6tXz%3V(ypABuU637{~^8;BqohZGD3DxZ0aw`$H0OjXX%H@her|@C^Ql`2wUiCC4|XJ6u$|yPx7iyhbxt$~H7_v79M} zpLtcBl-0E&9&xRnvj8ojWTJ&P zUCV-W%Z<3rVf&Mw_wO@-^Z8fbg@#?hNIk#tm?mjA2S*18$jm3FSHm17iJefL1N5-k zEd<;@WYp{v+V*Z;IVGI)hZbb=p344uiveX)Th#DcpQQsr+y7u2fD_aVS-`tlp61tP(A2x4tny3gYZ+$rcyuqsD zqs9C~;+@ZHrrv@)Go`+HOYw0WAUzVd7^3+~@QY!Ed=^j#$I0zO&oQ~gNH!{|vLCge z29)OxNyDfVvS9VBnO{5&aCb-J?NB_*;n18t$(2fUNc$G|m8O^W+mITySdU@MXSOmC zJfftW@OQSsnM%|gFL-KH0>L&K`Lc<6B^*UR^JVV$7`$I@izp10wBL!k7U2*Zv}Ce> z3lkGczHu)gnXrh9GyafUP0XCr$hfRQ0yDTA`su*n)gOOR5@cPMi!!DscwlEQk%lUN zn;_@DyP$$@vexBJOB~(syS$l*jFQ;Kj(eU&)Q6CSI7pFcHI*xRD;ohF9mA0E>V)wf zZ1H_C5)vyjgC*7S$8i@IV+%P|tcjdlow3evf6PQA+e58E6_R#1clKMnU1*jXau71| zG;1aA_>{OQqv=fo>M#k-PGqh5(dhD9E;>KE;30OgWa6cV|@Cnj5*%;j%!r{M{` z-8g4=B~rf=w|q(h8`lLZWs1CXL^2Y(#xH4VFHHdyOg=cbfuqK5o7tZ@K;`|lUH2@d zIDFEQY_)Zdfy8;c1j6_CGed1D^#arWKd+Sp$YU(yw$yWi#XDJI|C#^Ap}d@-P%mJ0 z5%m-amP;zQM7!$5#T-CaZ@kX!ccK9*imZ^WM9$BPkDjypx=2GUUM4BiE+1@4^z+U1 znvorP4u*xI+qI7Ho+PLO-Nco27uBF7BbDbO-yi0fzcLNiSuD-Eir@YtbkB^WpWVz+ z`&hpW9Bn_m-VZGN&QfA^?D+f#g>)m!)<)5od*w2~BuNB;L)XGceI?;y%a80(ej10a zJXj?!f>(tya_OGUfWq7TzPpPJRFjp6Kcd9#R{hyeZ+7ef3uZGV6w$_V!)-YfMvU(h zfJiuFBej@-{oMUsR7RNk$O`lK$1x<>eUrA(ENYpE@y8Y3Ee`xDN4$@7I0PqVNlGS+ zqL>7_y1!^Fh43ooV8*8`-!&}hWkgLi@Rna6rfd(M$A4tu|6T6~Ug#jNSXtJYY;$n0 z;?=YL>J26QG9H@E%h~&{&NW+Jo6C+!N(V3GC>(@IbQTF6L@@wP`2;C(wNt?{X* zkGk%!CiBbn~A4Q^j1K%o7B0i6#CnMl`4lCCbh03`cy@&Z5VH0p#_< zE*_l)Nd{OoXwKRA8RQ9!2s?AF;@Up$XLSeho_Q3m@&2=`>V z2!^yI=BrKPx?99<&Fm{`dfJW7|s-r_%uNieTG3xA}7&vhsQ)l6ZZUF1b(YA8rL&TkSf8mr!!`Niz^sy-8i%jaSA6%l(V!{dwVB+zt<8+|M4rj?CWmpME$fM}YBh=~V zo6Js9OcSiaWo6t3pGTX^_QwZ_!&yXBU8}wnKLJSF7Z&5;80u!Dufm4p0@2DY9ONRV zGhGSmT&ZNok3T*#HNF=YKWhC+0W68+u&)OFqBOfMgf!$-8^6<*5pu=1?S?NOQp`YNDeYyUZCyMZM;g+@5 zy{9(EM?-Ny{uVd?)e;(ScYvU__ft;iC3>|xNd6@vNmp^I?2kX3rwp~=qN0=LK9M`e zAhVy2VhVX9#a&b9I}G^_a*5`q zhV(=4Z;8!z(D>u8!yCV;qXyua`jj4>`{e*RqRYzsoQ zQb+78q%_f4Dmxxae7AbuO2?KoK~@nVo~FV-?UMJoAl{&MeZ5~frQ$jvl(Hh6V6GnxsFcCb}nr(X=7keNH+K}1|N#F|4^kZ$p z;X-2GK0EH(5`0o}Jou8@A~C91$}PIU7LllztodegcqLYOIaKP_ zOrmY$nF(SIfr_VeQX2+C+|Wud%a6F%cZ}bRf%z|VufKk8SKTl$zoSdHdO&A1KpVAs zAtR!zs1Q%#b^#I~vxS!`xAKozT%?#Z_&AzSa1Z#67nt2dK4eGTWjAMI5U5Vmo)gyc zT7H8F9^A7G_TBBq2Y~06}Z{gCQU5Ocd9{0t6uE74!6g{_+bbc2{ zStkwnWzMww1@Y4;xX-2LaB}}$4A;$LxP04=O@oe&6Vh=puRzs!^tgrF=+{wX!6r{$3`3* z9gC~dQVkf!78iT3D=|2CCbRK>t+g!fx4tWL>m`$gZXE8pJmDd;Yjx~f!9Fqcd#af{ zvXM1^7>_R*tYkn*c2Yi`Dfz=V`~si*_G-Y(^$v%}%ZwKaV@Ll8z?wY9fdaFHnmmS8 z=^e$f2s<&qk^O_SXGClvXKpsb>a&f^&w@iIR^;6JFhOQaKGqgiY~a(<#6m5RSdz#qY}wFW^0AYBUtzPgU*LN!fLcs$sm>0`f*p-PqJEgTi)$PyD<{t9C~cl)vp16s71xO>+My zq-&>gD{g@7KFwOID;$PWrd;f>i9@c_ij4)@K$*y<{SG|Ooa!sgJ%^cTz|^IF8sAgI0MBJTv|@L;+9}E5Ut0dUBKkyq*4oBE1a6UEDjpw{%796Rd{4E(xkr5 z(zl*6fh>(Hus~3!`m~Ce2Cc}`=jg>T2|{A{lH|X5%u>T|=IsSXcL1@tT0}K9c|y(2 z4dc5FP^Y6spWxkUz+aI=GP7QfTj!JB^DZs0dpME7@x1+RyBitN@G<~Ps7n&y6IiNI zxec%Lu@6&~o{r#UEt9QmRUv0>7=0+Omz+w|^iO^CoIUYEeaX}}Ue6(8t~D8*`;SHO zJLXE&KGx;do^reA!sKlpSqyp#J03X?7XER|r$6wme->;)2$L>$hbF?h_-WIopdgMe zSfTKD^Y3g)Wl0lSe`o3+ucw~F0k_8p`J1YP;jh77goXT)hyVo=$%z#G#p#*IDxgzh!^@ z$^~~lODU;g8d*L0%#tR`SS0%w;XBy*BI% z$LohV1+Rqwd_&1q31!g+*_4GlH+u%;A3i?T!=Bn1$NBHxihC8)WZ-~K8bUhO?%8jMPw}CQasl2kig0I?>26Bj5CuQX8ivR0OpQ@Y0RRL2fP4 zn7)qdo57&-yLI6FT69KY(y8k8*bG|`Q}KH>umVRyumQ3>H0lIeGlv6s(@( z`$8B@LS^$!jl%xotxa;l!RCn7E8Ud0bj*VyEi2iX53KVmeg(!+2OPI2R!y{kz96ed zyW&;W8#k`p>}o{H?DiDOw;t5|v1!?_Gq{4+8<0qCk20MycxE2!pI^%M>`_9qUq(BD z)XVqPWq?c4jg$arJ@w$Ew`%mtjD(J|)|83o&jD`-9@2}4*JJ6_)YSL=+#P;XQ_9cV z1f5^a%?q)~0q&a!yL)@vaH`t%Ia`}yaMz|}CAW*Cw(G9hmmlgUq zx3}!AYX*5>=lQ=;k9F4ITe`0cJeOj`7~lGV&jha`PwDkv=^sE$Q{+`w zCYNQ%C7q#L$p()klWZxM=`@b>M8_R86jwDp>SI;t`fBIb#+2?KS8p5A(o|r)qO>pJ z9z2{Z_4DF>mAuizw!n9gJbQI0gS4(Oa%##s+T|rWv(1%kIJIqKL6Or;Ev&3yg6*`L z6DXQIV%l~K7pDDHqe&3d7g+LuUfQrfXjJOk2Ff6br`DHtC1Nob1)cLHl{=KcIOW=k z{kNZzzr*egiqe}9Zl7?ft&9>qYjImRL`NJWdiT|o(mhqta$4WQyEevusGTG`>wRJj zV9y*uVZ2%{Q7feh$g5*qhp$fk#=Kk9E=K56HjJts=b2vW#Tgq;2g?|n`m{;FU+85n zU9fpN^`)~uHe22bh4cYvDVGr>oYTSX-u?To7ozzg?waktqzlrGHL*#e%~a8`@mf@L z!I@~5hHc&tSG!MPdp$A`O7J5DWwi^g-7`dHesbJ_vetjq*Px#cF71$Qa)RI_8J264 z&tWBy@OcCDROn}l*S&ao@N!zw8hgYzDtCYNy+;W!f;Tog88qWi7s-*!2r*~yGAFNx z2`{-X9RkDYXGUV6#IxpJ6BYxxZXte)bycP7W;e2~X7h4||BkqY26NHJ;v~_1Vq;=4 zx4(LVR1K%d92a3(g6qBrf~YgIPXp9K&lkP@JDk?@gw%hUO2a>k)z*(L9(_ze%v9L%Zd z#E0RVs{qz%Dr)rIeKp0UVVU={^2e#SWoXY;pITNl_4x+=gFCzV4YJNHg+)dAgZ&3m zS56d~ezL_TYW&kSOWvmB=`zgu;gp`2Zapbvl#13S6^nAMya+gtv!#He;6CzMdGE0M zn!i9l#PO4suWtmLHC%|1pOY3GW~;9{-nINU!t62?EI)!33_?hLJYO6?2Q8jdVR0H+ zi_2aF@u%w^w3+ZbvFTP>?;M<4{F&-!{^Om(3x}JXfYMHT)>Tia(?_PtH!IHUn>E&i z=eFW&ab`D6{`}4FnvF*qJE67$j|aK^Kk&gkec|D#GZp5RdI`p40TYZ1SR>~H|G}T! z|G}T^_6x!~nyj-MA`!Px@g<>Cl@;0<#g7YGH?)S$x$jCBp7DMPJy^<>wFM({D{QVF zWU;!Ky>Wl?U*@mqf~Kj&{c6EnNK)+Lu0|cvSiHPtF^Jc?d7KN%<7#`ZV*Q@{N`vB)c+N|R?M@I{SSQY6aC-e z>)Qw>ah9@*xw}1@L(Cm0z2{4}XL|z=(-&V&M^Cq-L^U-}J?MDxjOL`#Xmv$d`o$4}0>*uiUIec4_kM>nY%`fJo#D{%z` z-{udIoz}3|Ns2Wi?X^19K^sQy=*P+I){Xi)YI$DiToB*nOf~g#jXm1F2Jil2_{wH? zRgD_{$lduM4=?xWX#kZV=idALkL(iyxxkAq#QqDo)b+sbZtPstc8~_3QvpbVl+J6Y z8O%|WuN4QctR6j;6ffMFg}|cnb<2+Ivq$6>CI}l98Fpb*KE1S<$0-wS`IWE{a-Y7! z6$qpmRsbrwud`cO&D-hDI+S_@Jkr7`fy|3~gX6)XsmmoKXxu5RIQJrt*kHKsP=Y@0 z&c1dP)RmvKvGdriTRUORxmxna>yh!2`SXo@(#}9kTGGXx^h-P`k06P}`9gKMq(}|` z>O<)VP+6w)MC#v^SuFC6|J;E|x?YQg7lN5v%+RBURfoWFM&EZ`F6u{8{Dd?)Cx{ec zoqM@s_tUZW$M5`VFUPg6jiYNFj7B3Zpev7hsR7h18NIp$_P+Kp>c{g(NwZI=^RBDHm(DQ7|*38Bga8SYOvKib`;F8X@Socq?!-DcVmooyKL` zV-9@M%1zVO#sfQ^o-sm(0{b2f)j;FuHVVXQy{%FFErUKeY0DCEmx0Kiy*J)6!pC9E z0UGJ?M0HAin|Ptu9*DfgoDLxB`STSp-g%|d_Hdi#&hvo%z1}n4mk77P+Wq{>t1GP% z!TuS+*q8G(u*<~AtO1&F`VwG4E{+wU4ODQmybw4HTCrn4v;75j{JrmD|9vYHni2p_fvwlrGI4!CyIVOjpZ1|LCbq@%j&Af=gSH}+W!NNKbcB+)$o=W=-^zTPEXCO zhQ|fT5jzhh{u9L$5?A$<@H7$ief3nLN`tP&JqDP2D5>rHGcJJ#v$OR*a*4IFjz-8) z``bYWVoFB3=68)QoFh!Izq;tbY?f_svH_QGpGkSmz5dYr3(v*Dslbd!WnJ0DVj(g@bzF_O8_Sw8ZmG*d<%EklV_if@j zQ|`?qV}eS9x9cFRg=B+)#}L44u9B^orr9ctuER|ZBOKEw{=z4$ockIcqt%$R0wfn& zg4~hQDp2kT23a$=V7=-rHmp*cdQ&P>jD&mhfE@w^O3<7X_tl1Ud4sDFMT%Dd3`Z3fDf4rW571}pE z4?^?EP%fy@w=6v9Wn@RD%1m?123t3o%yDqQ_hJtQX9&<3*nP}>HJDY zJA3>hVpL5{ySpTer{g)} zoWxws{?|R(S^G1O#Jv3idgRPKjUV>cgy2{039Pr=@V!vbstE$Is49Di6B9J3ion*o7#hIlbzl28 zSY@F1(gskSX9zH1dt36eSSsN0N5sL(M)M+QF;-nSBA!-JAqn@%`I{Iq{>|d34p7;C$ zSkOwSP;53C--!W6%O*2#wQfL};f|U`_({1X9ZWtY(ozP#{l^JI!qP8+V!;e9%RinF zyd%v7y?A)THxb0vmV%(i20PUQ|}{+Au+AlTFWRE)35o9;9Scli8r?twg` zu)L3;)ve%LkYXjPeZ-N&FZYzwTk3HmgHE$1pP%?bt=gjMi!KVp2fNj)?p9vTwdVVv)N zyJSu~GB0XtNNb^m(dgtgQNz+@7l*_$BL}b$f&~LVgkG{oAINC zt?r~*<@Cc$!-zv8ZZ3aADhm2C=94fKhscrzV-&PYa!~M0=Z}XtW&wUu>t2F_#pEn^ z6f#O$SeBrd`glt(WsHDPpcF6vBOJ4{G3Qe5mY~zq)Ayggf*W1GlEAKUg{OhlB}xJ% z3Fia2x$c|HuY@P55m=!@Mo*y-gx+Scs@uXg;et>fzZ z)3503e={QreWSmc*f-Q^#+TwQFdA}t;vax7EAHXs_yZwdP89p`D|Jqd0uc|>W(-Nx zD&z&JOWi5Efo0b6+S+(*3I7j zej!gka{9;*9O03WZ~1iX96zco*H4=x^zypkjj)g^X?zNX<9-EXDya7FPlel<- z1Ne~1d}k^#-+lg#ZdFA=(?AK7fN7DXjSb+^G#zXtjAMTyi#ysc0oTON7szaKUf$>D z{~`C~M4Fdwv?05_egBNd%qD3ixhcXCntFMA*z#Fpu0k=WYr>lbEiB7NMcyRp+p8B9 zLErsZfFH2^Z5vl8FypMxY6Y>2G6sP}b!$>xn0oP-rX9$<6-1pXnY5tgk_XOqw~l@c z%ER}CtO6JP`i;j9l4fRlDxIkZB#W7%H}*A1uCeP#%f4QRVpNR2{y<>zY3)>rArN5P zPG-S3A38pzvw)~*EA zTU2nKG)yK6GG`|y=Kn(B#XjeEkN$5?;O9{=7mrG;Mh#*!kyS`>RCZ%I zf@E4tq3g^qUyw}QrE#%AX(!;v-_#E^HYJE(IUxhRkXUGX&HpjC98Lyu>^ks7s()2h zQSrC;-XZCiX6n4}W0_o9vJ8~uO=9KdUa%1N{%;H~qN0Mcyu3WQuwb5ka|LDrExil0 z?}T!#yYFBa6W8d)^Y?|G`<#2-FE4bM)hTdsbMGv`o+siV){4X8(w0I3%p@EeANkUv zvxVQD&~IG+G_Cg0LO0R`nz#9=0Pq$7Na}b-qE{MKm(}u`)wkr{MX&dRu*P?uH>o$v zv8tV*l$wq6ezOB>O`g!tXRRPgq36LEjTL@usZ#dl@tl&=cTbJKZUF_md?9leu4lsH ze~yi`F&c5s4^`X#e=t8}z5YENAyh+|^moD;ue7HdjMuz1eH|B3urT^edh8|k(bnDZ$s+hH$H{aVvk zl|Ahf@tO=(qe9 z$*Rj7W(`fYe+9LFeQwZSqiFM*6sxC!2AhFHOX56)%UAxb7}<-n)ly2RxA~`>{OErH zQx~R@l%Z)K#=Hnk{rC$4)SuHe?i)US>ZF#e=oy5R!YaWiys~_=GDIy4b?<`2QGSYu z`K-D0jjpt+QhZJu}YqZ}D6$9^cq){?z_D>*ONJMEPQUlspi~m_Avl+xrI(u!@zp z)mxg|ACurVukc_HX#Et4}z$yZ3z#PUYgC zvX%R>+6|%=)7PEom|}56O!;s=gK_v=+*iSDWWcx zOzOAuE5noCJHuY$GA|`NW)GBe&<(c!4-pdys7xhVV*mW$T4xN?Ny6g81J=^%~=s9=l-P;&n^==?_(H#ySs z{l96L^`Uw^(E&o4k?gv6GQYk4n{jATa=XcTxuK;r8J1?HaB0ic?DTDiZAdcl%^KHN{|%wRXLu?)vFlU$(F_)?eFvB9Q7 z?nA|uyOAx-YcS`A*=c;O=Vxl!Js z`oN1{os+YjRI&da<)3LB`tqPF&58xhZ{GrAK+ik<7x1+VCZ?(yk4+Qwrmrx|mzaQA z_e+E2^!9y37R>wV)_Up~bXdFI!StnB$$iLm*QDvY#)cwDk}{ghwYv#@>y&w8dPJaF z846?M^4_+0gbfJ!9W&56Kr#&>0b&Ce=Z~{2xOn(1#lY=00u*WMErr$47-r!_Yr@lm z?E6K@%?RPELCRM)E4PswtT zg^DrH&tz$*d(NL(`GBGs?pL$T&w&-^6Rv^nv}KDbH$6w%KK6D$&tmtx&np{v?x9*3 zF;`H@XD>J1>LTEXk)ZDlSkz~uM5t6&phb1Aax5c1d2aUDt2U$ivb+HB^hiWElbjVq ze>`8F=3VBW^D<`dtE#A|xS;lHrt>=E{9r=hI^Co+PpzFZcPC4^h57l1Sr4P;H=z3vkkYY^1+=CF zmalKt0!=yJo@_V$;W?&IdB&D>&e8rg>25V`q<0o)=c-*L5qu2cL;=nNANqhtC|MwR zN$pz9^9e{N-QhIgar*qn>Rsn?6<`Xec2SV|>xqBu%yvH6_94IH(tgg%S+V3$DuruJ zN9Mq%#9ggeC0`#;=b~Q9wDB`<_I_Lek$%~cc>#pG-QjUcwyhBOlq0fX!t49oWQhXG z+64~z&D3U#bKks69dX-i-t+58Oy&QXB1@c~a8GI&hX50I(OSO`p{tbPC1ntyVV z{Bg`C#JHZzLpo09|Ih}|A3Yr(kq7kR!K((rk`FJ*ND+^$Nnom)mSd^jd=BUE5+K}2?-y^McxGqZOyL>6I}|HI8Nd#a3% zG@d|`3%S&&grdWRUW^}k*OgjQ!AT$|*ILp?3Vq)P(;W?)gOY1?giSsrleCvECO<-_CBpy&{b9U}!v(e{*V@Wh>*6I-ZI{&e*@QV=RP?GpEStVC=DIDCWpE$*(b z#aDZ70dZJ?#3KpZcz`Ww#FH7i zktM0bv+C#K^Fd}Du5Uoi!%*M?rwK&Dxh+HvrMnMskK#U}{%`3DULsB~(n`7eD$y8ZAE!+QVlE^u9IkTn25JB7l^E5 z@H57QJ!HWE&l~n--EN(53gD0*6asv?1CY3R#LfQ!dfyXhR-3&dZ$(8-gcLE$^{;3E z1H1q}01`>b$q3quP0I6bA7+PDh5H|57|KVmv-eN8=ARaK)nkwNXLi?5=4d0qy?YuH zbH_8C*(r=08eq~Al=`S^9|YLOi23AGifR|k6L`_9`2FC;%@GX7@?)UyG6nvB?VM>; zQh6B0C#|f|7E{C#bJD2@$y~_E6|-!yPA*AdT0n?fq7x~W<1(2+fmvZ`?gom6yC%7% zmT9=JxR*6*V|gSsO-0GMXw9_DhdJ}v`|+OpKj)r%-uu4)``q921Wx8)Q#a^K-w9eV zkhx+%nwclV*|^c|%I(n(qOsn#4ElJfXTSXsprLJ@R9c;)cB&+$tn(=tEU4P;JGDo_ zz{nac2)WcGa`i@?b@d#g_saXE(7Y7V;2;eu(12T)O38tCOQJ#Py0)|ijzgN$}uS)1F%N% zeIf*JglkSKhR7V|T#-wA<=i(Pr`0afBkU?29M}0?Flip~@YZ~07!=WWqk25A964zq zmJiL}j2!8rlV}2$aJh6pv2#D2dW(nU8rGPL3iTgQ19sPrWlh^bZ7)6dEHROKFDlb~ zS|YZG!q+(;({YybbI9QlBzWe%x_ zayvq8&oAwJRiN6a@bjX%%5ia>(@c~VK?Aa1o%w{P_~GgD-TMW@;SR@Sbk{I$cDIIv zOt(2cMQ=3vM%<*0okEZH%r~fsfI*! zGY*Z)X-`o8)E079G2SXURBv*^x|$8%TFYKx8r$x(JlTknicPv?>zA#_?pNLI=(j2Z zFP`vCAKNUO7FQ3>>glBLPsWuRsPgcWMQv9^l}%kbQdaR$8s`Px%It$)RFhxB5tFU( z5@o|_=3{r!9^QaL5}=wO0~}H*{=|F%td^{TxY<~lBmad1Lwd#bs%f%v>=O@_?w;SIt}8{nSL&G}6wM)!LF4mKYF zGvm}|zTNo+4FOuH%>uE#whlt=4<1mPE1fJrC_FI_Ucu%68HsNRh|Qwg-CX|25q-vF znMIXcKE~1@l|EN*i+(b7gItg~ml4%aHaM>4AddQDg3XwckS2g=@){RJTCI!3cKW<% z^FwyiYv`Q&Ms}4Ec2~LNZ9iH@Hu!*5-7dmjL|^uv1T}XC%2E=?FnUGTTVoUyvvonf zESQl!>G0g8;a*izT(=?&&*sWsyu1@vjI@x!f+Jc_K)YsbVp?hA_67gFR#8{pmwh)dbU0;ZJa5%!xdpx+*fNpMmN#>R4Jh>gDfU2&>s;1P*vWZGyI9f;a zz4M587j70U@=AlZNWlh1+X>u|53gI^o?uy0;kH{^h$e+^VgOD7urhgit!BIu#AH~8 z!F4_8n9Mv^o9}Q2HVyFDv$nYLrm*pNOl%J1iMox#d1WvGwde^H8h#>}=@Nt*@Cc6G zm6-BbTc{HJ8SoaC0AJ1$m%b!}M?yM(zV5&I6u$`YSz5dphV+l|Q}R5&HFhpNu%!wE z60??yWa-8DD*o*Mch6eyQ|nA+hlIlR@bK^)%1HN1O-bYN-lhzN%9_&STHhe6-d`}T z4c@8Tnfp}`%V)Fsw>`;a??zxU?9jQFcW-chZ0ubBxu3}^9}JW>5#l92pMBm+)qMUc zQ+l0y!Q${Z$?`W<%gxOVZsagKhy{uZ)y>+1u*HS$T1aJ*3>+;52uZ&uG?=xxrU-P< z>^4EcMP5OItpUg1VQ>!}U2H-!^A~Q_mv*oNqp+cZb+a7ezi{TTV^SjQ$l{U>H|+m) ecKtAq(%9p Date: Thu, 11 Nov 2021 18:31:55 +0100 Subject: [PATCH 0751/1235] Use new exceptions --- can/interfaces/gs_usb.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 447cdf47f..2490784ca 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -7,6 +7,8 @@ import usb import logging +from ..exceptions import CanInitializationError, CanOperationError + logger = logging.getLogger(__name__) @@ -22,7 +24,7 @@ def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs): """ gs_usb = GsUsb.find(bus=bus, address=address) if not gs_usb: - raise can.CanError("Can not find device {}".format(channel)) + raise CanInitializationError(f"Cannot find device {channel}") self.gs_usb = gs_usb self.channel_info = channel @@ -38,7 +40,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): :param timeout: timeout is not supported. The function won't return until message is sent or exception is raised. - :raises can.CanError: + :raises CanOperationError: if the message could not be sent """ can_id = msg.arbitration_id @@ -64,7 +66,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): try: self.gs_usb.send(frame) except usb.core.USBError: - raise can.CanError("The message can not be sent") + raise CanOperationError("The message could not be sent") def _recv_internal( self, timeout: Optional[float] @@ -76,6 +78,8 @@ def _recv_internal( :meth:`~can.BusABC.set_filters` do not match and the call has not yet timed out. + Never raises an error/exception. + :param float timeout: seconds to wait for a message, see :meth:`~can.BusABC.send` 0 and None will be converted to minimum value 1ms. @@ -85,9 +89,6 @@ def _recv_internal( 2. a bool that is True if message filtering has already been done and else False. In this interface it is always False since filtering is not available - - :raises can.CanError: - if an error occurred while reading """ frame = GsUsbFrame() @@ -111,8 +112,4 @@ def _recv_internal( return msg, False def shutdown(self): - """ - Called to carry out any interface specific cleanup required - in shutting down a bus. - """ self.gs_usb.stop() From 9080c47acbad4ef512555f9148388e378a76f821 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 16 Nov 2021 22:35:50 +0100 Subject: [PATCH 0752/1235] Complete broken PR #1156 (Specific Exceptions: Adapting slcan interface) (#1166) * Complete switch to new exceptions * Format code * Fix TestBaseRotatingLogger --- can/interfaces/slcan.py | 105 ++++++++++++++++++++-------------- test/test_rotating_loggers.py | 7 +-- 2 files changed, 64 insertions(+), 48 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index ac90a2600..035969d8b 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -9,7 +9,12 @@ import logging from can import BusABC, Message -from ..exceptions import CanInterfaceNotImplementedError, CanOperationError +from ..exceptions import ( + CanInterfaceNotImplementedError, + CanInitializationError, + CanOperationError, + error_check, +) from can import typechecking @@ -62,8 +67,6 @@ def __init__( **kwargs: Any, ) -> None: """ - :raise ValueError: if both *bitrate* and *btr* are set - :param str channel: port of underlying serial or usb device (e.g. ``/dev/ttyUSB0``, ``COM8``, ...) Must not be empty. Can also end with ``@115200`` (or similarly) to specify the baudrate. @@ -79,30 +82,37 @@ def __init__( Time to wait in seconds after opening serial connection :param rtscts: turn hardware handshake (RTS/CTS) on and off + + :raise ValueError: if both ``bitrate`` and ``btr`` are set or the channel is invalid + :raise CanInterfaceNotImplementedError: if the serial module is missing + :raise CanInitializationError: if the underlying serial connection could not be established """ if serial is None: raise CanInterfaceNotImplementedError("The serial module is not installed") if not channel: # if None or empty - raise TypeError("Must specify a serial port.") + raise ValueError("Must specify a serial port.") if "@" in channel: (channel, baudrate) = channel.split("@") ttyBaudrate = int(baudrate) - self.serialPortOrig = serial.serial_for_url( - channel, baudrate=ttyBaudrate, rtscts=rtscts - ) + + with error_check(exception_type=CanInitializationError): + self.serialPortOrig = serial.serial_for_url( + channel, baudrate=ttyBaudrate, rtscts=rtscts + ) self._buffer = bytearray() time.sleep(sleep_after_open) - 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(bitrate) - if btr is not None: - self.set_bitrate_reg(btr) - self.open() + with error_check(exception_type=CanInitializationError): + 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(bitrate) + if btr is not None: + self.set_bitrate_reg(btr) + self.open() super().__init__( channel, ttyBaudrate=115200, bitrate=None, rtscts=False, **kwargs @@ -110,17 +120,19 @@ def __init__( def set_bitrate(self, bitrate: int) -> None: """ - :raise ValueError: if both *bitrate* is not among the possible values - :param bitrate: Bitrate in bit/s + + :raise ValueError: if ``bitrate`` is not among the possible values """ - self.close() if bitrate in self._BITRATES: - self._write(self._BITRATES[bitrate]) + bitrate_code = self._BITRATES[bitrate] else: bitrates = ", ".join(str(k) for k in self._BITRATES.keys()) raise ValueError(f"Invalid bitrate, choose one of {bitrates}.") + + self.close() + self._write(bitrate_code) self.open() def set_bitrate_reg(self, btr: str) -> None: @@ -133,33 +145,38 @@ def set_bitrate_reg(self, btr: str) -> None: self.open() def _write(self, string: str) -> None: - self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) - self.serialPortOrig.flush() + with error_check("Could not write to serial device"): + self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) + self.serialPortOrig.flush() def _read(self, timeout: Optional[float]) -> Optional[str]: - # first read what is already in receive buffer - while self.serialPortOrig.in_waiting: - self._buffer += self.serialPortOrig.read() - # if we still don't have a complete message, do a blocking read - start = time.time() - time_left = timeout - while not (ord(self._OK) in self._buffer or ord(self._ERROR) in self._buffer): - self.serialPortOrig.timeout = time_left - byte = self.serialPortOrig.read() - if byte: - self._buffer += byte - # if timeout is None, try indefinitely - if timeout is None: - continue - # try next one only if there still is time, and with - # reduced timeout - else: - time_left = timeout - (time.time() - start) - if time_left > 0: + with error_check("Could not read from serial device"): + # first read what is already in receive buffer + while self.serialPortOrig.in_waiting: + self._buffer += self.serialPortOrig.read() + # if we still don't have a complete message, do a blocking read + start = time.time() + time_left = timeout + while not ( + ord(self._OK) in self._buffer or ord(self._ERROR) in self._buffer + ): + self.serialPortOrig.timeout = time_left + byte = self.serialPortOrig.read() + if byte: + self._buffer += byte + # if timeout is None, try indefinitely + if timeout is None: continue + # try next one only if there still is time, and with + # reduced timeout else: - return None + time_left = timeout - (time.time() - start) + if time_left > 0: + continue + else: + return None + # return first message for i in range(len(self._buffer)): if self._buffer[i] == ord(self._OK) or self._buffer[i] == ord(self._ERROR): @@ -170,8 +187,9 @@ def _read(self, timeout: Optional[float]) -> Optional[str]: def flush(self) -> None: del self._buffer[:] - while self.serialPortOrig.in_waiting: - self.serialPortOrig.read() + with error_check("Could not flush"): + while self.serialPortOrig.in_waiting: + self.serialPortOrig.read() def open(self) -> None: self._write("O") @@ -247,7 +265,8 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: def shutdown(self) -> None: self.close() - self.serialPortOrig.close() + with error_check("Could not close serial socket"): + self.serialPortOrig.close() def fileno(self) -> int: try: diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index dede154ab..d900f4f23 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -6,11 +6,8 @@ import os from pathlib import Path -import tempfile from unittest.mock import Mock -import pytest - import can from .data.example_data import generate_message @@ -90,7 +87,7 @@ def test_rotate_without_rotator(self, tmp_path): assert os.path.exists(source) is False assert os.path.exists(dest) is False - logger_instance._get_new_writer(source) + logger_instance._writer = logger_instance._get_new_writer(source) logger_instance.stop() assert os.path.exists(source) is True @@ -113,7 +110,7 @@ def test_rotate_with_rotator(self, tmp_path): assert os.path.exists(source) is False assert os.path.exists(dest) is False - logger_instance._get_new_writer(source) + logger_instance._writer = logger_instance._get_new_writer(source) logger_instance.stop() assert os.path.exists(source) is True From dc7397ec87c3f3f6982189faaa2d624d35563505 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:05:19 +0200 Subject: [PATCH 0753/1235] Better documentation of parameters --- can/interfaces/robotell.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index cb35aa774..dd4c6c377 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -56,10 +56,10 @@ def __init__( ): """ :param str channel: - port of underlying serial or usb device (e.g. /dev/ttyUSB0, COM8, ...) - Must not be empty. + port of underlying serial or usb device (e.g. ``/dev/ttyUSB0``, ``COM8``, ...) + Must not be empty. Can also end with ``@115200`` (or similarly) to specify the baudrate. :param int ttyBaudrate: - baudrate of underlying serial or usb device + baudrate of underlying serial or usb device (Ignored if set via the ``channel`` parameter) :param int bitrate: CAN Bitrate in bit/s. Value is stored in the adapter and will be used as default if no bitrate is specified :param bool rtscts: From 746082989c2b6c444a7911c0c044362759b2a247 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:11:37 +0200 Subject: [PATCH 0754/1235] Use CanInterfaceNotImplementedError --- can/interfaces/robotell.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index dd4c6c377..1e85d5b1a 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -6,6 +6,7 @@ import logging from can import BusABC, Message +from ..exceptions import CanInterfaceNotImplementedError logger = logging.getLogger(__name__) @@ -65,6 +66,8 @@ def __init__( :param bool rtscts: turn hardware handshake (RTS/CTS) on and off """ + if serial is None: + raise CanInterfaceNotImplementedError("The serial module is not installed") if not channel: # if None or empty raise TypeError("Must specify a serial port.") @@ -74,7 +77,7 @@ def __init__( channel, baudrate=ttyBaudrate, rtscts=rtscts ) - ## Disable flushing queued config ACKs on lookup channel (for unit tests) + # Disable flushing queued config ACKs on lookup channel (for unit tests) self._loopback_test = channel == "loop://" self._rxbuffer = bytearray() # raw bytes from the serial port @@ -104,7 +107,7 @@ def set_bitrate(self, bitrate): self._writeconfig(self._CAN_BAUD_ID, bitrate) else: raise ValueError( - "Invalid bitrate, must be less than " + str(self._MAX_CAN_BAUD) + f"Invalid bitrate, must be less than {self._MAX_CAN_BAUD}" ) def set_auto_retransmit(self, retrans_flag): @@ -119,8 +122,8 @@ def set_auto_bus_management(self, auto_man): :param bool auto_man: Enable/disable automatic bus management """ - ## Not sure what "automatic bus managemenet" does. Does not seem to control - ## automatic ACK of CAN frames (listen only mode) + # Not sure what "automatic bus management" does. Does not seem to control + # automatic ACK of CAN frames (listen only mode) self._writeconfig(self._CAN_ABOM_ID, 1 if auto_man else 0) def set_serial_rate(self, serial_bps): @@ -161,7 +164,7 @@ def _getconfigsize(self, configid): return 4 if configid == self._CAN_READ_SERIAL1 or configid <= self._CAN_READ_SERIAL2: return 8 - if configid >= self._CAN_FILTER_BASE_ID and configid <= self._CAN_FILTER_MAX_ID: + if self._CAN_FILTER_BASE_ID <= configid <= self._CAN_FILTER_MAX_ID: return 8 return 0 @@ -178,9 +181,7 @@ def _readconfig(self, configid, timeout): newmsg = self._readmessage(not self._loopback_test, True, timeout) if newmsg is None: logger.warning( - "Timeout waiting for response when reading config value {:04X}.".format( - configid - ) + f"Timeout waiting for response when reading config value {configid:04X}." ) return None return newmsg[4:12] @@ -211,8 +212,8 @@ def _writeconfig(self, configid, value, value2=0): newmsg = self._readmessage(not self._loopback_test, True, 1) if newmsg is None: logger.warning( - "Timeout waiting for response when writing config value " - + str(configid) + "Timeout waiting for response when writing config value %d", + configid ) def _readmessage(self, flushold, cfgchannel, timeout): @@ -261,7 +262,7 @@ def _readmessage(self, flushold, cfgchannel, timeout): cs = (cs + newmsg[idx]) & 0xFF if newmsg[16] == cs: # OK, valid message - place it in the correct queue - if newmsg[13] == 0xFF: ## Check for config channel + if newmsg[13] == 0xFF: # Check for config channel self._configmsg.append(newmsg) else: self._rxmsg.append(newmsg) @@ -269,9 +270,8 @@ def _readmessage(self, flushold, cfgchannel, timeout): logger.warning("Incorrect message checksum, discarded message") else: logger.warning( - "Invalid message structure length " - + str(len(newmsg)) - + ", ignoring message" + "Invalid message structure length %d, ignoring message", + len(newmsg) ) # Check if we have a message in the desired queue - if so copy and return From e1bfd093b354dc8c7e19aa19f80df5d9762e914a Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 15:12:23 +0000 Subject: [PATCH 0755/1235] Format code with black --- can/interfaces/robotell.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 1e85d5b1a..17f1b1e11 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -106,9 +106,7 @@ def set_bitrate(self, bitrate): if bitrate <= self._MAX_CAN_BAUD: self._writeconfig(self._CAN_BAUD_ID, bitrate) else: - raise ValueError( - f"Invalid bitrate, must be less than {self._MAX_CAN_BAUD}" - ) + raise ValueError(f"Invalid bitrate, must be less than {self._MAX_CAN_BAUD}") def set_auto_retransmit(self, retrans_flag): """ @@ -212,8 +210,7 @@ def _writeconfig(self, configid, value, value2=0): newmsg = self._readmessage(not self._loopback_test, True, 1) if newmsg is None: logger.warning( - "Timeout waiting for response when writing config value %d", - configid + "Timeout waiting for response when writing config value %d", configid ) def _readmessage(self, flushold, cfgchannel, timeout): @@ -271,7 +268,7 @@ def _readmessage(self, flushold, cfgchannel, timeout): else: logger.warning( "Invalid message structure length %d, ignoring message", - len(newmsg) + len(newmsg), ) # Check if we have a message in the desired queue - if so copy and return From aa5dfd083de07b62bed50af12585fec3119ac921 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:15:43 +0200 Subject: [PATCH 0756/1235] Simplify logging --- can/interfaces/robotell.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 17f1b1e11..ba3c40dcd 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -89,11 +89,10 @@ def __init__( if bitrate is not None: self.set_bitrate(bitrate) - self.channel_info = "Robotell USB-CAN s/n %s on %s" % ( - self.get_serial_number(1), - channel, + self.channel_info = ( + f"Robotell USB-CAN s/n {self.get_serial_number(1)} on {channel}" ) - logger.info("Using device: {}".format(self.channel_info)) + logger.info("Using device: %s", self.channel_info) super().__init__(channel=channel, **kwargs) From 4db969de8bd8154838a5da76e42bfe21b37af69c Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 19 Nov 2021 07:22:38 -0500 Subject: [PATCH 0757/1235] Allowing for extra config arguments in can.logger (#1170) --- can/logger.py | 24 +++++++++++++++++------- can/viewer.py | 11 +++++++---- test/test_viewer.py | 24 ++++++++++++------------ 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/can/logger.py b/can/logger.py index 7e0b32931..d078cf2d3 100644 --- a/can/logger.py +++ b/can/logger.py @@ -58,8 +58,13 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: ) parser.add_argument( - "--app_name", - help="""App name can be necessary in the initializer. For example with Vector.""", + "extra_args", + nargs=argparse.REMAINDER, + help="""\ + The remainding arguments will be used for the interface initialisation. + For example, `-i vector -c 1 --app-name=MyCanApp` is the equivalent to + opening the bus with `Bus('vector', channel=1, app_name='MyCanApp')` + """, ) @@ -102,8 +107,6 @@ def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus: config["fd"] = True if parsed_args.data_bitrate: config["data_bitrate"] = parsed_args.data_bitrate - if parsed_args.app_name: - config["app_name"] = parsed_args.app_name return Bus(parsed_args.channel, **config) # type: ignore @@ -129,6 +132,13 @@ def _parse_filters(parsed_args: Any) -> CanFilters: return can_filters +def _parse_additonal_config(unknown_args): + return dict( + (arg.split("=", 1)[0].lstrip("--").replace("-", "_"), arg.split("=", 1)[1]) + for arg in unknown_args + ) + + def main() -> None: parser = argparse.ArgumentParser( description="Log CAN traffic, printing messages to stdout or to a given file.", @@ -179,9 +189,9 @@ def main() -> None: parser.print_help(sys.stderr) raise SystemExit(errno.EINVAL) - results = parser.parse_args() - - bus = _create_bus(results, can_filters=_parse_filters(results)) + results, unknown_args = parser.parse_known_args() + additional_config = _parse_additonal_config(unknown_args) + bus = _create_bus(results, can_filters=_parse_filters(results), **additional_config) if results.active: bus.state = BusState.ACTIVE diff --git a/can/viewer.py b/can/viewer.py index 8fd3fc294..b74e954a0 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -36,6 +36,7 @@ _parse_filters, _append_filter_argument, _create_base_argument_parser, + _parse_additonal_config, ) @@ -480,7 +481,7 @@ def parse_args(args): parser.print_help(sys.stderr) raise SystemExit(errno.EINVAL) - parsed_args = parser.parse_args(args) + parsed_args, unknown_args = parser.parse_known_args(args) can_filters = _parse_filters(parsed_args) @@ -534,13 +535,15 @@ def parse_args(args): else: data_structs[key] = struct.Struct(fmt) - return parsed_args, can_filters, data_structs + additional_config = _parse_additonal_config(unknown_args) + return parsed_args, can_filters, data_structs, additional_config def main() -> None: - parsed_args, can_filters, data_structs = parse_args(sys.argv[1:]) + parsed_args, can_filters, data_structs, additional_config = parse_args(sys.argv[1:]) - additional_config = {"can_filters": can_filters} if can_filters else {} + if can_filters: + additional_config.update({"can_filters": can_filters}) bus = _create_bus(parsed_args, **additional_config) # print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") diff --git a/test/test_viewer.py b/test/test_viewer.py index 9b74add6b..004877d7a 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -381,19 +381,19 @@ def test_pack_unpack(self): ) def test_parse_args(self): - parsed_args, _, _ = parse_args(["-b", "250000"]) + parsed_args, _, _, _ = parse_args(["-b", "250000"]) self.assertEqual(parsed_args.bitrate, 250000) - parsed_args, _, _ = parse_args(["--bitrate", "500000"]) + parsed_args, _, _, _ = parse_args(["--bitrate", "500000"]) self.assertEqual(parsed_args.bitrate, 500000) - parsed_args, _, _ = parse_args(["-c", "can0"]) + parsed_args, _, _, _ = parse_args(["-c", "can0"]) self.assertEqual(parsed_args.channel, "can0") - parsed_args, _, _ = parse_args(["--channel", "PCAN_USBBUS1"]) + parsed_args, _, _, _ = parse_args(["--channel", "PCAN_USBBUS1"]) self.assertEqual(parsed_args.channel, "PCAN_USBBUS1") - parsed_args, _, data_structs = parse_args(["-d", "100: Date: Tue, 23 Nov 2021 09:41:49 -0500 Subject: [PATCH 0758/1235] Updating neoVI documentation and docstrings (#1173) --- can/interfaces/ics_neovi/neovi_bus.py | 8 ++++---- doc/interfaces/neovi.rst | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index cdbf2a978..b4b7b4bb2 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -1,8 +1,8 @@ """ -ICS NeoVi interface module. +Intrepid Control Systems (ICS) neoVI interface module. python-ics is a Python wrapper around the API provided by Intrepid Control -Systems for communicating with their NeoVI range of devices. +Systems for communicating with their neoVI range of devices. Implementation references: * https://github.com/intrepidcs/python_ics @@ -30,7 +30,7 @@ import ics except ImportError as ie: logger.warning( - "You won't be able to use the ICS NeoVi can backend without the " + "You won't be able to use the ICS neoVI can backend without the " "python-ics module installed!: %s", ie, ) @@ -42,7 +42,7 @@ except ImportError as ie: logger.warning( - "Using ICS NeoVi can backend without the " + "Using ICS neoVI can backend without the " "filelock module installed may cause some issues!: %s", ie, ) diff --git a/doc/interfaces/neovi.rst b/doc/interfaces/neovi.rst index dbb753479..05423ac8e 100644 --- a/doc/interfaces/neovi.rst +++ b/doc/interfaces/neovi.rst @@ -1,9 +1,9 @@ -NEOVI Interface -================== +neoVI +===== .. warning:: - This ``ICS NeoVI`` documentation is a work in progress. Feedback and revisions + This ``ICS neoVI`` documentation is a work in progress. Feedback and revisions are most welcome! @@ -14,7 +14,7 @@ wrapper on Windows. Installation ------------ -This neovi interface requires the installation of the ICS neoVI DLL and python-ics +This neoVI interface requires the installation of the ICS neoVI DLL and python-ics package. - Download and install the Intrepid Product Drivers From 78c0db3c53bdeb2c4457c8e546e6bddd0efcf9a8 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Wed, 24 Nov 2021 12:34:55 -0500 Subject: [PATCH 0759/1235] Calling stop_all_periodic_tasks() in BusABC (#1174) Currently, only socketcan is calling stop_all_periodic_tasks() on shutdown and respecting the fact that periodic tasks should be stopped on Bus instance shutdown (according to BusABC.send_periodic docstring). --- can/bus.py | 1 + can/interfaces/canalystii.py | 1 + can/interfaces/cantact.py | 1 + can/interfaces/gs_usb.py | 1 + can/interfaces/iscan.py | 1 + can/interfaces/kvaser/canlib.py | 1 + can/interfaces/neousys/neousys.py | 1 + can/interfaces/nican.py | 1 + can/interfaces/nixnet.py | 1 + can/interfaces/robotell.py | 1 + can/interfaces/seeedstudio/seeedstudio.py | 1 + can/interfaces/serial/serial_can.py | 1 + can/interfaces/slcan.py | 1 + can/interfaces/socketcan/socketcan.py | 2 +- can/interfaces/systec/ucanbus.py | 1 + can/interfaces/udp_multicast/bus.py | 1 + can/interfaces/usb2can/usb2canInterface.py | 1 + can/interfaces/vector/canlib.py | 1 + can/interfaces/virtual.py | 1 + 19 files changed, 19 insertions(+), 1 deletion(-) diff --git a/can/bus.py b/can/bus.py index 1d90de2c4..b9ccfcfad 100644 --- a/can/bus.py +++ b/can/bus.py @@ -411,6 +411,7 @@ def shutdown(self) -> None: Called to carry out any interface specific cleanup required in shutting down a bus. """ + self.stop_all_periodic_tasks() def __enter__(self): return self diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index f75a06c30..395e4b399 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -204,6 +204,7 @@ def flush_tx_buffer(self, channel: Optional[int] = None) -> None: self.device.flush_tx_buffer(ch, float("infinity")) def shutdown(self) -> None: + super().shutdown() for channel in self.channels: self.device.stop(channel) self.device = None diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 3c538550c..056a64a6b 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -131,6 +131,7 @@ def send(self, msg, timeout=None): ) def shutdown(self): + super().shutdown() with error_check("Cannot shutdown interface"): self.interface.stop() diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 2490784ca..7731d797d 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -112,4 +112,5 @@ def _recv_internal( return msg, False def shutdown(self): + super().shutdown() self.gs_usb.stop() diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index 376ccc3fa..0e51ddd12 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -152,6 +152,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: iscan.isCAN_TransmitMessageEx(self.channel, ctypes.byref(raw_msg)) def shutdown(self) -> None: + super().shutdown() iscan.isCAN_CloseDevice(self.channel) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 4886c70fb..a951e39de 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -643,6 +643,7 @@ def flash(self, flash=True): log.error("Could not flash LEDs (%s)", e) def shutdown(self): + super().shutdown() # Wait for transmit queue to be cleared try: canWriteSync(self._write_handle, 100) diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index d4a89fd03..05e1a4418 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -233,6 +233,7 @@ def _neousys_status_cb(self, status) -> None: logger.info("%s _neousys_status_cb: %d", self.init_config, status) def shutdown(self): + super().shutdown() NEOUSYS_CANLIB.CAN_Stop(self.channel) @staticmethod diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 9c096cee3..0beeee429 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -375,4 +375,5 @@ def reset(self) -> None: def shutdown(self) -> None: """Close object.""" + super().shutdown() nican.ncCloseObject(self.handle) diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index a39884017..4e3caa2ad 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -221,6 +221,7 @@ def reset(self): def shutdown(self): """Close object.""" + super().shutdown() self.__session_send.flush() self.__session_receive.flush() diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index cb35aa774..d9d9740c5 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -367,6 +367,7 @@ def send(self, msg, timeout=None): ) def shutdown(self): + super().shutdown() self.serialPortOrig.close() def fileno(self): diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index f250e95a5..4d09ca0cd 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -120,6 +120,7 @@ def shutdown(self): """ Close the serial interface. """ + super().shutdown() self.ser.close() def init_frame(self, timeout=None): diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 8efd36bb9..c214d8559 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -100,6 +100,7 @@ def shutdown(self) -> None: """ Close the serial interface. """ + super().shutdown() self._ser.close() def send(self, msg: Message, timeout: Optional[float] = None) -> None: diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 035969d8b..63ea4ca42 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -264,6 +264,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: self._write(sendStr) def shutdown(self) -> None: + super().shutdown() self.close() with error_check("Could not close serial socket"): self.serialPortOrig.close() diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 3d25f7d4b..3506824a9 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -696,7 +696,7 @@ def __init__( def shutdown(self) -> None: """Stops all active periodic tasks and closes the socket.""" - self.stop_all_periodic_tasks() + super().shutdown() for channel, bcm_socket in self._bcm_sockets.items(): log.debug("Closing bcm socket for channel %s", channel) bcm_socket.close() diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index c1068494f..88224b856 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -312,6 +312,7 @@ def shutdown(self): """ Shuts down all CAN interfaces and hardware interface. """ + super().shutdown() try: self._ucan.shutdown() except Exception as exception: diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index df08af880..8fc286627 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -139,6 +139,7 @@ def shutdown(self) -> None: Never throws errors and only logs them. """ + super().shutdown() self._multicast.shutdown() @staticmethod diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 4c086adea..258853425 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -165,6 +165,7 @@ def shutdown(self): :raise cam.CanOperationError: is closing the connection did not work """ + super().shutdown() status = self.can.close(self.handle) if status != CanalError.SUCCESS: diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index e9f299e74..21c6d0f1f 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -614,6 +614,7 @@ def flush_tx_buffer(self) -> None: xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask) def shutdown(self) -> None: + super().shutdown() xldriver.xlDeactivateChannel(self.port_handle, self.mask) xldriver.xlClosePort(self.port_handle) xldriver.xlCloseDriver() diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 6c73b7ba8..a903435ac 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -122,6 +122,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: raise CanOperationError("Could not send message to one or more recipients") def shutdown(self) -> None: + super().shutdown() if self._open: self._open = False From 998615a3e94bcad4c8bf3c7289631c882d083932 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Thu, 25 Nov 2021 13:29:07 -0500 Subject: [PATCH 0760/1235] Adding support for gzipped ASC logging file (.asc.gz) (#1138) --- can/__init__.py | 2 +- can/io/__init__.py | 2 +- can/io/asc.py | 71 +++++++++++++++++++++++++++++++++++++++-- can/io/logger.py | 6 ++-- can/io/player.py | 6 ++-- test/logformats_test.py | 30 ++++++++++++++++- 6 files changed, 108 insertions(+), 9 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 618ef347f..c95b19ebf 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -25,7 +25,7 @@ ) from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync -from .io import ASCWriter, ASCReader +from .io import ASCWriter, ASCReader, GzipASCWriter, GzipASCReader from .io import BLFReader, BLFWriter from .io import CanutilsLogReader, CanutilsLogWriter from .io import CSVWriter, CSVReader diff --git a/can/io/__init__.py b/can/io/__init__.py index 0d3741b05..66e3a8c56 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -8,7 +8,7 @@ from .player import LogReader, MessageSync # Format specific -from .asc import ASCWriter, ASCReader +from .asc import ASCWriter, ASCReader, GzipASCWriter, GzipASCReader from .blf import BLFReader, BLFWriter from .canutils import CanutilsLogReader, CanutilsLogWriter from .csv import CSVWriter, CSVReader diff --git a/can/io/asc.py b/can/io/asc.py index d708de6e7..4e78d7528 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -5,13 +5,14 @@ - https://bitbucket.org/tobylorenz/vector_asc/src/47556e1a6d32c859224ca62d075e1efcc67fa690/src/Vector/ASC/tests/unittests/data/CAN_Log_Trigger_3_2.asc?at=master&fileviewer=file-view-default - under `test/data/logfile.asc` """ - -from typing import cast, Any, Generator, IO, List, Optional, Dict +import gzip +from typing import cast, Any, Generator, IO, List, Optional, Dict, Union from datetime import datetime import time import logging +from .. import typechecking from ..message import Message from ..listener import Listener from ..util import channel2int @@ -396,3 +397,69 @@ def on_message_received(self, msg: Message) -> None: data=" ".join(data), ) self.log_event(serialized, msg.timestamp) + + +class GzipASCReader(ASCReader): + """Gzipped version of :class:`~can.ASCReader`""" + + def __init__( + self, + file: Union[typechecking.FileLike, typechecking.StringPathLike], + base: str = "hex", + relative_timestamp: bool = True, + ): + """ + :param file: a path-like object or as file-like object to read from + If this is a file-like object, is has to opened in text + read mode, not binary read mode. + :param base: Select the base(hex or dec) of id and data. + If the header of the asc file contains base information, + this value will be overwritten. Default "hex". + :param relative_timestamp: Select whether the timestamps are + `relative` (starting at 0.0) or `absolute` (starting at + the system time). Default `True = relative`. + """ + self._fileobj = None + if file is not None and (hasattr(file, "read") and hasattr(file, "write")): + # file is None or some file-like object + self._fileobj = file + super(GzipASCReader, self).__init__( + gzip.open(file, mode="rt"), base, relative_timestamp + ) + + def stop(self) -> None: + super(GzipASCReader, self).stop() + if self._fileobj is not None: + self._fileobj.close() + + +class GzipASCWriter(ASCWriter): + """Gzipped version of :class:`~can.ASCWriter`""" + + def __init__( + self, + file: Union[typechecking.FileLike, typechecking.StringPathLike], + channel: int = 1, + compresslevel: int = 6, + ): + """ + :param file: a path-like object or as file-like object to write to + If this is a file-like object, is has to opened in text + write mode, not binary write mode. + :param channel: a default channel to use when the message does not + have a channel set + :param compresslevel: Gzip compresslevel, see + :class:`~gzip.GzipFile` for details. The default is 6. + """ + self._fileobj = None + if file is not None and (hasattr(file, "read") and hasattr(file, "write")): + # file is None or some file-like object + self._fileobj = file + super(GzipASCWriter, self).__init__( + gzip.open(file, mode="wt", compresslevel=compresslevel), channel + ) + + def stop(self) -> None: + super(GzipASCWriter, self).stop() + if self._fileobj is not None: + self._fileobj.close() diff --git a/can/io/logger.py b/can/io/logger.py index 1fc2a8487..3890e7432 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -21,7 +21,7 @@ from ..message import Message from ..listener import Listener from .generic import BaseIOHandler, FileIOMessageWriter -from .asc import ASCWriter +from .asc import ASCWriter, GzipASCWriter from .blf import BLFWriter from .canutils import CanutilsLogWriter from .csv import CSVWriter @@ -36,6 +36,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method The format is determined from the file format which can be one of: * .asc: :class:`can.ASCWriter` + * .asc.gz: :class:`can.CompressedASCWriter` * .blf :class:`can.BLFWriter` * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` @@ -54,6 +55,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method fetched_plugins = False message_writers = { ".asc": ASCWriter, + ".asc.gz": GzipASCWriter, ".blf": BLFWriter, ".csv": CSVWriter, ".db": SqliteWriter, @@ -83,7 +85,7 @@ def __new__( # type: ignore ) Logger.fetched_plugins = True - suffix = pathlib.PurePath(filename).suffix.lower() + suffix = "".join(s.lower() for s in pathlib.PurePath(filename).suffixes) try: return cast( Listener, Logger.message_writers[suffix](filename, *args, **kwargs) diff --git a/can/io/player.py b/can/io/player.py index e39d52c5e..f710e15d5 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -14,7 +14,7 @@ import can from .generic import BaseIOHandler, MessageReader -from .asc import ASCReader +from .asc import ASCReader, GzipASCReader from .blf import BLFReader from .canutils import CanutilsLogReader from .csv import CSVReader @@ -27,6 +27,7 @@ class LogReader(BaseIOHandler): The format is determined from the file format which can be one of: * .asc + * .asc.gz * .blf * .csv * .db @@ -49,6 +50,7 @@ class LogReader(BaseIOHandler): fetched_plugins = False message_readers = { ".asc": ASCReader, + ".asc.gz": GzipASCReader, ".blf": BLFReader, ".csv": CSVReader, ".db": SqliteReader, @@ -75,7 +77,7 @@ def __new__( # type: ignore ) LogReader.fetched_plugins = True - suffix = pathlib.PurePath(filename).suffix.lower() + suffix = "".join(s.lower() for s in pathlib.PurePath(filename).suffixes) try: return typing.cast( MessageReader, diff --git a/test/logformats_test.py b/test/logformats_test.py index 347785b4b..400bf369d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -11,7 +11,7 @@ TODO: correctly set preserves_channel and adds_default_channel """ - +import gzip import logging import unittest import tempfile @@ -555,6 +555,34 @@ def test_can_and_canfd_error_frames(self): self.assertMessagesEqual(actual, expected_messages) +class TestGzipASCFileFormat(ReaderWriterTest): + """Tests can.GzipASCWriter and can.GzipASCReader""" + + def _setup_instance(self): + super()._setup_instance_helper( + can.GzipASCWriter, + can.GzipASCReader, + binary_file=True, + check_comments=True, + preserves_channel=False, + adds_default_channel=0, + ) + + def assertIncludesComments(self, filename): + """ + Ensures that all comments are literally contained in the given file. + + :param filename: the path-like object to use + """ + if self.original_comments: + # read the entire outout file + with gzip.open(filename, "rt" if self.binary_file else "r") as file: + output_contents = file.read() + # check each, if they can be found in there literally + for comment in self.original_comments: + self.assertIn(comment, output_contents) + + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From 48e904039d5954dfa08aa85ab934bfcb6b5e23fb Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 1 Dec 2021 23:20:47 +0100 Subject: [PATCH 0761/1235] Improve issue templates (#1178) --- .github/ISSUE_TEMPLATE/bug_report.md | 29 ++++++++++++----------- .github/ISSUE_TEMPLATE/feature_request.md | 16 ++++++------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2f73865bb..0fe9b647c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,34 +7,35 @@ assignees: '' --- -**Describe the bug** -A clear and concise description of what the bug is. +### Describe the bug + -**To Reproduce** -Steps to reproduce the behavior (please add longer code examples below): +### To Reproduce + -**Expected behavior** -A clear and concise description of what you expected to happen. +### Expected behavior + -**Additional context** -OS-version: +### Additional context + +OS and version: Python version: python-can version: -python-can interface/s: +python-can interface/s (if applicable): - +
Traceback and logs - - + + - + ```python def func(): - return 'hello, world!' + return "hello, world!" ```
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 36014cde5..b3a153a27 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,14 +7,14 @@ assignees: '' --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +### Is your feature request related to a problem? Please describe. + -**Describe the solution you'd like** -A clear and concise description of what you want to happen. +### Describe the solution you'd like + -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. +### Describe alternatives you've considered + -**Additional context** -Add any other context or screenshots about the feature request here. +### Additional context + From 5b5bcc27c6fa283620ebee31fe4ac0c2f9aba0b0 Mon Sep 17 00:00:00 2001 From: Simon Tegelid Date: Sun, 5 Dec 2021 20:20:14 +0100 Subject: [PATCH 0762/1235] Add logconvert script (#1072) Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/logconvert.py | 66 +++++++++++++++++++++++++++++++++++++++ scripts/can_logconvert.py | 11 +++++++ test/test_scripts.py | 16 ++++++++++ 3 files changed, 93 insertions(+) create mode 100644 can/logconvert.py create mode 100644 scripts/can_logconvert.py diff --git a/can/logconvert.py b/can/logconvert.py new file mode 100644 index 000000000..6a2f52341 --- /dev/null +++ b/can/logconvert.py @@ -0,0 +1,66 @@ +""" +Convert a log file from one format to another. +""" + +import sys +import argparse +import errno + +from can import LogReader, Logger, SizedRotatingLogger + + +class ArgumentParser(argparse.ArgumentParser): + def error(self, message): + self.print_help(sys.stderr) + self.exit(errno.EINVAL, "%s: error: %s\n" % (self.prog, message)) + + +def main(): + parser = ArgumentParser( + description="Convert a log file from one format to another.", + ) + + parser.add_argument( + "-s", + "--file_size", + dest="file_size", + type=int, + help="Maximum file size in bytes. Rotate log file when size threshold is reached.", + default=None, + ) + + parser.add_argument( + "input", + metavar="INFILE", + type=str, + help="Input filename. The type is dependent on the suffix, see can.LogReader.", + ) + + parser.add_argument( + "output", + metavar="OUTFILE", + type=str, + help="Output filename. The type is dependent on the suffix, see can.Logger.", + ) + + args = parser.parse_args() + + with LogReader(args.input) as reader: + + if args.file_size: + logger = SizedRotatingLogger( + base_filename=args.output, max_bytes=args.file_size + ) + else: + logger = Logger(filename=args.output) + + with logger: + try: + for m in reader: # pylint: disable=not-an-iterable + logger(m) + except KeyboardInterrupt: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/scripts/can_logconvert.py b/scripts/can_logconvert.py new file mode 100644 index 000000000..3cd8839a2 --- /dev/null +++ b/scripts/can_logconvert.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +""" +See :mod:`can.logconvert`. +""" + +from can.logconvert import main + + +if __name__ == "__main__": + main() diff --git a/test/test_scripts.py b/test/test_scripts.py index 34017c29e..8efd70eff 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -103,6 +103,22 @@ def _import(self): return module +class TestLogconvertScript(CanScriptTest): + def _commands(self): + commands = [ + "python -m can.logconvert --help", + "python scripts/can_logconvert.py --help", + ] + if IS_UNIX: + commands += ["can_logconvert.py --help"] + return commands + + def _import(self): + import can.logconvert as module + + return module + + # TODO add #390 From 3b47d42f38c7ad5adea5c78bc482df9f6c106dac Mon Sep 17 00:00:00 2001 From: Nicolas Busser Date: Sun, 5 Dec 2021 20:21:05 +0100 Subject: [PATCH 0763/1235] Add socketcan parameter to ignore CAN error frames (#1128) --- can/interfaces/socketcan/socketcan.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 3506824a9..5355378ab 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -610,6 +610,7 @@ def __init__( local_loopback: bool = True, fd: bool = False, can_filters: Optional[CanFilters] = None, + ignore_rx_error_frames=False, **kwargs, ) -> None: """Creates a new socketcan bus. @@ -639,6 +640,8 @@ def __init__( If CAN-FD frames should be supported. :param can_filters: See :meth:`can.BusABC.set_filters`. + :param ignore_rx_error_frames: + If incoming error frames should be discarded. """ self.socket = create_socket() self.channel = channel @@ -671,11 +674,12 @@ def __init__( except socket.error as error: log.error("Could not enable CAN-FD frames (%s)", error) - # enable error frames - try: - self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF) - except socket.error as error: - log.error("Could not enable error frames (%s)", error) + if not ignore_rx_error_frames: + # enable error frames + try: + self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF) + except socket.error as error: + log.error("Could not enable error frames (%s)", error) # enable nanosecond resolution timestamping # we can always do this since From acc2ebdcb4408ab1675d0559025fcf35920b3cbe Mon Sep 17 00:00:00 2001 From: domologic Date: Mon, 6 Dec 2021 17:26:23 +0100 Subject: [PATCH 0764/1235] socketcand Backend for python-can (#1140) * merge most recent version of socketcand-interface branch * don't change loggine level * do not request python 3.7 * parse < hi > & < ok > messages * Initial upload: Doc for interfaces/socketcand * Python code example was not displayed * indent issues * minor changes * code optimization as proposed by felix * Typo * Monor doc improvement * license information added * reformatted using https://black.vercel.app/ * license removed Co-authored-by: Gerrit Telkamp --- can/interfaces/__init__.py | 1 + can/interfaces/socketcand/__init__.py | 9 ++ can/interfaces/socketcand/socketcand.py | 174 ++++++++++++++++++++++++ doc/interfaces/socketcand.rst | 117 ++++++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 can/interfaces/socketcand/__init__.py create mode 100644 can/interfaces/socketcand/socketcand.py create mode 100644 doc/interfaces/socketcand.rst diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 884665934..428e89899 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -25,6 +25,7 @@ "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"), "neousys": ("can.interfaces.neousys", "NeousysBus"), + "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), } try: diff --git a/can/interfaces/socketcand/__init__.py b/can/interfaces/socketcand/__init__.py new file mode 100644 index 000000000..442c06d8b --- /dev/null +++ b/can/interfaces/socketcand/__init__.py @@ -0,0 +1,9 @@ +""" +Interface to socketcand +see https://github.com/linux-can/socketcand + +Copyright (C) 2021 DOMOLOGIC GmbH +http://www.domologic.de +""" + +from .socketcand import SocketCanDaemonBus diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py new file mode 100644 index 000000000..6b74b59e0 --- /dev/null +++ b/can/interfaces/socketcand/socketcand.py @@ -0,0 +1,174 @@ +""" +Interface to socketcand +see https://github.com/linux-can/socketcand + +Authors: Marvin Seiler, Gerrit Telkamp + +Copyright (C) 2021 DOMOLOGIC GmbH +http://www.domologic.de +""" +import can +import socket +import select +import logging +import time +import traceback +from collections import deque + +log = logging.getLogger(__name__) + + +def convert_ascii_message_to_can_message(ascii_msg: str) -> can.Message: + if not ascii_msg.startswith("< frame ") or not ascii_msg.endswith(" >"): + log.warning(f"Could not parse ascii message: {ascii_msg}") + return None + else: + # frame_string = ascii_msg.removeprefix("< frame ").removesuffix(" >") + frame_string = ascii_msg[8:-2] + parts = frame_string.split(" ", 3) + can_id, timestamp = int(parts[0], 16), float(parts[1]) + + data = bytearray.fromhex(parts[2]) + can_dlc = len(data) + can_message = can.Message( + timestamp=timestamp, arbitration_id=can_id, data=data, dlc=can_dlc + ) + return can_message + + +def convert_can_message_to_ascii_message(can_message: can.Message) -> str: + # Note: socketcan bus adds extended flag, remote_frame_flag & error_flag to id + # not sure if that is necessary here + can_id = can_message.arbitration_id + # Note: seems like we cannot add CANFD_BRS (bitrate_switch) and CANFD_ESI (error_state_indicator) flags + data = can_message.data + length = can_message.dlc + bytes_string = " ".join("{:x}".format(x) for x in data[0:length]) + return f"< send {can_id:X} {length:X} {bytes_string} >" + + +def connect_to_server(s, host, port): + timeout_ms = 10000 + now = time.time() * 1000 + end_time = now + timeout_ms + while now < end_time: + try: + s.connect((host, port)) + return + except Exception as e: + log.warning(f"Failed to connect to server: {type(e)} Message: {e}") + now = time.time() * 1000 + raise TimeoutError( + f"connect_to_server: Failed to connect server for {timeout_ms} ms" + ) + + +class SocketCanDaemonBus(can.BusABC): + def __init__(self, channel, host, port, can_filters=None, **kwargs): + self.__host = host + self.__port = port + self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.__message_buffer = deque() + self.__receive_buffer = "" # i know string is not the most efficient here + connect_to_server(self.__socket, self.__host, self.__port) + self._expect_msg("< hi >") + + log.info( + f"SocketCanDaemonBus: connected with address {self.__socket.getsockname()}" + ) + self._tcp_send(f"< open {channel} >") + self._expect_msg("< ok >") + self._tcp_send(f"< rawmode >") + self._expect_msg("< ok >") + super().__init__(channel=channel, can_filters=can_filters) + + def _recv_internal(self, timeout): + if len(self.__message_buffer) != 0: + can_message = self.__message_buffer.popleft() + return can_message, False + + try: + # get all sockets that are ready (can be a list with a single value + # being self.socket or an empty list if self.socket is not ready) + ready_receive_sockets, _, _ = select.select( + [self.__socket], [], [], timeout + ) + except socket.error as exc: + # something bad happened (e.g. the interface went down) + log.error(f"Failed to receive: {exc}") + raise can.CanError(f"Failed to receive: {exc}") + + try: + if not ready_receive_sockets: + # socket wasn't readable or timeout occurred + log.debug("Socket not ready") + return None, False + + ascii_msg = self.__socket.recv(1024).decode( + "ascii" + ) # may contain multiple messages + self.__receive_buffer += ascii_msg + log.debug(f"Received Ascii Message: {ascii_msg}") + buffer_view = self.__receive_buffer + chars_processed_successfully = 0 + while True: + if len(buffer_view) == 0: + break + + start = buffer_view.find("<") + if start == -1: + log.warning( + f"Bad data: No opening < found => discarding entire buffer '{buffer_view}'" + ) + chars_processed_successfully = len(self.__receive_buffer) + break + end = buffer_view.find(">") + if end == -1: + log.warning("Got incomplete message => waiting for more data") + if len(buffer_view) > 200: + log.warning( + "Incomplete message exceeds 200 chars => Discarding" + ) + chars_processed_successfully = len(self.__receive_buffer) + break + chars_processed_successfully += end + 1 + single_message = buffer_view[start : end + 1] + parsed_can_message = convert_ascii_message_to_can_message( + single_message + ) + if parsed_can_message is None: + log.warning(f"Invalid Frame: {single_message}") + else: + self.__message_buffer.append(parsed_can_message) + buffer_view = buffer_view[end + 1 :] + + self.__receive_buffer = self.__receive_buffer[ + chars_processed_successfully + 1 : + ] + can_message = ( + None + if len(self.__message_buffer) == 0 + else self.__message_buffer.popleft() + ) + return can_message, False + + except Exception as exc: + log.error(f"Failed to receive: {exc} {traceback.format_exc()}") + raise can.CanError(f"Failed to receive: {exc} {traceback.format_exc()}") + + def _tcp_send(self, msg: str): + log.debug(f"Sending TCP Message: '{msg}'") + self.__socket.sendall(msg.encode("ascii")) + + def _expect_msg(self, msg): + ascii_msg = self.__socket.recv(256).decode("ascii") + if not ascii_msg == msg: + raise can.CanError(f"{msg} message expected!") + + def send(self, msg, timeout=None): + ascii_msg = convert_can_message_to_ascii_message(msg) + self._tcp_send(ascii_msg) + + def shutdown(self): + self.stop_all_periodic_tasks() + self.__socket.close() diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst new file mode 100644 index 000000000..e50f134e1 --- /dev/null +++ b/doc/interfaces/socketcand.rst @@ -0,0 +1,117 @@ +.. _socketcand_doc: + +socketcand Interface +==================== +`Socketcand `__ is part of the +`Linux-CAN `__ project, providing a +Network-to-CAN bridge as Linux damon. It implements a specific +`TCP/IP based communication protocol `__ +to transfer CAN frames and control commands. + +The main advantage compared to UDP-based protocols (e.g. virtual interface) +is, that TCP guarantees delivery and that the message order is kept. + +Here is a small example dumping all can messages received by a socketcand +daemon running on a remote Raspberry Pi: + +.. code-block:: python + + import can + + bus = can.interface.Bus(bustype='socketcand', host="10.0.16.15", port=29536, channel="can0") + + # loop until Ctrl-C + try: + while True: + msg = bus.recv() + print (msg) + except KeyboardInterrupt: + pass + +The output may look like this: +:: + Timestamp: 1637791111.209224 ID: 000006fd X Rx DLC: 8 c4 10 e3 2d 96 ff 25 6b + Timestamp: 1637791111.233951 ID: 000001ad X Rx DLC: 4 4d 47 c7 64 + Timestamp: 1637791111.409415 ID: 000005f7 X Rx DLC: 8 86 de e6 0f 42 55 5d 39 + Timestamp: 1637791111.434377 ID: 00000665 X Rx DLC: 8 97 96 51 0f 23 25 fc 28 + Timestamp: 1637791111.609763 ID: 0000031d X Rx DLC: 8 16 27 d8 3d fe d8 31 24 + Timestamp: 1637791111.634630 ID: 00000587 X Rx DLC: 8 4e 06 85 23 6f 81 2b 65 + +Socketcand Quickstart +--------------------- + +The following section will show how to get the stuff installed on a Raspberry Pi with a MCP2515-based +CAN interface, e.g. available from `Waveshare `__. +However, it will also work with any other socketcan device. + +Install CAN Interface for a MCP2515 based interface on a Raspberry Pi +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Add the following lines to ``/boot/config.txt``. Please take care on the frequency of the crystal on your MCP2515 board: +:: + dtparam=spi=on + dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25,spimaxfrequency=1000000 + +Reboot after ``/boot/config.txt`` has been modified. + + +Enable socketcan for can0 +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create config file for systemd-networkd to start the socketcan interface automatically: + +.. code-block:: bash + + cat >/etc/systemd/network/80-can.network <<'EOT' + [Match] + Name=can0 + [CAN] + BitRate=250K + RestartSec=100ms + EOT + +Enable ``systemd-networkd`` on reboot and start it immediately (if it was not already startet): + +.. code-block:: bash + + sudo systemctl enable systemd-networkd + sudo systemctl start systemd-networkd + + +Build socketcand from source +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + # autoconf is needed to build socketcand + sudo apt-get install -y autoconf + # clone & build sources + git clone https://github.com/linux-can/socketcand.git + cd socketcand + ./autogen.sh + ./configure + make + + +Install socketcand +~~~~~~~~~~~~~~~~~~ +.. code-block:: bash + + make install + + +Run socketcand +~~~~~~~~~~~~~~ +.. code-block:: bash + + ./socketcand -v -i can0 + +During start, socketcand will prompt its IP address and port it listens to: +:: + Verbose output activated + + Using network interface 'eth0' + Listen adress is 10.0.16.15 + Broadcast adress is 10.0.255.255 + creating broadcast thread... + binding socket to 10.0.16.15:29536 From b886203701397e12e6d43a3d4b4ee32f07817eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20Burgos=20Maci=C3=A1?= Date: Tue, 7 Dec 2021 20:03:25 +0100 Subject: [PATCH 0765/1235] Ixxat CAN FD support (#1119) * Added: Ixxat interface copy before start changing for canfd. * Changed: ixxat_fd adapting for usage with vcinpl2.dll instead of vcinpl.dll. * Fixed: ixxat fd receive function with timeout = 0 was causing to receive always last frame. Now, if function does not return VCI_OK, no new message is returned. * Added: ixxat_fd baudrate from user options instead of hardcoded. * Added: ixxat_fd channel capabilities check. * Added: Myself to contributors list. Added: Documentation for ixxat_fd usage. * Related to ixxat_fd: Added: Format with black. Added: test to improve coverage. Changed: Default baudrate for data segment is now 2 Mbps. * Changed: renamed files before "merging" ixxat and ixxat_fd interfaces. * Changed: Merged structures, exceptions and constants from ixxat-fd to ixxat. * Added: Ixxat interface proxy class delegating to related implementation where required. Changed: Documentation updated accordingly. * Changed: Tests for CAN-FD in ixxat updated according to last changes. * Changed: Suggested changes from pull request review. * Changed: Suggested changes from pull request review. * Changed: Type hint, typing.Tuple instead of tuple. * Changed: TSEG1, TSEG2 and SJW parameters available as optional advanced settings. Changed: Deprecated parameters UniqueHardwareId, rxFifoSize, txFifoSize (use unique_hardware_id, rx_fifo_size, tx_fifo_size instead). Changed: Replaced ctypes.c_long return value for ctypes functions returning HRESULT with an alias "hresult_type" that is a ctypes.c_ulong. * Update doc/interfaces/ixxat.rst Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Changed: Documentation. * Changed: Ixxat extended parameter default from False to True. Now it is also considered in "no fd" implementation. * Changed: Ixxat parameter unique_hardware_id type hint is now optional. * Changed: Ixxat doc comment types removed (let Sphinx derive them from the method signature). * Changed: Updated Ixxat tests to use new argument names. * Fixed: Missing **kwargs parameter in vcinpl and vcinpl2. Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- CONTRIBUTORS.txt | 1 + can/interfaces/ixxat/__init__.py | 5 +- can/interfaces/ixxat/canlib.py | 905 +++----------------- can/interfaces/ixxat/canlib_vcinpl.py | 877 ++++++++++++++++++++ can/interfaces/ixxat/canlib_vcinpl2.py | 1043 ++++++++++++++++++++++++ can/interfaces/ixxat/constants.py | 109 +++ can/interfaces/ixxat/structures.py | 158 +++- doc/interfaces/ixxat.rst | 24 +- test/test_interface_ixxat.py | 8 +- test/test_interface_ixxat_fd.py | 62 ++ 10 files changed, 2364 insertions(+), 828 deletions(-) create mode 100644 can/interfaces/ixxat/canlib_vcinpl.py create mode 100644 can/interfaces/ixxat/canlib_vcinpl2.py create mode 100644 test/test_interface_ixxat_fd.py diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b7ac9fbd4..c4bd2b76a 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -28,3 +28,4 @@ Jan Goeteyn "ykzheng" Lear Corporation Nick Black +Francisco Javier Burgos Macia diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index fc2cae0f3..347caed50 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -4,4 +4,7 @@ Copyright (C) 2016 Giuseppe Corbelli """ -from can.interfaces.ixxat.canlib import IXXATBus, get_ixxat_hwids +from can.interfaces.ixxat.canlib import IXXATBus +from can.interfaces.ixxat.canlib_vcinpl import ( + get_ixxat_hwids, +) # import this and not the one from vcinpl2 for backward compatibility diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index ced840ff3..4dc0d3e6e 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,848 +1,147 @@ -""" -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems - -TODO: We could implement this interface such that setting other filters - could work when the initial filters were set to zero using the - software fallback. Or could the software filters even be changed - after the connection was opened? We need to document that bahaviour! - See also the NICAN interface. - -""" - -import ctypes -import functools -import logging -import sys -from typing import Optional +import can.interfaces.ixxat.canlib_vcinpl as vcinpl +import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 from can import BusABC, Message -from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError -from can.broadcastmanager import ( - LimitedDurationCyclicSendTaskABC, - RestartableCyclicTaskABC, -) -from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT - -from . import constants, structures -from .exceptions import * - -__all__ = [ - "VCITimeout", - "VCIError", - "VCIBusOffError", - "VCIDeviceNotFoundError", - "IXXATBus", - "vciFormatError", -] - -log = logging.getLogger("can.ixxat") - -from time import perf_counter as _timer_function - -# Hack to have vciFormatError as a free function, see below -vciFormatError = None - -# main ctypes instance -_canlib = None -# TODO: Use ECI driver for linux -if sys.platform == "win32" or sys.platform == "cygwin": - try: - _canlib = CLibrary("vcinpl.dll") - except Exception as e: - log.warning("Cannot load IXXAT vcinpl library: %s", e) -else: - # Will not work on other systems, but have it importable anyway for - # tests/sphinx - log.warning("IXXAT VCI library does not work on %s platform", sys.platform) - - -def __vciFormatErrorExtended(library_instance, function, HRESULT, arguments): - """Format a VCI error and attach failed function, decoded HRESULT and arguments - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :param arguments: - Arbitrary arguments tuple - :return: - Formatted string - """ - # TODO: make sure we don't generate another exception - return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, HRESULT), arguments - ) - - -def __vciFormatError(library_instance, function, HRESULT): - """Format a VCI error and attach failed function and decoded HRESULT - :param CLibrary library_instance: - Mapped instance of IXXAT vcinpl library - :param callable function: - Failed function - :param HRESULT HRESULT: - HRESULT returned by vcinpl call - :return: - Formatted string - """ - buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) - ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) - library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) - return "function {} failed ({})".format( - function._name, buf.value.decode("utf-8", "replace") - ) - - -def __check_status(result, function, arguments): - """ - Check the result of a vcinpl function call and raise appropriate exception - in case of an error. Used as errcheck function when mapping C functions - with ctypes. - :param result: - Function call numeric result - :param callable function: - Called function - :param arguments: - Arbitrary arguments tuple - :raise: - :class:VCITimeout - :class:VCIRxQueueEmptyError - :class:StopIteration - :class:VCIError - """ - if isinstance(result, int): - # Real return value is an unsigned long - result = ctypes.c_ulong(result).value - - if result == constants.VCI_E_TIMEOUT: - raise VCITimeout("Function {} timed out".format(function._name)) - elif result == constants.VCI_E_RXQUEUE_EMPTY: - raise VCIRxQueueEmptyError() - elif result == constants.VCI_E_NO_MORE_ITEMS: - raise StopIteration() - elif result == constants.VCI_E_ACCESSDENIED: - pass # not a real error, might happen if another program has initialized the bus - elif result != constants.VCI_OK: - raise VCIError(vciFormatError(function, result)) - - return result - - -try: - # Map all required symbols and initialize library --------------------------- - # HRESULT VCIAPI vciInitialize ( void ); - _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) - - # void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); - _canlib.map_symbol( - "vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32) - ) - # Hack to have vciFormatError as a free function - vciFormatError = functools.partial(__vciFormatError, _canlib) - - # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); - _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); - _canlib.map_symbol( - "vciEnumDeviceNext", - ctypes.c_long, - (HANDLE, structures.PVCIDEVICEINFO), - __check_status, - ) - - # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); - _canlib.map_symbol( - "vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status - ) - # HRESULT vciDeviceClose( HANDLE hDevice ) - _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) - - # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); - _canlib.map_symbol( - "canChannelOpen", - ctypes.c_long, - (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), - __check_status, - ) - # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); - _canlib.map_symbol( - "canChannelInitialize", - ctypes.c_long, - (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), - __check_status, - ) - # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); - _canlib.map_symbol( - "canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status - ) - # HRESULT canChannelClose( HANDLE hChannel ) - _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE,), __check_status) - # EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); - _canlib.map_symbol( - "canChannelReadMessage", - ctypes.c_long, - (HANDLE, ctypes.c_uint32, structures.PCANMSG), - __check_status, - ) - # HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); - _canlib.map_symbol( - "canChannelPeekMessage", - ctypes.c_long, - (HANDLE, structures.PCANMSG), - __check_status, - ) - # HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); - _canlib.map_symbol( - "canChannelWaitTxEvent", - ctypes.c_long, - (HANDLE, ctypes.c_uint32), - __check_status, - ) - # HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); - _canlib.map_symbol( - "canChannelWaitRxEvent", - ctypes.c_long, - (HANDLE, ctypes.c_uint32), - __check_status, - ) - # HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); - _canlib.map_symbol( - "canChannelPostMessage", - ctypes.c_long, - (HANDLE, structures.PCANMSG), - __check_status, - ) - # HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); - _canlib.map_symbol( - "canChannelSendMessage", - ctypes.c_long, - (HANDLE, ctypes.c_uint32, structures.PCANMSG), - __check_status, - ) - - # EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); - _canlib.map_symbol( - "canControlOpen", - ctypes.c_long, - (HANDLE, ctypes.c_uint32, PHANDLE), - __check_status, - ) - # EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); - _canlib.map_symbol( - "canControlInitialize", - ctypes.c_long, - (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), - __check_status, - ) - # EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) - # EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); - _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) - # EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); - _canlib.map_symbol( - "canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status - ) - # EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); - _canlib.map_symbol( - "canControlGetStatus", - ctypes.c_long, - (HANDLE, structures.PCANLINESTATUS), - __check_status, - ) - # EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); - _canlib.map_symbol( - "canControlGetCaps", - ctypes.c_long, - (HANDLE, structures.PCANCAPABILITIES), - __check_status, - ) - # EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); - _canlib.map_symbol( - "canControlSetAccFilter", - ctypes.c_long, - (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), - __check_status, - ) - # EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); - _canlib.map_symbol( - "canControlAddFilterIds", - ctypes.c_long, - (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), - __check_status, - ) - # EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); - _canlib.map_symbol( - "canControlRemFilterIds", - ctypes.c_long, - (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), - __check_status, - ) - # EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); - _canlib.map_symbol( - "canSchedulerOpen", - ctypes.c_long, - (HANDLE, ctypes.c_uint32, PHANDLE), - __check_status, - ) - # EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); - _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE,), __check_status) - # EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); - _canlib.map_symbol( - "canSchedulerGetCaps", - ctypes.c_long, - (HANDLE, structures.PCANCAPABILITIES), - __check_status, - ) - # EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); - _canlib.map_symbol( - "canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status - ) - # EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); - _canlib.map_symbol( - "canSchedulerAddMessage", - ctypes.c_long, - (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), - __check_status, - ) - # EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol( - "canSchedulerRemMessage", - ctypes.c_long, - (HANDLE, ctypes.c_uint32), - __check_status, - ) - # EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); - _canlib.map_symbol( - "canSchedulerStartMessage", - ctypes.c_long, - (HANDLE, ctypes.c_uint32, ctypes.c_uint16), - __check_status, - ) - # EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); - _canlib.map_symbol( - "canSchedulerStopMessage", - ctypes.c_long, - (HANDLE, ctypes.c_uint32), - __check_status, - ) - _canlib.vciInitialize() -except AttributeError: - # In case _canlib == None meaning we're not on win32/no lib found - pass -except Exception as e: - log.warning("Could not initialize IXXAT VCI library: %s", e) -# --------------------------------------------------------------------------- - - -CAN_INFO_MESSAGES = { - constants.CAN_INFO_START: "CAN started", - constants.CAN_INFO_STOP: "CAN stopped", - constants.CAN_INFO_RESET: "CAN reset", -} - -CAN_ERROR_MESSAGES = { - constants.CAN_ERROR_STUFF: "CAN bit stuff error", - constants.CAN_ERROR_FORM: "CAN form error", - constants.CAN_ERROR_ACK: "CAN acknowledgment error", - constants.CAN_ERROR_BIT: "CAN bit error", - constants.CAN_ERROR_CRC: "CAN CRC error", - constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", -} - -CAN_STATUS_FLAGS = { - constants.CAN_STATUS_TXPEND: "transmission pending", - constants.CAN_STATUS_OVRRUN: "data overrun occurred", - constants.CAN_STATUS_ERRLIM: "error warning limit exceeded", - constants.CAN_STATUS_BUSOFF: "bus off", - constants.CAN_STATUS_ININIT: "init mode active", - constants.CAN_STATUS_BUSCERR: "bus coupling error", -} -# ---------------------------------------------------------------------------- +from typing import Optional class IXXATBus(BusABC): """The CAN Bus implemented for the IXXAT interface. - .. warning:: - - This interface does implement efficient filtering of messages, but - the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` - using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` - does not work. + Based on the C implementation of IXXAT, two different dlls are provided by IXXAT, one to work with CAN, + the other with CAN-FD. + This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2) class depending on fd user option. """ - CHANNEL_BITRATES = { - 0: { - 10000: constants.CAN_BT0_10KB, - 20000: constants.CAN_BT0_20KB, - 50000: constants.CAN_BT0_50KB, - 100000: constants.CAN_BT0_100KB, - 125000: constants.CAN_BT0_125KB, - 250000: constants.CAN_BT0_250KB, - 500000: constants.CAN_BT0_500KB, - 666000: constants.CAN_BT0_667KB, - 666666: constants.CAN_BT0_667KB, - 666667: constants.CAN_BT0_667KB, - 667000: constants.CAN_BT0_667KB, - 800000: constants.CAN_BT0_800KB, - 1000000: constants.CAN_BT0_1000KB, - }, - 1: { - 10000: constants.CAN_BT1_10KB, - 20000: constants.CAN_BT1_20KB, - 50000: constants.CAN_BT1_50KB, - 100000: constants.CAN_BT1_100KB, - 125000: constants.CAN_BT1_125KB, - 250000: constants.CAN_BT1_250KB, - 500000: constants.CAN_BT1_500KB, - 666000: constants.CAN_BT1_667KB, - 666666: constants.CAN_BT1_667KB, - 666667: constants.CAN_BT1_667KB, - 667000: constants.CAN_BT1_667KB, - 800000: constants.CAN_BT1_800KB, - 1000000: constants.CAN_BT1_1000KB, - }, - } - - def __init__(self, channel, can_filters=None, **kwargs): + def __init__( + self, + channel: int, + can_filters=None, + receive_own_messages: bool = False, + unique_hardware_id: Optional[int] = None, + extended: bool = True, + fd: bool = False, + rx_fifo_size: int = None, + tx_fifo_size: int = None, + bitrate: int = 500000, + data_bitrate: int = 2000000, + sjw_abr: int = None, + tseg1_abr: int = None, + tseg2_abr: int = None, + sjw_dbr: int = None, + tseg1_dbr: int = None, + tseg2_dbr: int = None, + ssp_dbr: int = None, + **kwargs + ): """ - :param int channel: + :param channel: The Channel id to create this bus with. - :param list can_filters: + :param can_filters: See :meth:`can.BusABC.set_filters`. - :param bool receive_own_messages: + :param receive_own_messages: Enable self-reception of sent messages. - :param int UniqueHardwareId: + :param unique_hardware_id: UniqueHardwareId to connect (optional, will use the first found if not supplied) - :param int bitrate: - Channel bitrate in bit/s - """ - if _canlib is None: - raise CanInterfaceNotImplementedError( - "The IXXAT VCI library has not been initialized. Check the logs for more details." - ) - log.info("CAN Filters: %s", can_filters) - log.info("Got configuration of: %s", kwargs) - # Configuration options - bitrate = kwargs.get("bitrate", 500000) - UniqueHardwareId = kwargs.get("UniqueHardwareId", None) - rxFifoSize = kwargs.get("rxFifoSize", 16) - txFifoSize = kwargs.get("txFifoSize", 16) - self._receive_own_messages = kwargs.get("receive_own_messages", False) - # Usually comes as a string from the config file - channel = int(channel) - - if bitrate not in self.CHANNEL_BITRATES[0]: - raise ValueError("Invalid bitrate {}".format(bitrate)) - - if rxFifoSize <= 0: - raise ValueError("rxFifoSize must be > 0") - - if txFifoSize <= 0: - raise ValueError("txFifoSize must be > 0") - - if channel < 0: - raise ValueError("channel number must be >= 0") - - self._device_handle = HANDLE() - self._device_info = structures.VCIDEVICEINFO() - self._control_handle = HANDLE() - self._channel_handle = HANDLE() - self._channel_capabilities = structures.CANCAPABILITIES() - self._message = structures.CANMSG() - self._payload = (ctypes.c_byte * 8)() - - # Search for supplied device - if UniqueHardwareId is None: - log.info("Searching for first available device") - else: - log.info("Searching for unique HW ID %s", UniqueHardwareId) - _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) - while True: - try: - _canlib.vciEnumDeviceNext( - self._device_handle, ctypes.byref(self._device_info) - ) - except StopIteration: - if UniqueHardwareId is None: - raise VCIDeviceNotFoundError( - "No IXXAT device(s) connected or device(s) in use by other process(es)." - ) - else: - raise VCIDeviceNotFoundError( - "Unique HW ID {} not connected or not available.".format( - UniqueHardwareId - ) - ) - else: - if (UniqueHardwareId is None) or ( - self._device_info.UniqueHardwareId.AsChar - == bytes(UniqueHardwareId, "ascii") - ): - break - else: - log.debug( - "Ignoring IXXAT with hardware id '%s'.", - self._device_info.UniqueHardwareId.AsChar.decode("ascii"), - ) - _canlib.vciEnumDeviceClose(self._device_handle) + :param extended: + Default True, enables the capability to use extended IDs. - try: - _canlib.vciDeviceOpen( - ctypes.byref(self._device_info.VciObjectId), - ctypes.byref(self._device_handle), - ) - except Exception as exception: - raise CanInitializationError(f"Could not open device: {exception}") + :param fd: + Default False, enables CAN-FD usage. - log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) + :param rx_fifo_size: + Receive fifo size (default 1024 for fd, else 16) - log.info( - "Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", - channel, - rxFifoSize, - txFifoSize, - ) + :param tx_fifo_size: + Transmit fifo size (default 128 for fd, else 16) - try: - _canlib.canChannelOpen( - self._device_handle, - channel, - constants.FALSE, - ctypes.byref(self._channel_handle), - ) - except Exception as exception: - raise CanInitializationError( - f"Could not open and initialize channel: {exception}" - ) + :param bitrate: + Channel bitrate in bit/s - # Signal TX/RX events when at least one frame has been handled - _canlib.canChannelInitialize(self._channel_handle, rxFifoSize, 1, txFifoSize, 1) - _canlib.canChannelActivate(self._channel_handle, constants.TRUE) + :param data_bitrate: + Channel bitrate in bit/s (only in CAN-Fd if baudrate switch enabled). - log.info("Initializing control %d bitrate %d", channel, bitrate) - _canlib.canControlOpen( - self._device_handle, channel, ctypes.byref(self._control_handle) - ) - _canlib.canControlInitialize( - self._control_handle, - constants.CAN_OPMODE_STANDARD - | constants.CAN_OPMODE_EXTENDED - | constants.CAN_OPMODE_ERRFRAME, - self.CHANNEL_BITRATES[0][bitrate], - self.CHANNEL_BITRATES[1][bitrate], - ) - _canlib.canControlGetCaps( - self._control_handle, ctypes.byref(self._channel_capabilities) - ) + :param sjw_abr: + Bus timing value sample jump width (arbitration). Only takes effect with fd enabled. - # With receive messages, this field contains the relative reception time of - # the message in ticks. The resolution of a tick can be calculated from the fields - # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: - # frequency [1/s] = dwClockFreq / dwTscDivisor - # We explicitly cast to float for Python 2.x users - self._tick_resolution = float( - self._channel_capabilities.dwClockFreq - / self._channel_capabilities.dwTscDivisor - ) + :param tseg1_abr: + Bus timing value tseg1 (arbitration). Only takes effect with fd enabled. - # Setup filters before starting the channel - if can_filters: - log.info("The IXXAT VCI backend is filtering messages") - # Disable every message coming in - for extended in (0, 1): - _canlib.canControlSetAccFilter( - self._control_handle, - extended, - constants.CAN_ACC_CODE_NONE, - constants.CAN_ACC_MASK_NONE, - ) - for can_filter in can_filters: - # Filters define what messages are accepted - code = int(can_filter["can_id"]) - mask = int(can_filter["can_mask"]) - extended = can_filter.get("extended", False) - _canlib.canControlAddFilterIds( - self._control_handle, 1 if extended else 0, code << 1, mask << 1 - ) - log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) + :param tseg2_abr: + Bus timing value tseg2 (arbitration). Only takes effect with fd enabled. - # Start the CAN controller. Messages will be forwarded to the channel - _canlib.canControlStart(self._control_handle, constants.TRUE) + :param sjw_dbr: + Bus timing value sample jump width (data). Only takes effect with fd and baudrate switch enabled. - # For cyclic transmit list. Set when .send_periodic() is first called - self._scheduler = None - self._scheduler_resolution = None - self.channel = channel + :param tseg1_dbr: + Bus timing value tseg1 (data). Only takes effect with fd and bitrate switch enabled. - # Usually you get back 3 messages like "CAN initialized" ecc... - # Clear the FIFO by filter them out with low timeout - for _ in range(rxFifoSize): - try: - _canlib.canChannelReadMessage( - self._channel_handle, 0, ctypes.byref(self._message) - ) - except (VCITimeout, VCIRxQueueEmptyError): - break + :param tseg2_dbr: + Bus timing value tseg2 (data). Only takes effect with fd and bitrate switch enabled. - super().__init__(channel=channel, can_filters=None, **kwargs) + :param ssp_dbr: + Secondary sample point (data). Only takes effect with fd and bitrate switch enabled. - def _inWaiting(self): - try: - _canlib.canChannelWaitRxEvent(self._channel_handle, 0) - except VCITimeout: - return 0 + """ + if fd: + if rx_fifo_size is None: + rx_fifo_size = 1024 + if tx_fifo_size is None: + tx_fifo_size = 128 + self.bus = vcinpl2.IXXATBus( + channel=channel, + can_filters=can_filters, + receive_own_messages=receive_own_messages, + unique_hardware_id=unique_hardware_id, + extended=extended, + rx_fifo_size=rx_fifo_size, + tx_fifo_size=tx_fifo_size, + bitrate=bitrate, + data_bitrate=data_bitrate, + sjw_abr=sjw_abr, + tseg1_abr=tseg1_abr, + tseg2_abr=tseg2_abr, + sjw_dbr=sjw_dbr, + tseg1_dbr=tseg1_dbr, + tseg2_dbr=tseg2_dbr, + ssp_dbr=ssp_dbr, + **kwargs + ) else: - return 1 + if rx_fifo_size is None: + rx_fifo_size = 16 + if tx_fifo_size is None: + tx_fifo_size = 16 + self.bus = vcinpl.IXXATBus( + channel=channel, + can_filters=can_filters, + receive_own_messages=receive_own_messages, + unique_hardware_id=unique_hardware_id, + extended=extended, + rx_fifo_size=rx_fifo_size, + tx_fifo_size=tx_fifo_size, + bitrate=bitrate, + **kwargs + ) def flush_tx_buffer(self): """Flushes the transmit buffer on the IXXAT""" - # TODO #64: no timeout? - _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) + return self.bus.flush_tx_buffer() def _recv_internal(self, timeout): """Read a message from IXXAT device.""" - - # TODO: handling CAN error messages? - data_received = False - - if timeout == 0: - # Peek without waiting - try: - _canlib.canChannelPeekMessage( - self._channel_handle, ctypes.byref(self._message) - ) - except (VCITimeout, VCIRxQueueEmptyError): - return None, True - else: - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True - else: - # Wait if no message available - if timeout is None or timeout < 0: - remaining_ms = constants.INFINITE - t0 = None - else: - timeout_ms = int(timeout * 1000) - remaining_ms = timeout_ms - t0 = _timer_function() - - while True: - try: - _canlib.canChannelReadMessage( - self._channel_handle, remaining_ms, ctypes.byref(self._message) - ) - except (VCITimeout, VCIRxQueueEmptyError): - # Ignore the 2 errors, the timeout is handled manually with the _timer_function() - pass - else: - # See if we got a data or info/error messages - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True - break - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: - log.info( - CAN_INFO_MESSAGES.get( - self._message.abData[0], - "Unknown CAN info message code {}".format( - self._message.abData[0] - ), - ) - ) - - elif ( - self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR - ): - log.warning( - CAN_ERROR_MESSAGES.get( - self._message.abData[0], - "Unknown CAN error message code {}".format( - self._message.abData[0] - ), - ) - ) - - elif ( - self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS - ): - log.info(_format_can_status(self._message.abData[0])) - if self._message.abData[0] & constants.CAN_STATUS_BUSOFF: - raise VCIBusOffError() - - elif ( - self._message.uMsgInfo.Bits.type - == constants.CAN_MSGTYPE_TIMEOVR - ): - pass - else: - log.warning("Unexpected message info type") - - if t0 is not None: - remaining_ms = timeout_ms - int((_timer_function() - t0) * 1000) - if remaining_ms < 0: - break - - if not data_received: - # Timed out / can message type is not DATA - return None, True - - # The _message.dwTime is a 32bit tick value and will overrun, - # so expect to see the value restarting from 0 - rx_msg = Message( - timestamp=self._message.dwTime - / self._tick_resolution, # Relative time in s - is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr), - is_extended_id=bool(self._message.uMsgInfo.Bits.ext), - arbitration_id=self._message.dwMsgId, - dlc=self._message.uMsgInfo.Bits.dlc, - data=self._message.abData[: self._message.uMsgInfo.Bits.dlc], - channel=self.channel, - ) - - return rx_msg, True + return self.bus._recv_internal(timeout) def send(self, msg: Message, timeout: Optional[float] = None) -> None: - """ - Sends a message on the bus. The interface may buffer the message. - - :param msg: - The message to send. - :param timeout: - Timeout after some time. - :raise: - :class:CanTimeoutError - :class:CanOperationError - """ - # This system is not designed to be very efficient - message = structures.CANMSG() - message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 - message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 - message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 - message.dwMsgId = msg.arbitration_id - if msg.dlc: - message.uMsgInfo.Bits.dlc = msg.dlc - adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) - ctypes.memmove(message.abData, adapter, len(msg.data)) - - if timeout: - _canlib.canChannelSendMessage( - self._channel_handle, int(timeout * 1000), message - ) - - else: - _canlib.canChannelPostMessage(self._channel_handle, message) + return self.bus.send(msg, timeout) def _send_periodic_internal(self, msg, period, duration=None): - """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) - caps = structures.CANCAPABILITIES() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = float(caps.dwClockFreq) / caps.dwCmsDivisor - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask( - self._scheduler, msg, period, duration, self._scheduler_resolution - ) + return self.bus._send_periodic_internal(msg, period, duration) def shutdown(self): - if self._scheduler is not None: - _canlib.canSchedulerClose(self._scheduler) - _canlib.canChannelClose(self._channel_handle) - _canlib.canControlStart(self._control_handle, constants.FALSE) - _canlib.canControlClose(self._control_handle) - _canlib.vciDeviceClose(self._device_handle) - - -class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): - """A message in the cyclic transmit list.""" - - def __init__(self, scheduler, msgs, period, duration, resolution): - super().__init__(msgs, period, duration) - if len(self.messages) != 1: - raise ValueError( - "IXXAT Interface only supports periodic transmission of 1 element" - ) - - self._scheduler = scheduler - self._index = None - self._count = int(duration / period) if duration else 0 - - self._msg = structures.CANCYCLICTXMSG() - self._msg.wCycleTime = int(round(period * resolution)) - self._msg.dwMsgId = self.messages[0].arbitration_id - self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA - self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0 - self._msg.uMsgInfo.Bits.rtr = 1 if self.messages[0].is_remote_frame else 0 - self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc - for i, b in enumerate(self.messages[0].data): - self._msg.abData[i] = b - self.start() - - def start(self): - """Start transmitting message (add to list if needed).""" - if self._index is None: - self._index = ctypes.c_uint32() - _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) - _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) - - def pause(self): - """Pause transmitting message (keep it in the list).""" - _canlib.canSchedulerStopMessage(self._scheduler, self._index) - - def stop(self): - """Stop transmitting message (remove from list).""" - # Remove it completely instead of just stopping it to avoid filling up - # the list with permanently stopped messages - _canlib.canSchedulerRemMessage(self._scheduler, self._index) - self._index = None - - -def _format_can_status(status_flags: int): - """ - Format a status bitfield found in CAN_MSGTYPE_STATUS messages or in dwStatus - field in CANLINESTATUS. - - Valid states are defined in the CAN_STATUS_* constants in cantype.h - """ - states = [] - for flag, description in CAN_STATUS_FLAGS.items(): - if status_flags & flag: - states.append(description) - status_flags &= ~flag - - if status_flags: - states.append("unknown state 0x{:02x}".format(status_flags)) - - if states: - return "CAN status message: {}".format(", ".join(states)) - else: - return "Empty CAN status message" - - -def get_ixxat_hwids(): - """Get a list of hardware ids of all available IXXAT devices.""" - hwids = [] - device_handle = HANDLE() - device_info = structures.VCIDEVICEINFO() - - _canlib.vciEnumDeviceOpen(ctypes.byref(device_handle)) - while True: - try: - _canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info)) - except StopIteration: - break - else: - hwids.append(device_info.UniqueHardwareId.AsChar.decode("ascii")) - _canlib.vciEnumDeviceClose(device_handle) - - return hwids + return self.bus.shutdown() diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py new file mode 100644 index 000000000..cb0447b49 --- /dev/null +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -0,0 +1,877 @@ +""" +Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems + +TODO: We could implement this interface such that setting other filters + could work when the initial filters were set to zero using the + software fallback. Or could the software filters even be changed + after the connection was opened? We need to document that bahaviour! + See also the NICAN interface. + +""" + +import ctypes +import functools +import logging +import sys +from typing import Optional, Callable, Tuple + +from can import BusABC, Message +from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError +from can.broadcastmanager import ( + LimitedDurationCyclicSendTaskABC, + RestartableCyclicTaskABC, +) +from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT +from can.util import deprecated_args_alias + +from . import constants, structures +from .exceptions import * + +__all__ = [ + "VCITimeout", + "VCIError", + "VCIBusOffError", + "VCIDeviceNotFoundError", + "IXXATBus", + "vciFormatError", +] + +log = logging.getLogger("can.ixxat") + +from time import perf_counter + +# Hack to have vciFormatError as a free function, see below +vciFormatError = None + +# main ctypes instance +_canlib = None +# TODO: Use ECI driver for linux +if sys.platform == "win32" or sys.platform == "cygwin": + try: + _canlib = CLibrary("vcinpl.dll") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) +else: + # Will not work on other systems, but have it importable anyway for + # tests/sphinx + log.warning("IXXAT VCI library does not work on %s platform", sys.platform) + + +def __vciFormatErrorExtended( + library_instance: CLibrary, function: Callable, vret: int, args: Tuple +): + """Format a VCI error and attach failed function, decoded HRESULT and arguments + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT vret: + HRESULT returned by vcinpl call + :param args: + Arbitrary arguments tuple + :return: + Formatted string + """ + # TODO: make sure we don't generate another exception + return "{} - arguments were {}".format( + __vciFormatError(library_instance, function, vret), args + ) + + +def __vciFormatError(library_instance: CLibrary, function: Callable, vret: int): + """Format a VCI error and attach failed function and decoded HRESULT + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT vret: + HRESULT returned by vcinpl call + :return: + Formatted string + """ + buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(vret, buf, constants.VCI_MAX_ERRSTRLEN) + return "function {} failed ({})".format( + function._name, buf.value.decode("utf-8", "replace") + ) + + +def __check_status(result, function, args): + """ + Check the result of a vcinpl function call and raise appropriate exception + in case of an error. Used as errcheck function when mapping C functions + with ctypes. + :param result: + Function call numeric result + :param callable function: + Called function + :param args: + Arbitrary arguments tuple + :raise: + :class:VCITimeout + :class:VCIRxQueueEmptyError + :class:StopIteration + :class:VCIError + """ + if isinstance(result, int): + # Real return value is an unsigned long, the following line converts the number to unsigned + result = ctypes.c_ulong(result).value + + if result == constants.VCI_E_TIMEOUT: + raise VCITimeout("Function {} timed out".format(function._name)) + elif result == constants.VCI_E_RXQUEUE_EMPTY: + raise VCIRxQueueEmptyError() + elif result == constants.VCI_E_NO_MORE_ITEMS: + raise StopIteration() + elif result == constants.VCI_E_ACCESSDENIED: + pass # not a real error, might happen if another program has initialized the bus + elif result != constants.VCI_OK: + raise VCIError(vciFormatError(function, result)) + + return result + + +try: + # Map all required symbols and initialize library --------------------------- + # HRESULT VCIAPI vciInitialize ( void ); + _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) + + # void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); + _canlib.map_symbol( + "vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32) + ) + # Hack to have vciFormatError as a free function + vciFormatError = functools.partial(__vciFormatError, _canlib) + + # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceOpen", ctypes.c_long, (PHANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); + _canlib.map_symbol( + "vciEnumDeviceNext", + ctypes.c_long, + (HANDLE, structures.PVCIDEVICEINFO), + __check_status, + ) + + # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); + _canlib.map_symbol( + "vciDeviceOpen", ctypes.c_long, (structures.PVCIID, PHANDLE), __check_status + ) + # HRESULT vciDeviceClose( HANDLE hDevice ) + _canlib.map_symbol("vciDeviceClose", ctypes.c_long, (HANDLE,), __check_status) + + # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); + _canlib.map_symbol( + "canChannelOpen", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canChannelInitialize( IN HANDLE hCanChn, IN UINT16 wRxFifoSize, IN UINT16 wRxThreshold, IN UINT16 wTxFifoSize, IN UINT16 wTxThreshold ); + _canlib.map_symbol( + "canChannelInitialize", + ctypes.c_long, + (HANDLE, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint16), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); + _canlib.map_symbol( + "canChannelActivate", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status + ) + # HRESULT canChannelClose( HANDLE hChannel ) + _canlib.map_symbol("canChannelClose", ctypes.c_long, (HANDLE,), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG pCanMsg ); + _canlib.map_symbol( + "canChannelReadMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, structures.PCANMSG), + __check_status, + ) + # HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG pCanMsg ); + _canlib.map_symbol( + "canChannelPeekMessage", + ctypes.c_long, + (HANDLE, structures.PCANMSG), + __check_status, + ) + # HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); + _canlib.map_symbol( + "canChannelWaitTxEvent", + ctypes.c_long, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); + _canlib.map_symbol( + "canChannelWaitRxEvent", + ctypes.c_long, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG pCanMsg ); + _canlib.map_symbol( + "canChannelPostMessage", + ctypes.c_long, + (HANDLE, structures.PCANMSG), + __check_status, + ) + # HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG pCanMsg ); + _canlib.map_symbol( + "canChannelSendMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, structures.PCANMSG), + __check_status, + ) + + # EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); + _canlib.map_symbol( + "canControlOpen", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, PHANDLE), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlInitialize( IN HANDLE hCanCtl, IN UINT8 bMode, IN UINT8 bBtr0, IN UINT8 bBtr1 ); + _canlib.map_symbol( + "canControlInitialize", + ctypes.c_long, + (HANDLE, ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlClose", ctypes.c_long, (HANDLE,), __check_status) + # EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlReset", ctypes.c_long, (HANDLE,), __check_status) + # EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); + _canlib.map_symbol( + "canControlStart", ctypes.c_long, (HANDLE, ctypes.c_long), __check_status + ) + # EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS pStatus ); + _canlib.map_symbol( + "canControlGetStatus", + ctypes.c_long, + (HANDLE, structures.PCANLINESTATUS), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES pCanCaps ); + _canlib.map_symbol( + "canControlGetCaps", + ctypes.c_long, + (HANDLE, structures.PCANCAPABILITIES), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); + _canlib.map_symbol( + "canControlSetAccFilter", + ctypes.c_long, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); + _canlib.map_symbol( + "canControlAddFilterIds", + ctypes.c_long, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); + _canlib.map_symbol( + "canControlRemFilterIds", + ctypes.c_long, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); + _canlib.map_symbol( + "canSchedulerOpen", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, PHANDLE), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); + _canlib.map_symbol("canSchedulerClose", ctypes.c_long, (HANDLE,), __check_status) + # EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES pCaps ); + _canlib.map_symbol( + "canSchedulerGetCaps", + ctypes.c_long, + (HANDLE, structures.PCANCAPABILITIES), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); + _canlib.map_symbol( + "canSchedulerActivate", ctypes.c_long, (HANDLE, ctypes.c_int), __check_status + ) + # EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG pMessage, PUINT32 pdwIndex ); + _canlib.map_symbol( + "canSchedulerAddMessage", + ctypes.c_long, + (HANDLE, structures.PCANCYCLICTXMSG, ctypes.POINTER(ctypes.c_uint32)), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol( + "canSchedulerRemMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); + _canlib.map_symbol( + "canSchedulerStartMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32, ctypes.c_uint16), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol( + "canSchedulerStopMessage", + ctypes.c_long, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + _canlib.vciInitialize() +except AttributeError: + # In case _canlib == None meaning we're not on win32/no lib found + pass +except Exception as e: + log.warning("Could not initialize IXXAT VCI library: %s", e) +# --------------------------------------------------------------------------- + + +CAN_INFO_MESSAGES = { + constants.CAN_INFO_START: "CAN started", + constants.CAN_INFO_STOP: "CAN stopped", + constants.CAN_INFO_RESET: "CAN reset", +} + +CAN_ERROR_MESSAGES = { + constants.CAN_ERROR_STUFF: "CAN bit stuff error", + constants.CAN_ERROR_FORM: "CAN form error", + constants.CAN_ERROR_ACK: "CAN acknowledgment error", + constants.CAN_ERROR_BIT: "CAN bit error", + constants.CAN_ERROR_CRC: "CAN CRC error", + constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", +} + +CAN_STATUS_FLAGS = { + constants.CAN_STATUS_TXPEND: "transmission pending", + constants.CAN_STATUS_OVRRUN: "data overrun occurred", + constants.CAN_STATUS_ERRLIM: "error warning limit exceeded", + constants.CAN_STATUS_BUSOFF: "bus off", + constants.CAN_STATUS_ININIT: "init mode active", + constants.CAN_STATUS_BUSCERR: "bus coupling error", +} +# ---------------------------------------------------------------------------- + + +class IXXATBus(BusABC): + """The CAN Bus implemented for the IXXAT interface. + + .. warning:: + + This interface does implement efficient filtering of messages, but + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. + + """ + + CHANNEL_BITRATES = { + 0: { + 10000: constants.CAN_BT0_10KB, + 20000: constants.CAN_BT0_20KB, + 50000: constants.CAN_BT0_50KB, + 100000: constants.CAN_BT0_100KB, + 125000: constants.CAN_BT0_125KB, + 250000: constants.CAN_BT0_250KB, + 500000: constants.CAN_BT0_500KB, + 666000: constants.CAN_BT0_667KB, + 666666: constants.CAN_BT0_667KB, + 666667: constants.CAN_BT0_667KB, + 667000: constants.CAN_BT0_667KB, + 800000: constants.CAN_BT0_800KB, + 1000000: constants.CAN_BT0_1000KB, + }, + 1: { + 10000: constants.CAN_BT1_10KB, + 20000: constants.CAN_BT1_20KB, + 50000: constants.CAN_BT1_50KB, + 100000: constants.CAN_BT1_100KB, + 125000: constants.CAN_BT1_125KB, + 250000: constants.CAN_BT1_250KB, + 500000: constants.CAN_BT1_500KB, + 666000: constants.CAN_BT1_667KB, + 666666: constants.CAN_BT1_667KB, + 666667: constants.CAN_BT1_667KB, + 667000: constants.CAN_BT1_667KB, + 800000: constants.CAN_BT1_800KB, + 1000000: constants.CAN_BT1_1000KB, + }, + } + + @deprecated_args_alias( + UniqueHardwareId="unique_hardware_id", + rxFifoSize="rx_fifo_size", + txFifoSize="tx_fifo_size", + ) + def __init__( + self, + channel: int, + can_filters=None, + receive_own_messages: bool = False, + unique_hardware_id: Optional[int] = None, + extended: bool = True, + rx_fifo_size: int = 16, + tx_fifo_size: int = 16, + bitrate: int = 500000, + **kwargs, + ): + """ + :param channel: + The Channel id to create this bus with. + + :param can_filters: + See :meth:`can.BusABC.set_filters`. + + :param receive_own_messages: + Enable self-reception of sent messages. + + :param unique_hardware_id: + unique_hardware_id to connect (optional, will use the first found if not supplied) + + :param extended: + Default True, enables the capability to use extended IDs. + + :param rx_fifo_size: + Receive fifo size (default 16) + + :param tx_fifo_size: + Transmit fifo size (default 16) + + :param bitrate: + Channel bitrate in bit/s + """ + if _canlib is None: + raise CanInterfaceNotImplementedError( + "The IXXAT VCI library has not been initialized. Check the logs for more details." + ) + log.info("CAN Filters: %s", can_filters) + # Configuration options + self._receive_own_messages = receive_own_messages + # Usually comes as a string from the config file + channel = int(channel) + + if bitrate not in self.CHANNEL_BITRATES[0]: + raise ValueError("Invalid bitrate {}".format(bitrate)) + + if rx_fifo_size <= 0: + raise ValueError("rx_fifo_size must be > 0") + + if tx_fifo_size <= 0: + raise ValueError("tx_fifo_size must be > 0") + + if channel < 0: + raise ValueError("channel number must be >= 0") + + self._device_handle = HANDLE() + self._device_info = structures.VCIDEVICEINFO() + self._control_handle = HANDLE() + self._channel_handle = HANDLE() + self._channel_capabilities = structures.CANCAPABILITIES() + self._message = structures.CANMSG() + self._payload = (ctypes.c_byte * 8)() + + # Search for supplied device + if unique_hardware_id is None: + log.info("Searching for first available device") + else: + log.info("Searching for unique HW ID %s", unique_hardware_id) + _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext( + self._device_handle, ctypes.byref(self._device_info) + ) + except StopIteration: + if unique_hardware_id is None: + raise VCIDeviceNotFoundError( + "No IXXAT device(s) connected or device(s) in use by other process(es)." + ) + else: + raise VCIDeviceNotFoundError( + "Unique HW ID {} not connected or not available.".format( + unique_hardware_id + ) + ) + else: + if (unique_hardware_id is None) or ( + self._device_info.UniqueHardwareId.AsChar + == bytes(unique_hardware_id, "ascii") + ): + break + else: + log.debug( + "Ignoring IXXAT with hardware id '%s'.", + self._device_info.UniqueHardwareId.AsChar.decode("ascii"), + ) + _canlib.vciEnumDeviceClose(self._device_handle) + + try: + _canlib.vciDeviceOpen( + ctypes.byref(self._device_info.VciObjectId), + ctypes.byref(self._device_handle), + ) + except Exception as exception: + raise CanInitializationError(f"Could not open device: {exception}") + + log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) + + log.info( + "Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", + channel, + rx_fifo_size, + tx_fifo_size, + ) + + try: + _canlib.canChannelOpen( + self._device_handle, + channel, + constants.FALSE, + ctypes.byref(self._channel_handle), + ) + except Exception as exception: + raise CanInitializationError( + f"Could not open and initialize channel: {exception}" + ) + + # Signal TX/RX events when at least one frame has been handled + _canlib.canChannelInitialize( + self._channel_handle, rx_fifo_size, 1, tx_fifo_size, 1 + ) + _canlib.canChannelActivate(self._channel_handle, constants.TRUE) + + log.info("Initializing control %d bitrate %d", channel, bitrate) + _canlib.canControlOpen( + self._device_handle, channel, ctypes.byref(self._control_handle) + ) + + # compute opmode before control initialize + opmode = constants.CAN_OPMODE_STANDARD | constants.CAN_OPMODE_ERRFRAME + if extended: + opmode |= constants.CAN_OPMODE_EXTENDED + + # control initialize + _canlib.canControlInitialize( + self._control_handle, + opmode, + self.CHANNEL_BITRATES[0][bitrate], + self.CHANNEL_BITRATES[1][bitrate], + ) + _canlib.canControlGetCaps( + self._control_handle, ctypes.byref(self._channel_capabilities) + ) + + # With receive messages, this field contains the relative reception time of + # the message in ticks. The resolution of a tick can be calculated from the fields + # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: + # frequency [1/s] = dwClockFreq / dwTscDivisor + self._tick_resolution = ( + self._channel_capabilities.dwClockFreq + / self._channel_capabilities.dwTscDivisor + ) + + # Setup filters before starting the channel + if can_filters: + log.info("The IXXAT VCI backend is filtering messages") + # Disable every message coming in + for extended in (0, 1): + _canlib.canControlSetAccFilter( + self._control_handle, + extended, + constants.CAN_ACC_CODE_NONE, + constants.CAN_ACC_MASK_NONE, + ) + for can_filter in can_filters: + # Filters define what messages are accepted + code = int(can_filter["can_id"]) + mask = int(can_filter["can_mask"]) + extended = can_filter.get("extended", False) + _canlib.canControlAddFilterIds( + self._control_handle, 1 if extended else 0, code << 1, mask << 1 + ) + log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) + + # Start the CAN controller. Messages will be forwarded to the channel + _canlib.canControlStart(self._control_handle, constants.TRUE) + + # For cyclic transmit list. Set when .send_periodic() is first called + self._scheduler = None + self._scheduler_resolution = None + self.channel = channel + + # Usually you get back 3 messages like "CAN initialized" ecc... + # Clear the FIFO by filter them out with low timeout + for _ in range(rx_fifo_size): + try: + _canlib.canChannelReadMessage( + self._channel_handle, 0, ctypes.byref(self._message) + ) + except (VCITimeout, VCIRxQueueEmptyError): + break + + super().__init__(channel=channel, can_filters=None, **kwargs) + + def _inWaiting(self): + try: + _canlib.canChannelWaitRxEvent(self._channel_handle, 0) + except VCITimeout: + return 0 + else: + return 1 + + def flush_tx_buffer(self): + """Flushes the transmit buffer on the IXXAT""" + # TODO #64: no timeout? + _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) + + def _recv_internal(self, timeout): + """Read a message from IXXAT device.""" + + # TODO: handling CAN error messages? + data_received = False + + if timeout == 0: + # Peek without waiting + try: + _canlib.canChannelPeekMessage( + self._channel_handle, ctypes.byref(self._message) + ) + except (VCITimeout, VCIRxQueueEmptyError): + return None, True + else: + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + else: + # Wait if no message available + if timeout is None or timeout < 0: + remaining_ms = constants.INFINITE + t0 = None + else: + timeout_ms = int(timeout * 1000) + remaining_ms = timeout_ms + t0 = perf_counter() + + while True: + try: + _canlib.canChannelReadMessage( + self._channel_handle, remaining_ms, ctypes.byref(self._message) + ) + except (VCITimeout, VCIRxQueueEmptyError): + # Ignore the 2 errors, the timeout is handled manually with the perf_counter() + pass + else: + # See if we got a data or info/error messages + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + break + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: + log.info( + CAN_INFO_MESSAGES.get( + self._message.abData[0], + "Unknown CAN info message code {}".format( + self._message.abData[0] + ), + ) + ) + + elif ( + self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR + ): + log.warning( + CAN_ERROR_MESSAGES.get( + self._message.abData[0], + "Unknown CAN error message code {}".format( + self._message.abData[0] + ), + ) + ) + + elif ( + self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS + ): + log.info(_format_can_status(self._message.abData[0])) + if self._message.abData[0] & constants.CAN_STATUS_BUSOFF: + raise VCIBusOffError() + + elif ( + self._message.uMsgInfo.Bits.type + == constants.CAN_MSGTYPE_TIMEOVR + ): + pass + else: + log.warning("Unexpected message info type") + + if t0 is not None: + remaining_ms = timeout_ms - int((perf_counter() - t0) * 1000) + if remaining_ms < 0: + break + + if not data_received: + # Timed out / can message type is not DATA + return None, True + + # The _message.dwTime is a 32bit tick value and will overrun, + # so expect to see the value restarting from 0 + rx_msg = Message( + timestamp=self._message.dwTime + / self._tick_resolution, # Relative time in s + is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr), + is_extended_id=bool(self._message.uMsgInfo.Bits.ext), + arbitration_id=self._message.dwMsgId, + dlc=self._message.uMsgInfo.Bits.dlc, + data=self._message.abData[: self._message.uMsgInfo.Bits.dlc], + channel=self.channel, + ) + + return rx_msg, True + + def send(self, msg: Message, timeout: Optional[float] = None) -> None: + """ + Sends a message on the bus. The interface may buffer the message. + + :param msg: + The message to send. + :param timeout: + Timeout after some time. + :raise: + :class:CanTimeoutError + :class:CanOperationError + """ + # This system is not designed to be very efficient + message = structures.CANMSG() + message.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 + message.dwMsgId = msg.arbitration_id + if msg.dlc: + message.uMsgInfo.Bits.dlc = msg.dlc + adapter = (ctypes.c_uint8 * len(msg.data)).from_buffer(msg.data) + ctypes.memmove(message.abData, adapter, len(msg.data)) + + if timeout: + _canlib.canChannelSendMessage( + self._channel_handle, int(timeout * 1000), message + ) + + else: + _canlib.canChannelPostMessage(self._channel_handle, message) + + def _send_periodic_internal(self, msg, period, duration=None): + """Send a message using built-in cyclic transmit list functionality.""" + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) + caps = structures.CANCAPABILITIES() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask( + self._scheduler, msg, period, duration, self._scheduler_resolution + ) + + def shutdown(self): + if self._scheduler is not None: + _canlib.canSchedulerClose(self._scheduler) + _canlib.canChannelClose(self._channel_handle) + _canlib.canControlStart(self._control_handle, constants.FALSE) + _canlib.canControlClose(self._control_handle) + _canlib.vciDeviceClose(self._device_handle) + + +class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): + """A message in the cyclic transmit list.""" + + def __init__(self, scheduler, msgs, period, duration, resolution): + super().__init__(msgs, period, duration) + if len(self.messages) != 1: + raise ValueError( + "IXXAT Interface only supports periodic transmission of 1 element" + ) + + self._scheduler = scheduler + self._index = None + self._count = int(duration / period) if duration else 0 + + self._msg = structures.CANCYCLICTXMSG() + self._msg.wCycleTime = int(round(period * resolution)) + self._msg.dwMsgId = self.messages[0].arbitration_id + self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0 + self._msg.uMsgInfo.Bits.rtr = 1 if self.messages[0].is_remote_frame else 0 + self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc + for i, b in enumerate(self.messages[0].data): + self._msg.abData[i] = b + self.start() + + def start(self): + """Start transmitting message (add to list if needed).""" + if self._index is None: + self._index = ctypes.c_uint32() + _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) + _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) + + def pause(self): + """Pause transmitting message (keep it in the list).""" + _canlib.canSchedulerStopMessage(self._scheduler, self._index) + + def stop(self): + """Stop transmitting message (remove from list).""" + # Remove it completely instead of just stopping it to avoid filling up + # the list with permanently stopped messages + _canlib.canSchedulerRemMessage(self._scheduler, self._index) + self._index = None + + +def _format_can_status(status_flags: int): + """ + Format a status bitfield found in CAN_MSGTYPE_STATUS messages or in dwStatus + field in CANLINESTATUS. + + Valid states are defined in the CAN_STATUS_* constants in cantype.h + """ + states = [] + for flag, description in CAN_STATUS_FLAGS.items(): + if status_flags & flag: + states.append(description) + status_flags &= ~flag + + if status_flags: + states.append("unknown state 0x{:02x}".format(status_flags)) + + if states: + return "CAN status message: {}".format(", ".join(states)) + else: + return "Empty CAN status message" + + +def get_ixxat_hwids(): + """Get a list of hardware ids of all available IXXAT devices.""" + hwids = [] + device_handle = HANDLE() + device_info = structures.VCIDEVICEINFO() + + _canlib.vciEnumDeviceOpen(ctypes.byref(device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info)) + except StopIteration: + break + else: + hwids.append(device_info.UniqueHardwareId.AsChar.decode("ascii")) + _canlib.vciEnumDeviceClose(device_handle) + + return hwids diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py new file mode 100644 index 000000000..37085d74a --- /dev/null +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -0,0 +1,1043 @@ +""" +Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems + +TODO: We could implement this interface such that setting other filters + could work when the initial filters were set to zero using the + software fallback. Or could the software filters even be changed + after the connection was opened? We need to document that bahaviour! + See also the NICAN interface. + +""" + +import ctypes +import functools +import logging +import sys +from typing import Optional, Callable, Tuple + +from can import BusABC, Message +from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError +from can.broadcastmanager import ( + LimitedDurationCyclicSendTaskABC, + RestartableCyclicTaskABC, +) +from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT + +import can.util +from can.util import deprecated_args_alias + +from . import constants, structures +from .exceptions import * + +__all__ = [ + "VCITimeout", + "VCIError", + "VCIBusOffError", + "VCIDeviceNotFoundError", + "IXXATBus", + "vciFormatError", +] + +log = logging.getLogger("can.ixxat") + +from time import perf_counter + +# Hack to have vciFormatError as a free function, see below +vciFormatError = None + +# main ctypes instance +_canlib = None +# TODO: Use ECI driver for linux +if sys.platform == "win32" or sys.platform == "cygwin": + try: + _canlib = CLibrary("vcinpl2.dll") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) +else: + # Will not work on other systems, but have it importable anyway for + # tests/sphinx + log.warning("IXXAT VCI library does not work on %s platform", sys.platform) + + +def __vciFormatErrorExtended( + library_instance: CLibrary, function: Callable, vret: int, args: Tuple +): + """Format a VCI error and attach failed function, decoded HRESULT and arguments + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT vret: + HRESULT returned by vcinpl call + :param args: + Arbitrary arguments tuple + :return: + Formatted string + """ + # TODO: make sure we don't generate another exception + return "{} - arguments were {}".format( + __vciFormatError(library_instance, function, vret), args + ) + + +def __vciFormatError(library_instance: CLibrary, function: Callable, vret: int): + """Format a VCI error and attach failed function and decoded HRESULT + :param CLibrary library_instance: + Mapped instance of IXXAT vcinpl library + :param callable function: + Failed function + :param HRESULT vret: + HRESULT returned by vcinpl call + :return: + Formatted string + """ + buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(vret, buf, constants.VCI_MAX_ERRSTRLEN) + return "function {} failed ({})".format( + function._name, buf.value.decode("utf-8", "replace") + ) + + +def __check_status(result, function, args): + """ + Check the result of a vcinpl function call and raise appropriate exception + in case of an error. Used as errcheck function when mapping C functions + with ctypes. + :param result: + Function call numeric result + :param callable function: + Called function + :param args: + Arbitrary arguments tuple + :raise: + :class:VCITimeout + :class:VCIRxQueueEmptyError + :class:StopIteration + :class:VCIError + """ + if result == constants.VCI_E_TIMEOUT: + raise VCITimeout("Function {} timed out".format(function._name)) + elif result == constants.VCI_E_RXQUEUE_EMPTY: + raise VCIRxQueueEmptyError() + elif result == constants.VCI_E_NO_MORE_ITEMS: + raise StopIteration() + elif result == constants.VCI_E_ACCESSDENIED: + pass # not a real error, might happen if another program has initialized the bus + elif result != constants.VCI_OK: + raise VCIError(vciFormatError(function, result)) + + return result + + +try: + hresult_type = ctypes.c_ulong + # Map all required symbols and initialize library --------------------------- + # HRESULT VCIAPI vciInitialize ( void ); + _canlib.map_symbol("vciInitialize", hresult_type, (), __check_status) + + # void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); + try: + _canlib.map_symbol( + "vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32) + ) + except: + _canlib.map_symbol( + "vciFormatErrorA", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32) + ) + _canlib.vciFormatError = _canlib.vciFormatErrorA + # Hack to have vciFormatError as a free function + vciFormatError = functools.partial(__vciFormatError, _canlib) + + # HRESULT VCIAPI vciEnumDeviceOpen( OUT PHANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceOpen", hresult_type, (PHANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceClose ( IN HANDLE hEnum ); + _canlib.map_symbol("vciEnumDeviceClose", hresult_type, (HANDLE,), __check_status) + # HRESULT VCIAPI vciEnumDeviceNext( IN HANDLE hEnum, OUT PVCIDEVICEINFO pInfo ); + _canlib.map_symbol( + "vciEnumDeviceNext", + hresult_type, + (HANDLE, structures.PVCIDEVICEINFO), + __check_status, + ) + + # HRESULT VCIAPI vciDeviceOpen( IN REFVCIID rVciid, OUT PHANDLE phDevice ); + _canlib.map_symbol( + "vciDeviceOpen", hresult_type, (structures.PVCIID, PHANDLE), __check_status + ) + # HRESULT vciDeviceClose( HANDLE hDevice ) + _canlib.map_symbol("vciDeviceClose", hresult_type, (HANDLE,), __check_status) + + # HRESULT VCIAPI canChannelOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, IN BOOL fExclusive, OUT PHANDLE phCanChn ); + _canlib.map_symbol( + "canChannelOpen", + hresult_type, + (HANDLE, ctypes.c_uint32, ctypes.c_long, PHANDLE), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI + # canChannelInitialize( IN HANDLE hCanChn, + # IN UINT16 wRxFifoSize, + # IN UINT16 wRxThreshold, + # IN UINT16 wTxFifoSize, + # IN UINT16 wTxThreshold, + # IN UINT32 dwFilterSize, + # IN UINT8 bFilterMode ); + _canlib.map_symbol( + "canChannelInitialize", + hresult_type, + ( + HANDLE, + ctypes.c_uint16, + ctypes.c_uint16, + ctypes.c_uint16, + ctypes.c_uint16, + ctypes.c_uint32, + ctypes.c_uint8, + ), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canChannelActivate( IN HANDLE hCanChn, IN BOOL fEnable ); + _canlib.map_symbol( + "canChannelActivate", hresult_type, (HANDLE, ctypes.c_long), __check_status + ) + # HRESULT canChannelClose( HANDLE hChannel ) + _canlib.map_symbol("canChannelClose", hresult_type, (HANDLE,), __check_status) + # EXTERN_C HRESULT VCIAPI canChannelReadMessage( IN HANDLE hCanChn, IN UINT32 dwMsTimeout, OUT PCANMSG2 pCanMsg ); + _canlib.map_symbol( + "canChannelReadMessage", + hresult_type, + (HANDLE, ctypes.c_uint32, structures.PCANMSG2), + __check_status, + ) + # HRESULT canChannelPeekMessage(HANDLE hChannel,PCANMSG2 pCanMsg ); + _canlib.map_symbol( + "canChannelPeekMessage", + hresult_type, + (HANDLE, structures.PCANMSG2), + __check_status, + ) + # HRESULT canChannelWaitTxEvent (HANDLE hChannel UINT32 dwMsTimeout ); + _canlib.map_symbol( + "canChannelWaitTxEvent", + hresult_type, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # HRESULT canChannelWaitRxEvent (HANDLE hChannel, UINT32 dwMsTimeout ); + _canlib.map_symbol( + "canChannelWaitRxEvent", + hresult_type, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # HRESULT canChannelPostMessage (HANDLE hChannel, PCANMSG2 pCanMsg ); + _canlib.map_symbol( + "canChannelPostMessage", + hresult_type, + (HANDLE, structures.PCANMSG2), + __check_status, + ) + # HRESULT canChannelSendMessage (HANDLE hChannel, UINT32 dwMsTimeout, PCANMSG2 pCanMsg ); + _canlib.map_symbol( + "canChannelSendMessage", + hresult_type, + (HANDLE, ctypes.c_uint32, structures.PCANMSG2), + __check_status, + ) + + # EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); + _canlib.map_symbol( + "canControlOpen", + hresult_type, + (HANDLE, ctypes.c_uint32, PHANDLE), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI + # canControlInitialize( IN HANDLE hCanCtl, + # IN UINT8 bOpMode, + # IN UINT8 bExMode, + # IN UINT8 bSFMode, + # IN UINT8 bEFMode, + # IN UINT32 dwSFIds, + # IN UINT32 dwEFIds, + # IN PCANBTP pBtpSDR, + # IN PCANBTP pBtpFDR ); + _canlib.map_symbol( + "canControlInitialize", + hresult_type, + ( + HANDLE, + ctypes.c_uint8, + ctypes.c_uint8, + ctypes.c_uint8, + ctypes.c_uint8, + ctypes.c_uint32, + ctypes.c_uint32, + structures.PCANBTP, + structures.PCANBTP, + ), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlClose( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlClose", hresult_type, (HANDLE,), __check_status) + # EXTERN_C HRESULT VCIAPI canControlReset( IN HANDLE hCanCtl ); + _canlib.map_symbol("canControlReset", hresult_type, (HANDLE,), __check_status) + # EXTERN_C HRESULT VCIAPI canControlStart( IN HANDLE hCanCtl, IN BOOL fStart ); + _canlib.map_symbol( + "canControlStart", hresult_type, (HANDLE, ctypes.c_long), __check_status + ) + # EXTERN_C HRESULT VCIAPI canControlGetStatus( IN HANDLE hCanCtl, OUT PCANLINESTATUS2 pStatus ); + _canlib.map_symbol( + "canControlGetStatus", + hresult_type, + (HANDLE, structures.PCANLINESTATUS2), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlGetCaps( IN HANDLE hCanCtl, OUT PCANCAPABILITIES2 pCanCaps ); + _canlib.map_symbol( + "canControlGetCaps", + hresult_type, + (HANDLE, structures.PCANCAPABILITIES2), + __check_status, + ) + # EXTERN_C HRESULT VCIAPI canControlSetAccFilter( IN HANDLE hCanCtl, IN BOOL fExtend, IN UINT32 dwCode, IN UINT32 dwMask ); + _canlib.map_symbol( + "canControlSetAccFilter", + hresult_type, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canControlAddFilterIds (HANDLE hControl, BOOL fExtended, UINT32 dwCode, UINT32 dwMask); + _canlib.map_symbol( + "canControlAddFilterIds", + hresult_type, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canControlRemFilterIds (HANDLE hControl, BOOL fExtendend, UINT32 dwCode, UINT32 dwMask ); + _canlib.map_symbol( + "canControlRemFilterIds", + hresult_type, + (HANDLE, ctypes.c_int, ctypes.c_uint32, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerOpen (HANDLE hDevice, UINT32 dwCanNo, PHANDLE phScheduler ); + _canlib.map_symbol( + "canSchedulerOpen", + hresult_type, + (HANDLE, ctypes.c_uint32, PHANDLE), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerClose (HANDLE hScheduler ); + _canlib.map_symbol("canSchedulerClose", hresult_type, (HANDLE,), __check_status) + # EXTERN_C HRESULT canSchedulerGetCaps (HANDLE hScheduler, PCANCAPABILITIES2 pCaps ); + _canlib.map_symbol( + "canSchedulerGetCaps", + hresult_type, + (HANDLE, structures.PCANCAPABILITIES2), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerActivate ( HANDLE hScheduler, BOOL fEnable ); + _canlib.map_symbol( + "canSchedulerActivate", hresult_type, (HANDLE, ctypes.c_int), __check_status + ) + # EXTERN_C HRESULT canSchedulerAddMessage (HANDLE hScheduler, PCANCYCLICTXMSG2 pMessage, PUINT32 pdwIndex ); + _canlib.map_symbol( + "canSchedulerAddMessage", + hresult_type, + (HANDLE, structures.PCANCYCLICTXMSG2, ctypes.POINTER(ctypes.c_uint32)), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerRemMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol( + "canSchedulerRemMessage", + hresult_type, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerStartMessage (HANDLE hScheduler, UINT32 dwIndex, UINT16 dwCount ); + _canlib.map_symbol( + "canSchedulerStartMessage", + hresult_type, + (HANDLE, ctypes.c_uint32, ctypes.c_uint16), + __check_status, + ) + # EXTERN_C HRESULT canSchedulerStopMessage (HANDLE hScheduler, UINT32 dwIndex ); + _canlib.map_symbol( + "canSchedulerStopMessage", + hresult_type, + (HANDLE, ctypes.c_uint32), + __check_status, + ) + _canlib.vciInitialize() +except AttributeError: + # In case _canlib == None meaning we're not on win32/no lib found + pass +except Exception as e: + log.warning("Could not initialize IXXAT VCI library: %s", e) +# --------------------------------------------------------------------------- + + +CAN_INFO_MESSAGES = { + constants.CAN_INFO_START: "CAN started", + constants.CAN_INFO_STOP: "CAN stopped", + constants.CAN_INFO_RESET: "CAN reset", +} + +CAN_ERROR_MESSAGES = { + constants.CAN_ERROR_STUFF: "CAN bit stuff error", + constants.CAN_ERROR_FORM: "CAN form error", + constants.CAN_ERROR_ACK: "CAN acknowledgment error", + constants.CAN_ERROR_BIT: "CAN bit error", + constants.CAN_ERROR_CRC: "CAN CRC error", + constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", +} + +CAN_STATUS_FLAGS = { + constants.CAN_STATUS_TXPEND: "transmission pending", + constants.CAN_STATUS_OVRRUN: "data overrun occurred", + constants.CAN_STATUS_ERRLIM: "error warning limit exceeded", + constants.CAN_STATUS_BUSOFF: "bus off", + constants.CAN_STATUS_ININIT: "init mode active", + constants.CAN_STATUS_BUSCERR: "bus coupling error", +} +# ---------------------------------------------------------------------------- + + +class IXXATBus(BusABC): + """The CAN Bus implemented for the IXXAT interface. + + .. warning:: + + This interface does implement efficient filtering of messages, but + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. + + """ + + @deprecated_args_alias( + UniqueHardwareId="unique_hardware_id", + rxFifoSize="rx_fifo_size", + txFifoSize="tx_fifo_size", + ) + def __init__( + self, + channel: int, + can_filters=None, + receive_own_messages: int = False, + unique_hardware_id: Optional[int] = None, + extended: bool = True, + rx_fifo_size: int = 1024, + tx_fifo_size: int = 128, + bitrate: int = 500000, + data_bitrate: int = 2000000, + sjw_abr: int = None, + tseg1_abr: int = None, + tseg2_abr: int = None, + sjw_dbr: int = None, + tseg1_dbr: int = None, + tseg2_dbr: int = None, + ssp_dbr: int = None, + **kwargs, + ): + """ + :param channel: + The Channel id to create this bus with. + + :param can_filters: + See :meth:`can.BusABC.set_filters`. + + :param receive_own_messages: + Enable self-reception of sent messages. + + :param unique_hardware_id: + unique_hardware_id to connect (optional, will use the first found if not supplied) + + :param extended: + Default True, enables the capability to use extended IDs. + + :param rx_fifo_size: + Receive fifo size (default 1024) + + :param tx_fifo_size: + Transmit fifo size (default 128) + + :param bitrate: + Channel bitrate in bit/s + + :param data_bitrate: + Channel bitrate in bit/s (only in CAN-Fd if baudrate switch enabled). + + :param sjw_abr: + Bus timing value sample jump width (arbitration). + + :param tseg1_abr: + Bus timing value tseg1 (arbitration) + + :param tseg2_abr: + Bus timing value tseg2 (arbitration) + + :param sjw_dbr: + Bus timing value sample jump width (data) + + :param tseg1_dbr: + Bus timing value tseg1 (data). Only takes effect with fd and bitrate switch enabled. + + :param tseg2_dbr: + Bus timing value tseg2 (data). Only takes effect with fd and bitrate switch enabled. + + :param ssp_dbr: + Secondary sample point (data). Only takes effect with fd and bitrate switch enabled. + + """ + if _canlib is None: + raise CanInterfaceNotImplementedError( + "The IXXAT VCI library has not been initialized. Check the logs for more details." + ) + log.info("CAN Filters: %s", can_filters) + # Configuration options + self._receive_own_messages = receive_own_messages + # Usually comes as a string from the config file + channel = int(channel) + + if bitrate not in constants.CAN_BITRATE_PRESETS and ( + tseg1_abr is None or tseg2_abr is None or sjw_abr is None + ): + raise ValueError( + "To use bitrate {} (that has not predefined preset) is mandatory to use also parameters tseg1_abr, tseg2_abr and swj_abr".format( + bitrate + ) + ) + if data_bitrate not in constants.CAN_DATABITRATE_PRESETS and ( + tseg1_dbr is None or tseg2_dbr is None or sjw_dbr is None + ): + raise ValueError( + "To use data_bitrate {} (that has not predefined preset) is mandatory to use also parameters tseg1_dbr, tseg2_dbr and swj_dbr".format( + data_bitrate + ) + ) + + if rx_fifo_size <= 0: + raise ValueError("rx_fifo_size must be > 0") + + if tx_fifo_size <= 0: + raise ValueError("tx_fifo_size must be > 0") + + if channel < 0: + raise ValueError("channel number must be >= 0") + + self._device_handle = HANDLE() + self._device_info = structures.VCIDEVICEINFO() + self._control_handle = HANDLE() + self._channel_handle = HANDLE() + self._channel_capabilities = structures.CANCAPABILITIES2() + self._message = structures.CANMSG2() + self._payload = (ctypes.c_byte * 64)() + + # Search for supplied device + if unique_hardware_id is None: + log.info("Searching for first available device") + else: + log.info("Searching for unique HW ID %s", unique_hardware_id) + _canlib.vciEnumDeviceOpen(ctypes.byref(self._device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext( + self._device_handle, ctypes.byref(self._device_info) + ) + except StopIteration: + if unique_hardware_id is None: + raise VCIDeviceNotFoundError( + "No IXXAT device(s) connected or device(s) in use by other process(es)." + ) + else: + raise VCIDeviceNotFoundError( + "Unique HW ID {} not connected or not available.".format( + unique_hardware_id + ) + ) + else: + if (unique_hardware_id is None) or ( + self._device_info.UniqueHardwareId.AsChar + == bytes(unique_hardware_id, "ascii") + ): + break + else: + log.debug( + "Ignoring IXXAT with hardware id '%s'.", + self._device_info.UniqueHardwareId.AsChar.decode("ascii"), + ) + _canlib.vciEnumDeviceClose(self._device_handle) + + try: + _canlib.vciDeviceOpen( + ctypes.byref(self._device_info.VciObjectId), + ctypes.byref(self._device_handle), + ) + except Exception as exception: + raise CanInitializationError(f"Could not open device: {exception}") + + log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) + + log.info( + "Initializing channel %d in shared mode, %d rx buffers, %d tx buffers", + channel, + rx_fifo_size, + tx_fifo_size, + ) + + try: + _canlib.canChannelOpen( + self._device_handle, + channel, + constants.FALSE, + ctypes.byref(self._channel_handle), + ) + except Exception as exception: + raise CanInitializationError( + f"Could not open and initialize channel: {exception}" + ) + + # Signal TX/RX events when at least one frame has been handled + _canlib.canChannelInitialize( + self._channel_handle, + rx_fifo_size, + 1, + tx_fifo_size, + 1, + 0, + constants.CAN_FILTER_PASS, + ) + _canlib.canChannelActivate(self._channel_handle, constants.TRUE) + + pBtpSDR = IXXATBus._canptb_build( + defaults=constants.CAN_BITRATE_PRESETS, + bitrate=bitrate, + tseg1=tseg1_abr, + tseg2=tseg2_abr, + sjw=sjw_abr, + ssp=0, + ) + pBtpFDR = IXXATBus._canptb_build( + defaults=constants.CAN_DATABITRATE_PRESETS, + bitrate=data_bitrate, + tseg1=tseg1_dbr, + tseg2=tseg2_dbr, + sjw=sjw_dbr, + ssp=ssp_dbr if ssp_dbr is not None else tseg1_dbr, + ) + + log.info( + "Initializing control %d with SDR={%s}, FDR={%s}", + channel, + pBtpSDR, + pBtpFDR, + ) + _canlib.canControlOpen( + self._device_handle, channel, ctypes.byref(self._control_handle) + ) + + _canlib.canControlGetCaps( + self._control_handle, ctypes.byref(self._channel_capabilities) + ) + + # check capabilities + bOpMode = constants.CAN_OPMODE_UNDEFINED + if ( + self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_STDANDEXT + ) != 0: + # controller supportes CAN_OPMODE_STANDARD and CAN_OPMODE_EXTENDED at the same time + bOpMode |= constants.CAN_OPMODE_STANDARD # enable both 11 bits reception + if extended: # parameter from configuration + bOpMode |= constants.CAN_OPMODE_EXTENDED # enable 29 bits reception + elif ( + self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_STDANDEXT + ) != 0: + log.warning( + "Channel %d capabilities allow either basic or extended IDs, but not both. using %s according to parameter [extended=%s]", + channel, + "extended" if extended else "basic", + "True" if extended else "False", + ) + bOpMode |= ( + constants.CAN_OPMODE_EXTENDED + if extended + else constants.CAN_OPMODE_STANDARD + ) + + if ( + self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_ERRFRAME + ) != 0: + bOpMode |= constants.CAN_OPMODE_ERRFRAME + + bExMode = constants.CAN_EXMODE_DISABLED + if (self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_EXTDATA) != 0: + bExMode |= constants.CAN_EXMODE_EXTDATALEN + + if ( + self._channel_capabilities.dwFeatures & constants.CAN_FEATURE_FASTDATA + ) != 0: + bExMode |= constants.CAN_EXMODE_FASTDATA + + _canlib.canControlInitialize( + self._control_handle, + bOpMode, + bExMode, + constants.CAN_FILTER_PASS, + constants.CAN_FILTER_PASS, + 0, + 0, + ctypes.byref(pBtpSDR), + ctypes.byref(pBtpFDR), + ) + + # With receive messages, this field contains the relative reception time of + # the message in ticks. The resolution of a tick can be calculated from the fields + # dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula: + # frequency [1/s] = dwClockFreq / dwTscDivisor + self._tick_resolution = ( + self._channel_capabilities.dwTscClkFreq + / self._channel_capabilities.dwTscDivisor + ) + + # Setup filters before starting the channel + if can_filters: + log.info("The IXXAT VCI backend is filtering messages") + # Disable every message coming in + for extended in (0, 1): + _canlib.canControlSetAccFilter( + self._control_handle, + extended, + constants.CAN_ACC_CODE_NONE, + constants.CAN_ACC_MASK_NONE, + ) + for can_filter in can_filters: + # Filters define what messages are accepted + code = int(can_filter["can_id"]) + mask = int(can_filter["can_mask"]) + extended = can_filter.get("extended", False) + _canlib.canControlAddFilterIds( + self._control_handle, 1 if extended else 0, code << 1, mask << 1 + ) + log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) + + # Start the CAN controller. Messages will be forwarded to the channel + _canlib.canControlStart(self._control_handle, constants.TRUE) + + # For cyclic transmit list. Set when .send_periodic() is first called + self._scheduler = None + self._scheduler_resolution = None + self.channel = channel + + # Usually you get back 3 messages like "CAN initialized" ecc... + # Clear the FIFO by filter them out with low timeout + for _ in range(rx_fifo_size): + try: + _canlib.canChannelReadMessage( + self._channel_handle, 0, ctypes.byref(self._message) + ) + except (VCITimeout, VCIRxQueueEmptyError): + break + + super().__init__(channel=channel, can_filters=None, **kwargs) + + @staticmethod + def _canptb_build(defaults, bitrate, tseg1, tseg2, sjw, ssp): + if bitrate in defaults: + d = defaults[bitrate] + if tseg1 is None: + tseg1 = d.wTS1 + if tseg2 is None: + tseg2 = d.wTS2 + if sjw is None: + sjw = d.wSJW + if ssp is None: + ssp = d.wTDO + dw_mode = d.dwMode + else: + dw_mode = 0 + + return structures.CANBTP( + dwMode=dw_mode, + dwBPS=bitrate, + wTS1=tseg1, + wTS2=tseg2, + wSJW=sjw, + wTDO=ssp, + ) + + def _inWaiting(self): + try: + _canlib.canChannelWaitRxEvent(self._channel_handle, 0) + except VCITimeout: + return 0 + else: + return 1 + + def flush_tx_buffer(self): + """Flushes the transmit buffer on the IXXAT""" + # TODO #64: no timeout? + _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) + + def _recv_internal(self, timeout): + """Read a message from IXXAT device.""" + + # TODO: handling CAN error messages? + data_received = False + + if timeout == 0: + # Peek without waiting + try: + _canlib.canChannelPeekMessage( + self._channel_handle, ctypes.byref(self._message) + ) + except (VCITimeout, VCIRxQueueEmptyError, VCIError): + # VCIError means no frame available (canChannelPeekMessage returned different from zero) + return None, True + else: + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + else: + # Wait if no message available + if timeout is None or timeout < 0: + remaining_ms = constants.INFINITE + t0 = None + else: + timeout_ms = int(timeout * 1000) + remaining_ms = timeout_ms + t0 = perf_counter() + + while True: + try: + _canlib.canChannelReadMessage( + self._channel_handle, remaining_ms, ctypes.byref(self._message) + ) + except (VCITimeout, VCIRxQueueEmptyError): + # Ignore the 2 errors, the timeout is handled manually with the perf_counter() + pass + else: + # See if we got a data or info/error messages + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + break + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: + log.info( + CAN_INFO_MESSAGES.get( + self._message.abData[0], + "Unknown CAN info message code {}".format( + self._message.abData[0] + ), + ) + ) + + elif ( + self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR + ): + log.warning( + CAN_ERROR_MESSAGES.get( + self._message.abData[0], + "Unknown CAN error message code {}".format( + self._message.abData[0] + ), + ) + ) + + elif ( + self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS + ): + log.info(_format_can_status(self._message.abData[0])) + if self._message.abData[0] & constants.CAN_STATUS_BUSOFF: + raise VCIBusOffError() + + elif ( + self._message.uMsgInfo.Bits.type + == constants.CAN_MSGTYPE_TIMEOVR + ): + pass + else: + log.warning("Unexpected message info type") + + if t0 is not None: + remaining_ms = timeout_ms - int((perf_counter() - t0) * 1000) + if remaining_ms < 0: + break + + if not data_received: + # Timed out / can message type is not DATA + return None, True + + data_len = can.util.dlc2len(self._message.uMsgInfo.Bits.dlc) + # The _message.dwTime is a 32bit tick value and will overrun, + # so expect to see the value restarting from 0 + rx_msg = Message( + timestamp=self._message.dwTime + / self._tick_resolution, # Relative time in s + is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr), + is_fd=bool(self._message.uMsgInfo.Bits.edl), + is_rx=True, + is_error_frame=bool( + self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR + ), + bitrate_switch=bool(self._message.uMsgInfo.Bits.fdr), + error_state_indicator=bool(self._message.uMsgInfo.Bits.esi), + is_extended_id=bool(self._message.uMsgInfo.Bits.ext), + arbitration_id=self._message.dwMsgId, + dlc=data_len, + data=self._message.abData[:data_len], + channel=self.channel, + ) + + return rx_msg, True + + def send(self, msg: Message, timeout: Optional[float] = None) -> None: + """ + Sends a message on the bus. The interface may buffer the message. + + :param msg: + The message to send. + :param timeout: + Timeout after some time. + :raise: + :class:CanTimeoutError + :class:CanOperationError + """ + # This system is not designed to be very efficient + message = structures.CANMSG2() + message.uMsgInfo.Bits.type = ( + constants.CAN_MSGTYPE_ERROR + if msg.is_error_frame + else constants.CAN_MSGTYPE_DATA + ) + message.uMsgInfo.Bits.rtr = 1 if msg.is_remote_frame else 0 + message.uMsgInfo.Bits.ext = 1 if msg.is_extended_id else 0 + message.uMsgInfo.Bits.srr = 1 if self._receive_own_messages else 0 + message.uMsgInfo.Bits.fdr = 1 if msg.bitrate_switch else 0 + message.uMsgInfo.Bits.esi = 1 if msg.error_state_indicator else 0 + message.uMsgInfo.Bits.edl = 1 if msg.is_fd else 0 + message.dwMsgId = msg.arbitration_id + if msg.dlc: # this dlc means number of bytes of payload + message.uMsgInfo.Bits.dlc = can.util.len2dlc(msg.dlc) + data_len_dif = msg.dlc - len(msg.data) + data = msg.data + bytearray( + [0] * data_len_dif + ) # pad with zeros until required length + adapter = (ctypes.c_uint8 * msg.dlc).from_buffer(data) + ctypes.memmove(message.abData, adapter, msg.dlc) + + if timeout: + _canlib.canChannelSendMessage( + self._channel_handle, int(timeout * 1000), message + ) + + else: + _canlib.canChannelPostMessage(self._channel_handle, message) + + def _send_periodic_internal(self, msg, period, duration=None): + """Send a message using built-in cyclic transmit list functionality.""" + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) + caps = structures.CANCAPABILITIES2() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = ( + caps.dwCmsClkFreq / caps.dwCmsDivisor + ) # TODO: confirm + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask( + self._scheduler, msg, period, duration, self._scheduler_resolution + ) + + def shutdown(self): + if self._scheduler is not None: + _canlib.canSchedulerClose(self._scheduler) + _canlib.canChannelClose(self._channel_handle) + _canlib.canControlStart(self._control_handle, constants.FALSE) + _canlib.canControlClose(self._control_handle) + _canlib.vciDeviceClose(self._device_handle) + + +class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): + """A message in the cyclic transmit list.""" + + def __init__(self, scheduler, msgs, period, duration, resolution): + super().__init__(msgs, period, duration) + if len(self.messages) != 1: + raise ValueError( + "IXXAT Interface only supports periodic transmission of 1 element" + ) + + self._scheduler = scheduler + self._index = None + self._count = int(duration / period) if duration else 0 + + self._msg = structures.CANCYCLICTXMSG2() + self._msg.wCycleTime = int(round(period * resolution)) + self._msg.dwMsgId = self.messages[0].arbitration_id + self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA + self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0 + self._msg.uMsgInfo.Bits.rtr = 1 if self.messages[0].is_remote_frame else 0 + self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc + for i, b in enumerate(self.messages[0].data): + self._msg.abData[i] = b + self.start() + + def start(self): + """Start transmitting message (add to list if needed).""" + if self._index is None: + self._index = ctypes.c_uint32() + _canlib.canSchedulerAddMessage(self._scheduler, self._msg, self._index) + _canlib.canSchedulerStartMessage(self._scheduler, self._index, self._count) + + def pause(self): + """Pause transmitting message (keep it in the list).""" + _canlib.canSchedulerStopMessage(self._scheduler, self._index) + + def stop(self): + """Stop transmitting message (remove from list).""" + # Remove it completely instead of just stopping it to avoid filling up + # the list with permanently stopped messages + _canlib.canSchedulerRemMessage(self._scheduler, self._index) + self._index = None + + +def _format_can_status(status_flags: int): + """ + Format a status bitfield found in CAN_MSGTYPE_STATUS messages or in dwStatus + field in CANLINESTATUS. + + Valid states are defined in the CAN_STATUS_* constants in cantype.h + """ + states = [] + for flag, description in CAN_STATUS_FLAGS.items(): + if status_flags & flag: + states.append(description) + status_flags &= ~flag + + if status_flags: + states.append("unknown state 0x{:02x}".format(status_flags)) + + if states: + return "CAN status message: {}".format(", ".join(states)) + else: + return "Empty CAN status message" + + +def get_ixxat_hwids(): + """Get a list of hardware ids of all available IXXAT devices.""" + hwids = [] + device_handle = HANDLE() + device_info = structures.VCIDEVICEINFO() + + _canlib.vciEnumDeviceOpen(ctypes.byref(device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info)) + except StopIteration: + break + else: + hwids.append(device_info.UniqueHardwareId.AsChar.decode("ascii")) + _canlib.vciEnumDeviceClose(device_handle) + + return hwids diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index e911a580a..1dbc22a44 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -4,6 +4,8 @@ Copyright (C) 2016 Giuseppe Corbelli """ +from . import structures + FALSE = 0 TRUE = 1 @@ -119,6 +121,12 @@ CAN_OPMODE_LISTONLY = 0x08 CAN_OPMODE_LOWSPEED = 0x10 +# Extended operating modes +CAN_EXMODE_DISABLED = 0x00 +CAN_EXMODE_EXTDATALEN = 0x01 +CAN_EXMODE_FASTDATA = 0x02 +CAN_EXMODE_NONISOCANFD = 0x04 + # Message types CAN_MSGTYPE_DATA = 0 CAN_MSGTYPE_INFO = 1 @@ -146,3 +154,104 @@ # acceptance code and mask to reject all CAN IDs CAN_ACC_MASK_NONE = 0xFFFFFFFF CAN_ACC_CODE_NONE = 0x80000000 + +# BTMODEs +CAN_BTMODE_RAW = 0x00000001 # raw mode +CAN_BTMODE_TSM = 0x00000002 # triple sampling mode + + +CAN_FILTER_VOID = 0x00 # invalid or unknown filter mode (do not use for initialization) +CAN_FILTER_LOCK = 0x01 # lock filter (inhibit all IDs) +CAN_FILTER_PASS = 0x02 # bypass filter (pass all IDs) +CAN_FILTER_INCL = 0x03 # inclusive filtering (pass registered IDs) +CAN_FILTER_EXCL = 0x04 # exclusive filtering (inhibit registered IDs) + + +CAN_MSGFLAGS_DLC = 0x0F # [bit 0] data length code +CAN_MSGFLAGS_OVR = 0x10 # [bit 4] data overrun flag +CAN_MSGFLAGS_SRR = 0x20 # [bit 5] self reception request +CAN_MSGFLAGS_RTR = 0x40 # [bit 6] remote transmission request +CAN_MSGFLAGS_EXT = 0x80 # [bit 7] frame format (0=11-bit, 1=29-bit) + + +CAN_MSGFLAGS2_SSM = 0x01 # [bit 0] single shot mode +CAN_MSGFLAGS2_HPM = 0x02 # [bit 1] high priority message +CAN_MSGFLAGS2_EDL = 0x04 # [bit 2] extended data length +CAN_MSGFLAGS2_FDR = 0x08 # [bit 3] fast data bit rate +CAN_MSGFLAGS2_ESI = 0x10 # [bit 4] error state indicator +CAN_MSGFLAGS2_RES = 0xE0 # [bit 5..7] reserved bits + + +CAN_ACCEPT_REJECT = 0x00 # message not accepted +CAN_ACCEPT_ALWAYS = 0xFF # message always accepted +CAN_ACCEPT_FILTER_1 = 0x01 # message accepted by filter 1 +CAN_ACCEPT_FILTER_2 = 0x02 # message accepted by filter 2 +CAN_ACCEPT_PASSEXCL = 0x03 # message passes exclusion filter + + +CAN_FEATURE_STDOREXT = 0x00000001 # 11 OR 29 bit (exclusive) +CAN_FEATURE_STDANDEXT = 0x00000002 # 11 AND 29 bit (simultaneous) +CAN_FEATURE_RMTFRAME = 0x00000004 # reception of remote frames +CAN_FEATURE_ERRFRAME = 0x00000008 # reception of error frames +CAN_FEATURE_BUSLOAD = 0x00000010 # bus load measurement +CAN_FEATURE_IDFILTER = 0x00000020 # exact message filter +CAN_FEATURE_LISTONLY = 0x00000040 # listen only mode +CAN_FEATURE_SCHEDULER = 0x00000080 # cyclic message scheduler +CAN_FEATURE_GENERRFRM = 0x00000100 # error frame generation +CAN_FEATURE_DELAYEDTX = 0x00000200 # delayed message transmitter +CAN_FEATURE_SINGLESHOT = 0x00000400 # single shot mode +CAN_FEATURE_HIGHPRIOR = 0x00000800 # high priority message +CAN_FEATURE_AUTOBAUD = 0x00001000 # automatic bit rate detection +CAN_FEATURE_EXTDATA = 0x00002000 # extended data length (CANFD) +CAN_FEATURE_FASTDATA = 0x00004000 # fast data bit rate (CANFD) +CAN_FEATURE_ISOFRAME = 0x00008000 # ISO conform frame (CANFD) +CAN_FEATURE_NONISOFRM = ( + 0x00010000 # non ISO conform frame (CANFD) (different CRC computation) +) +CAN_FEATURE_64BITTSC = 0x00020000 # 64-bit time stamp counter + + +CAN_BITRATE_PRESETS = { + 250000: structures.CANBTP( + dwMode=0, dwBPS=250000, wTS1=6400, wTS2=1600, wSJW=1600, wTDO=0 + ), # SP = 80,0% + 500000: structures.CANBTP( + dwMode=0, dwBPS=500000, wTS1=6400, wTS2=1600, wSJW=1600, wTDO=0 + ), # SP = 80,0% + 1000000: structures.CANBTP( + dwMode=0, dwBPS=1000000, wTS1=6400, wTS2=1600, wSJW=1600, wTDO=0 + ), # SP = 80,0% +} + +CAN_DATABITRATE_PRESETS = { + 500000: structures.CANBTP( + dwMode=0, dwBPS=500000, wTS1=6400, wTS2=1600, wSJW=1600, wTDO=6400 + ), # SP = 80,0% + 833333: structures.CANBTP( + dwMode=0, dwBPS=833333, wTS1=1600, wTS2=400, wSJW=400, wTDO=1620 + ), # SP = 80,0% + 1000000: structures.CANBTP( + dwMode=0, dwBPS=1000000, wTS1=1600, wTS2=400, wSJW=400, wTDO=1600 + ), # SP = 80,0% + 1538461: structures.CANBTP( + dwMode=0, dwBPS=1538461, wTS1=1000, wTS2=300, wSJW=300, wTDO=1040 + ), # SP = 76,9% + 2000000: structures.CANBTP( + dwMode=0, dwBPS=2000000, wTS1=1600, wTS2=400, wSJW=400, wTDO=1600 + ), # SP = 80,0% + 4000000: structures.CANBTP( + dwMode=0, dwBPS=4000000, wTS1=800, wTS2=200, wSJW=200, wTDO=800 + ), # SP = 80,0% + 5000000: structures.CANBTP( + dwMode=0, dwBPS=5000000, wTS1=600, wTS2=200, wSJW=200, wTDO=600 + ), # SP = 75,0% + 6666666: structures.CANBTP( + dwMode=0, dwBPS=6666666, wTS1=400, wTS2=200, wSJW=200, wTDO=402 + ), # SP = 66,7% + 8000000: structures.CANBTP( + dwMode=0, dwBPS=8000000, wTS1=400, wTS2=100, wSJW=100, wTDO=250 + ), # SP = 80,0% + 10000000: structures.CANBTP( + dwMode=0, dwBPS=10000000, wTS1=300, wTS2=100, wSJW=100, wTDO=200 + ), # SP = 75,0% +} diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index 73c01823d..f76a39a38 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -114,24 +114,34 @@ class CANCAPABILITIES(ctypes.Structure): class CANMSGINFO(ctypes.Union): class Bytes(ctypes.Structure): _fields_ = [ - ("bType", ctypes.c_uint8), - ("bAddFlags", ctypes.c_uint8), - ("bFlags", ctypes.c_uint8), - ("bAccept", ctypes.c_uint8), + ("bType", ctypes.c_uint8), # type (see CAN_MSGTYPE_ constants) + ( + "bAddFlags", + ctypes.c_uint8, + ), # extended flags (see CAN_MSGFLAGS2_ constants) + ("bFlags", ctypes.c_uint8), # flags (see CAN_MSGFLAGS_ constants) + ("bAccept", ctypes.c_uint8), # accept code (see CAN_ACCEPT_ constants) ] class Bits(ctypes.Structure): _fields_ = [ - ("type", ctypes.c_uint32, 8), - ("ssm", ctypes.c_uint32, 1), - ("hi", ctypes.c_uint32, 2), - ("res", ctypes.c_uint32, 5), - ("dlc", ctypes.c_uint32, 4), - ("ovr", ctypes.c_uint32, 1), - ("srr", ctypes.c_uint32, 1), - ("rtr", ctypes.c_uint32, 1), - ("ext", ctypes.c_uint32, 1), - ("afc", ctypes.c_uint32, 8), + ("type", ctypes.c_uint32, 8), # type (see CAN_MSGTYPE_ constants) + ("ssm", ctypes.c_uint32, 1), # single shot mode + ("hpm", ctypes.c_uint32, 1), # high priority message + ("edl", ctypes.c_uint32, 1), # extended data length + ("fdr", ctypes.c_uint32, 1), # fast data bit rate + ("esi", ctypes.c_uint32, 1), # error state indicator + ("res", ctypes.c_uint32, 3), # reserved set to 0 + ("dlc", ctypes.c_uint32, 4), # data length code + ("ovr", ctypes.c_uint32, 1), # data overrun + ("srr", ctypes.c_uint32, 1), # self reception request + ("rtr", ctypes.c_uint32, 1), # remote transmission request + ( + "ext", + ctypes.c_uint32, + 1, + ), # extended frame format (0=standard, 1=extended) + ("afc", ctypes.c_uint32, 8), # accept code (see CAN_ACCEPT_ constants) ] _fields_ = [("Bytes", Bytes), ("Bits", Bits)] @@ -164,3 +174,123 @@ class CANCYCLICTXMSG(ctypes.Structure): PCANCYCLICTXMSG = ctypes.POINTER(CANCYCLICTXMSG) + + +class CANBTP(ctypes.Structure): + _fields_ = [ + ("dwMode", ctypes.c_uint32), # timing mode (see CAN_BTMODE_ const) + ("dwBPS", ctypes.c_uint32), # bits per second or prescaler (see CAN_BTMODE_RAW) + ("wTS1", ctypes.c_uint16), # length of time segment 1 in quanta + ("wTS2", ctypes.c_uint16), # length of time segment 2 in quanta + ("wSJW", ctypes.c_uint16), # re-synchronization jump width im quanta + ( + "wTDO", + ctypes.c_uint16, + ), # transceiver delay offset (SSP offset) in quanta (0 = disabled, 0xFFFF = simplified SSP positioning) + ] + + def __str__(self): + return "dwMode=%d, dwBPS=%d, wTS1=%d, wTS2=%d, wSJW=%d, wTDO=%d" % ( + self.dwMode, + self.dwBPS, + self.wTS1, + self.wTS2, + self.wSJW, + self.wTDO, + ) + + +PCANBTP = ctypes.POINTER(CANBTP) + + +class CANCAPABILITIES2(ctypes.Structure): + _fields_ = [ + ("wCtrlType", ctypes.c_uint16), # Type of CAN controller (see CAN_CTRL_ const) + ("wBusCoupling", ctypes.c_uint16), # Type of Bus coupling (see CAN_BUSC_ const) + ( + "dwFeatures", + ctypes.c_uint32, + ), # supported features (see CAN_FEATURE_ constants) + ("dwCanClkFreq", ctypes.c_uint32), # CAN clock frequency [Hz] + ("sSdrRangeMin", CANBTP), # minimum bit timing values for standard bit rate + ("sSdrRangeMax", CANBTP), # maximum bit timing values for standard bit rate + ("sFdrRangeMin", CANBTP), # minimum bit timing values for fast data bit rate + ("sFdrRangeMax", CANBTP), # maximum bit timing values for fast data bit rate + ( + "dwTscClkFreq", + ctypes.c_uint32, + ), # clock frequency of the time stamp counter [Hz] + ("dwTscDivisor", ctypes.c_uint32), # divisor for the message time stamp counter + ( + "dwCmsClkFreq", + ctypes.c_uint32, + ), # clock frequency of cyclic message scheduler [Hz] + ("dwCmsDivisor", ctypes.c_uint32), # divisor for the cyclic message scheduler + ( + "dwCmsMaxTicks", + ctypes.c_uint32, + ), # maximum tick count value of the cyclic message + ( + "dwDtxClkFreq", + ctypes.c_uint32, + ), # clock frequency of the delayed message transmitter [Hz] + ( + "dwDtxDivisor", + ctypes.c_uint32, + ), # divisor for the delayed message transmitter + ( + "dwDtxMaxTicks", + ctypes.c_uint32, + ), # maximum tick count value of the delayed message transmitter + ] + + +PCANCAPABILITIES2 = ctypes.POINTER(CANCAPABILITIES2) + + +class CANLINESTATUS2(ctypes.Structure): + _fields_ = [ + ("bOpMode", ctypes.c_uint8), # current CAN operating mode + ("bExMode", ctypes.c_uint8), # current CAN extended operating mode + ("bBusLoad", ctypes.c_uint8), # average bus load in percent (0..100) + ("bReserved", ctypes.c_uint8), # reserved set to 0 + ("sBtpSdr", ctypes.c_uint8), # standard bit rate timing + ("sBtpFdr", ctypes.c_uint8), # fast data bit rate timing + ("dwStatus", ctypes.c_uint32), # status of the CAN controller (see CAN_STATUS_) + ] + + +PCANLINESTATUS2 = ctypes.POINTER(CANLINESTATUS2) + + +class CANMSG2(ctypes.Structure): + _fields_ = [ + ("dwTime", ctypes.c_uint32), # time stamp for receive message + ("rsvd", ctypes.c_uint32), # reserved (set to 0) + ("dwMsgId", ctypes.c_uint32), # CAN message identifier (INTEL format) + ("uMsgInfo", CANMSGINFO), # message information (bit field) + ("abData", ctypes.c_uint8 * 64), # message data + ] + + +PCANMSG2 = ctypes.POINTER(CANMSG2) + + +class CANCYCLICTXMSG2(ctypes.Structure): + _fields_ = [ + ("wCycleTime", ctypes.c_uint16), # cycle time for the message in ticks + ( + "bIncrMode", + ctypes.c_uint8, + ), # auto increment mode (see CAN_CTXMSG_INC_ const) + ( + "bByteIndex", + ctypes.c_uint8, + ), # index of the byte within abData[] to increment + ("dwMsgId", ctypes.c_uint32), # message identifier (INTEL format) + ("uMsgInfo", CANMSGINFO), # message information (bit field) + ("abData", ctypes.c_uint8 * 64), # message data + ] + + +PCANCYCLICTXMSG2 = ctypes.POINTER(CANCYCLICTXMSG2) diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 929221733..28fb6f314 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -36,11 +36,22 @@ Python-can will search for the first IXXAT device available and open the first c ``interface`` and ``channel`` parameters are interpreted by frontend ``can.interfaces.interface`` module, while the following parameters are optional and are interpreted by IXXAT implementation. -* ``bitrate`` (default 500000) Channel bitrate -* ``UniqueHardwareId`` (default first device) Unique hardware ID of the IXXAT device -* ``rxFifoSize`` (default 16) Number of RX mailboxes -* ``txFifoSize`` (default 16) Number of TX mailboxes -* ``extended`` (default False) Allow usage of extended IDs +* ``receive_own_messages`` (default False) Enable self-reception of sent messages. +* ``unique_hardware_id`` (default first device) Unique hardware ID of the IXXAT device. +* ``extended`` (default True) Allow usage of extended IDs. +* ``fd`` (default False) Enable CAN-FD capabilities. +* ``rx_fifo_size`` (default 16 for CAN, 1024 for CAN-FD) Number of RX mailboxes. +* ``tx_fifo_size`` (default 16 for CAN, 128 for CAN-FD) Number of TX mailboxes. +* ``bitrate`` (default 500000) Channel bitrate. +* ``data_bitrate`` (defaults to 2Mbps) Channel data bitrate (only canfd, to use when message bitrate_switch is used). +* ``sjw_abr`` (optional, only canfd) Bus timing value sample jump width (arbitration). +* ``tseg1_abr`` (optional, only canfd) Bus timing value tseg1 (arbitration). +* ``tseg2_abr`` (optional, only canfd) Bus timing value tseg2 (arbitration). +* ``sjw_dbr`` (optional, only used if baudrate switch enabled) Bus timing value sample jump width (data). +* ``tseg1_dbr`` (optional, only used if baudrate switch enabled) Bus timing value tseg1 (data). +* ``tseg2_dbr`` (optional, only used if baudrate switch enabled) Bus timing value tseg2 (data). +* ``ssp_dbr`` (optional, only used if baudrate switch enabled) Secondary sample point (data). + Filtering @@ -78,5 +89,6 @@ explicitly instantiated by the caller. - ``recv()`` is a blocking call with optional timeout. - ``send()`` is not blocking but may raise a VCIError if the TX FIFO is full -RX and TX FIFO sizes are configurable with ``rxFifoSize`` and ``txFifoSize`` +RX and TX FIFO sizes are configurable with ``rx_fifo_size`` and ``tx_fifo_size`` options, defaulting to 16 for both. + diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 55618c769..ccd985051 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -26,13 +26,13 @@ def test_bus_creation(self): with self.assertRaises(ValueError): can.Bus(interface="ixxat", channel=-1) - # rxFifoSize must be > 0 + # rx_fifo_size must be > 0 with self.assertRaises(ValueError): - can.Bus(interface="ixxat", channel=0, rxFifoSize=0) + can.Bus(interface="ixxat", channel=0, rx_fifo_size=0) - # txFifoSize must be > 0 + # tx_fifo_size must be > 0 with self.assertRaises(ValueError): - can.Bus(interface="ixxat", channel=0, txFifoSize=0) + can.Bus(interface="ixxat", channel=0, tx_fifo_size=0) class HardwareTestCase(unittest.TestCase): diff --git a/test/test_interface_ixxat_fd.py b/test/test_interface_ixxat_fd.py new file mode 100644 index 000000000..0aa999a21 --- /dev/null +++ b/test/test_interface_ixxat_fd.py @@ -0,0 +1,62 @@ +""" +Unittest for ixxat interface using fd option. + +Run only this test: +python setup.py test --addopts "--verbose -s test/test_interface_ixxat_fd.py" +""" + +import unittest +import can + + +class SoftwareTestCase(unittest.TestCase): + """ + Test cases that test the software only and do not rely on an existing/connected hardware. + """ + + def setUp(self): + try: + bus = can.Bus(interface="ixxat", fd=True, channel=0) + bus.shutdown() + except can.CanInterfaceNotImplementedError: + raise unittest.SkipTest("not available on this platform") + + def test_bus_creation(self): + # channel must be >= 0 + with self.assertRaises(ValueError): + can.Bus(interface="ixxat", fd=True, channel=-1) + + # rx_fifo_size must be > 0 + with self.assertRaises(ValueError): + can.Bus(interface="ixxat", fd=True, channel=0, rx_fifo_size=0) + + # tx_fifo_size must be > 0 + with self.assertRaises(ValueError): + can.Bus(interface="ixxat", fd=True, channel=0, tx_fifo_size=0) + + +class HardwareTestCase(unittest.TestCase): + """ + Test cases that rely on an existing/connected hardware. + """ + + def setUp(self): + try: + bus = can.Bus(interface="ixxat", fd=True, channel=0) + bus.shutdown() + except can.CanInterfaceNotImplementedError: + raise unittest.SkipTest("not available on this platform") + + def test_bus_creation(self): + # non-existent channel -> use arbitrary high value + with self.assertRaises(can.CanInitializationError): + can.Bus(interface="ixxat", fd=True, channel=0xFFFF) + + def test_send_after_shutdown(self): + with can.Bus(interface="ixxat", fd=True, channel=0) as bus: + with self.assertRaises(can.CanOperationError): + bus.send(can.Message(arbitration_id=0x3FF, dlc=0)) + + +if __name__ == "__main__": + unittest.main() From d603ff959d93cf5afa494c3900f78b49597dcf66 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 7 Dec 2021 20:07:46 +0100 Subject: [PATCH 0766/1235] Specific Exceptions: Adapting pcan interface (#1152) * Specific Exceptions: Adapting pcan interface Part of #1046. Also partly cleans up the docstrings in `basic.py` and uses some subtests in `test_pcan.py` (the parameter `name` was else unused in most parameterized tests). * Run black formatter * Remove wildcard import; move some dicts/lists to basic.py --- can/interfaces/pcan/basic.py | 155 +++++++++++++++++++++-------- can/interfaces/pcan/pcan.py | 186 ++++++++++++++--------------------- test/test_pcan.py | 166 ++++++++++++++++--------------- 3 files changed, 275 insertions(+), 232 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 91996680d..9fe7d1257 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -8,17 +8,12 @@ # # ------------------------------------------------------------------ # Author : Keneth Wagner -# Last change: 14.01.2021 Wagner -# -# Language: Python 2.7, 3.7 # ------------------------------------------------------------------ # # Copyright (C) 1999-2021 PEAK-System Technik GmbH, Darmstadt # more Info at http://www.peak-system.com -# # Module Imports -# from ctypes import * from ctypes.util import find_library from string import * @@ -553,6 +548,102 @@ class TPCANChannelInformation(Structure): ] # Availability status of a PCAN-Channel +# /////////////////////////////////////////////////////////// +# Additional objects +# /////////////////////////////////////////////////////////// + +PCAN_BITRATES = { + 1000000: PCAN_BAUD_1M, + 800000: PCAN_BAUD_800K, + 500000: PCAN_BAUD_500K, + 250000: PCAN_BAUD_250K, + 125000: PCAN_BAUD_125K, + 100000: PCAN_BAUD_100K, + 95000: PCAN_BAUD_95K, + 83000: PCAN_BAUD_83K, + 50000: PCAN_BAUD_50K, + 47000: PCAN_BAUD_47K, + 33000: PCAN_BAUD_33K, + 20000: PCAN_BAUD_20K, + 10000: PCAN_BAUD_10K, + 5000: PCAN_BAUD_5K, +} + +PCAN_FD_PARAMETER_LIST = ( + "nom_brp", + "nom_tseg1", + "nom_tseg2", + "nom_sjw", + "data_brp", + "data_tseg1", + "data_tseg2", + "data_sjw", +) + +PCAN_CHANNEL_NAMES = { + "PCAN_NONEBUS": PCAN_NONEBUS, + "PCAN_ISABUS1": PCAN_ISABUS1, + "PCAN_ISABUS2": PCAN_ISABUS2, + "PCAN_ISABUS3": PCAN_ISABUS3, + "PCAN_ISABUS4": PCAN_ISABUS4, + "PCAN_ISABUS5": PCAN_ISABUS5, + "PCAN_ISABUS6": PCAN_ISABUS6, + "PCAN_ISABUS7": PCAN_ISABUS7, + "PCAN_ISABUS8": PCAN_ISABUS8, + "PCAN_DNGBUS1": PCAN_DNGBUS1, + "PCAN_PCIBUS1": PCAN_PCIBUS1, + "PCAN_PCIBUS2": PCAN_PCIBUS2, + "PCAN_PCIBUS3": PCAN_PCIBUS3, + "PCAN_PCIBUS4": PCAN_PCIBUS4, + "PCAN_PCIBUS5": PCAN_PCIBUS5, + "PCAN_PCIBUS6": PCAN_PCIBUS6, + "PCAN_PCIBUS7": PCAN_PCIBUS7, + "PCAN_PCIBUS8": PCAN_PCIBUS8, + "PCAN_PCIBUS9": PCAN_PCIBUS9, + "PCAN_PCIBUS10": PCAN_PCIBUS10, + "PCAN_PCIBUS11": PCAN_PCIBUS11, + "PCAN_PCIBUS12": PCAN_PCIBUS12, + "PCAN_PCIBUS13": PCAN_PCIBUS13, + "PCAN_PCIBUS14": PCAN_PCIBUS14, + "PCAN_PCIBUS15": PCAN_PCIBUS15, + "PCAN_PCIBUS16": PCAN_PCIBUS16, + "PCAN_USBBUS1": PCAN_USBBUS1, + "PCAN_USBBUS2": PCAN_USBBUS2, + "PCAN_USBBUS3": PCAN_USBBUS3, + "PCAN_USBBUS4": PCAN_USBBUS4, + "PCAN_USBBUS5": PCAN_USBBUS5, + "PCAN_USBBUS6": PCAN_USBBUS6, + "PCAN_USBBUS7": PCAN_USBBUS7, + "PCAN_USBBUS8": PCAN_USBBUS8, + "PCAN_USBBUS9": PCAN_USBBUS9, + "PCAN_USBBUS10": PCAN_USBBUS10, + "PCAN_USBBUS11": PCAN_USBBUS11, + "PCAN_USBBUS12": PCAN_USBBUS12, + "PCAN_USBBUS13": PCAN_USBBUS13, + "PCAN_USBBUS14": PCAN_USBBUS14, + "PCAN_USBBUS15": PCAN_USBBUS15, + "PCAN_USBBUS16": PCAN_USBBUS16, + "PCAN_PCCBUS1": PCAN_PCCBUS1, + "PCAN_PCCBUS2": PCAN_PCCBUS2, + "PCAN_LANBUS1": PCAN_LANBUS1, + "PCAN_LANBUS2": PCAN_LANBUS2, + "PCAN_LANBUS3": PCAN_LANBUS3, + "PCAN_LANBUS4": PCAN_LANBUS4, + "PCAN_LANBUS5": PCAN_LANBUS5, + "PCAN_LANBUS6": PCAN_LANBUS6, + "PCAN_LANBUS7": PCAN_LANBUS7, + "PCAN_LANBUS8": PCAN_LANBUS8, + "PCAN_LANBUS9": PCAN_LANBUS9, + "PCAN_LANBUS10": PCAN_LANBUS10, + "PCAN_LANBUS11": PCAN_LANBUS11, + "PCAN_LANBUS12": PCAN_LANBUS12, + "PCAN_LANBUS13": PCAN_LANBUS13, + "PCAN_LANBUS14": PCAN_LANBUS14, + "PCAN_LANBUS15": PCAN_LANBUS15, + "PCAN_LANBUS16": PCAN_LANBUS16, +} + + # /////////////////////////////////////////////////////////// # PCAN-Basic API function declarations # /////////////////////////////////////////////////////////// @@ -601,8 +692,7 @@ def Initialize( Interrupt=c_ushort(0), ): - """ - Initializes a PCAN Channel + """Initializes a PCAN Channel Parameters: Channel : A TPCANHandle representing a PCAN Channel @@ -627,8 +717,7 @@ def Initialize( # def InitializeFD(self, Channel, BitrateFD): - """ - Initializes a FD capable PCAN Channel + """Initializes a FD capable PCAN Channel Parameters: Channel : The handle of a FD capable PCAN Channel @@ -659,8 +748,7 @@ def InitializeFD(self, Channel, BitrateFD): # def Uninitialize(self, Channel): - """ - Uninitializes one or all PCAN Channels initialized by CAN_Initialize + """Uninitializes one or all PCAN Channels initialized by CAN_Initialize Remarks: Giving the TPCANHandle value "PCAN_NONEBUS", uninitialize all initialized channels @@ -682,8 +770,7 @@ def Uninitialize(self, Channel): # def Reset(self, Channel): - """ - Resets the receive and transmit queues of the PCAN Channel + """Resets the receive and transmit queues of the PCAN Channel Remarks: A reset of the CAN controller is not performed @@ -705,8 +792,7 @@ def Reset(self, Channel): # def GetStatus(self, Channel): - """ - Gets the current status of a PCAN Channel + """Gets the current status of a PCAN Channel Parameters: Channel : A TPCANHandle representing a PCAN Channel @@ -725,8 +811,7 @@ def GetStatus(self, Channel): # def Read(self, Channel): - """ - Reads a CAN message from the receive queue of a PCAN Channel + """Reads a CAN message from the receive queue of a PCAN Channel Remarks: The return value of this method is a 3-touple, where @@ -740,7 +825,7 @@ def Read(self, Channel): Channel : A TPCANHandle representing a PCAN Channel Returns: - A touple with three values + A tuple with three values """ try: msg = TPCANMsg() @@ -755,8 +840,7 @@ def Read(self, Channel): # def ReadFD(self, Channel): - """ - Reads a CAN message from the receive queue of a FD capable PCAN Channel + """Reads a CAN message from the receive queue of a FD capable PCAN Channel Remarks: The return value of this method is a 3-touple, where @@ -770,7 +854,7 @@ def ReadFD(self, Channel): Channel : The handle of a FD capable PCAN Channel Returns: - A touple with three values + A tuple with three values """ try: msg = TPCANMsgFD() @@ -785,8 +869,7 @@ def ReadFD(self, Channel): # def Write(self, Channel, MessageBuffer): - """ - Transmits a CAN message + """Transmits a CAN message Parameters: Channel : A TPCANHandle representing a PCAN Channel @@ -806,8 +889,7 @@ def Write(self, Channel, MessageBuffer): # def WriteFD(self, Channel, MessageBuffer): - """ - Transmits a CAN message over a FD capable PCAN Channel + """Transmits a CAN message over a FD capable PCAN Channel Parameters: Channel : The handle of a FD capable PCAN Channel @@ -827,8 +909,7 @@ def WriteFD(self, Channel, MessageBuffer): # def FilterMessages(self, Channel, FromID, ToID, Mode): - """ - Configures the reception filter + """Configures the reception filter Remarks: The message filter will be expanded with every call to this function. @@ -855,8 +936,7 @@ def FilterMessages(self, Channel, FromID, ToID, Mode): # def GetValue(self, Channel, Parameter): - """ - Retrieves a PCAN Channel value + """Retrieves a PCAN Channel value Remarks: Parameters can be present or not according with the kind @@ -872,7 +952,7 @@ def GetValue(self, Channel, Parameter): Parameter : The TPCANParameter parameter to get Returns: - A touple with 2 values + A tuple with 2 values """ try: if ( @@ -912,9 +992,8 @@ def GetValue(self, Channel, Parameter): # def SetValue(self, Channel, Parameter, Buffer): - """ - Returns a descriptive text of a given TPCANStatus error - code, in any desired language + """Returns a descriptive text of a given TPCANStatus error + code, in any desired language Remarks: Parameters can be present or not according with the kind @@ -951,8 +1030,7 @@ def SetValue(self, Channel, Parameter, Buffer): def GetErrorText(self, Error, Language=0): - """ - Configures or sets a PCAN Channel value + """Configures or sets a PCAN Channel value Remarks: @@ -969,7 +1047,7 @@ def GetErrorText(self, Error, Language=0): Language : Indicates a 'Primary language ID' (Default is Neutral(0)) Returns: - A touple with 2 values + A tuple with 2 values """ try: mybuffer = create_string_buffer(256) @@ -981,8 +1059,7 @@ def GetErrorText(self, Error, Language=0): def LookUpChannel(self, Parameters): - """ - Finds a PCAN-Basic channel that matches with the given parameters + """Finds a PCAN-Basic channel that matches with the given parameters Remarks: @@ -995,7 +1072,7 @@ def LookUpChannel(self, Parameters): to be matched within a PCAN-Basic channel Returns: - A touple with 2 values + A tuple with 2 values """ try: mybuffer = TPCANHandle(0) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 670918a17..959874a64 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -5,12 +5,54 @@ import logging import time from datetime import datetime +import platform from typing import Optional -from can import CanError, Message, BusABC -from can.bus import BusState -from can.util import len2dlc, dlc2len -from .basic import * + +from ...message import Message +from ...bus import BusABC, BusState +from ...util import len2dlc, dlc2len +from ...exceptions import CanError, CanOperationError, CanInitializationError + + +from .basic import ( + PCAN_BITRATES, + PCAN_FD_PARAMETER_LIST, + PCAN_CHANNEL_NAMES, + PCAN_BAUD_500K, + PCAN_TYPE_ISA, + PCANBasic, + PCAN_ERROR_OK, + PCAN_ALLOW_ERROR_FRAMES, + PCAN_PARAMETER_ON, + PCAN_RECEIVE_EVENT, + PCAN_DEVICE_NUMBER, + PCAN_ERROR_QRCVEMPTY, + PCAN_ERROR_BUSLIGHT, + PCAN_ERROR_BUSHEAVY, + PCAN_MESSAGE_EXTENDED, + PCAN_MESSAGE_RTR, + PCAN_MESSAGE_FD, + PCAN_MESSAGE_BRS, + PCAN_MESSAGE_ESI, + PCAN_MESSAGE_ERRFRAME, + PCAN_MESSAGE_STANDARD, + TPCANMsgFD, + TPCANMsg, + PCAN_CHANNEL_IDENTIFYING, + PCAN_LISTEN_ONLY, + PCAN_PARAMETER_OFF, + TPCANHandle, + PCAN_PCIBUS1, + PCAN_USBBUS1, + PCAN_PCCBUS1, + PCAN_LANBUS1, + PCAN_CHANNEL_CONDITION, + PCAN_CHANNEL_AVAILABLE, + PCAN_CHANNEL_FEATURES, + FEATURE_FD_CAPABLE, + PCAN_DICT_STATUS, +) # Set up logging @@ -51,99 +93,6 @@ HAS_EVENTS = False -pcan_bitrate_objs = { - 1000000: PCAN_BAUD_1M, - 800000: PCAN_BAUD_800K, - 500000: PCAN_BAUD_500K, - 250000: PCAN_BAUD_250K, - 125000: PCAN_BAUD_125K, - 100000: PCAN_BAUD_100K, - 95000: PCAN_BAUD_95K, - 83000: PCAN_BAUD_83K, - 50000: PCAN_BAUD_50K, - 47000: PCAN_BAUD_47K, - 33000: PCAN_BAUD_33K, - 20000: PCAN_BAUD_20K, - 10000: PCAN_BAUD_10K, - 5000: PCAN_BAUD_5K, -} - - -pcan_fd_parameter_list = [ - "nom_brp", - "nom_tseg1", - "nom_tseg2", - "nom_sjw", - "data_brp", - "data_tseg1", - "data_tseg2", - "data_sjw", -] - -pcan_channel_names = { - "PCAN_NONEBUS": PCAN_NONEBUS, - "PCAN_ISABUS1": PCAN_ISABUS1, - "PCAN_ISABUS2": PCAN_ISABUS2, - "PCAN_ISABUS3": PCAN_ISABUS3, - "PCAN_ISABUS4": PCAN_ISABUS4, - "PCAN_ISABUS5": PCAN_ISABUS5, - "PCAN_ISABUS6": PCAN_ISABUS6, - "PCAN_ISABUS7": PCAN_ISABUS7, - "PCAN_ISABUS8": PCAN_ISABUS8, - "PCAN_DNGBUS1": PCAN_DNGBUS1, - "PCAN_PCIBUS1": PCAN_PCIBUS1, - "PCAN_PCIBUS2": PCAN_PCIBUS2, - "PCAN_PCIBUS3": PCAN_PCIBUS3, - "PCAN_PCIBUS4": PCAN_PCIBUS4, - "PCAN_PCIBUS5": PCAN_PCIBUS5, - "PCAN_PCIBUS6": PCAN_PCIBUS6, - "PCAN_PCIBUS7": PCAN_PCIBUS7, - "PCAN_PCIBUS8": PCAN_PCIBUS8, - "PCAN_PCIBUS9": PCAN_PCIBUS9, - "PCAN_PCIBUS10": PCAN_PCIBUS10, - "PCAN_PCIBUS11": PCAN_PCIBUS11, - "PCAN_PCIBUS12": PCAN_PCIBUS12, - "PCAN_PCIBUS13": PCAN_PCIBUS13, - "PCAN_PCIBUS14": PCAN_PCIBUS14, - "PCAN_PCIBUS15": PCAN_PCIBUS15, - "PCAN_PCIBUS16": PCAN_PCIBUS16, - "PCAN_USBBUS1": PCAN_USBBUS1, - "PCAN_USBBUS2": PCAN_USBBUS2, - "PCAN_USBBUS3": PCAN_USBBUS3, - "PCAN_USBBUS4": PCAN_USBBUS4, - "PCAN_USBBUS5": PCAN_USBBUS5, - "PCAN_USBBUS6": PCAN_USBBUS6, - "PCAN_USBBUS7": PCAN_USBBUS7, - "PCAN_USBBUS8": PCAN_USBBUS8, - "PCAN_USBBUS9": PCAN_USBBUS9, - "PCAN_USBBUS10": PCAN_USBBUS10, - "PCAN_USBBUS11": PCAN_USBBUS11, - "PCAN_USBBUS12": PCAN_USBBUS12, - "PCAN_USBBUS13": PCAN_USBBUS13, - "PCAN_USBBUS14": PCAN_USBBUS14, - "PCAN_USBBUS15": PCAN_USBBUS15, - "PCAN_USBBUS16": PCAN_USBBUS16, - "PCAN_PCCBUS1": PCAN_PCCBUS1, - "PCAN_PCCBUS2": PCAN_PCCBUS2, - "PCAN_LANBUS1": PCAN_LANBUS1, - "PCAN_LANBUS2": PCAN_LANBUS2, - "PCAN_LANBUS3": PCAN_LANBUS3, - "PCAN_LANBUS4": PCAN_LANBUS4, - "PCAN_LANBUS5": PCAN_LANBUS5, - "PCAN_LANBUS6": PCAN_LANBUS6, - "PCAN_LANBUS7": PCAN_LANBUS7, - "PCAN_LANBUS8": PCAN_LANBUS8, - "PCAN_LANBUS9": PCAN_LANBUS9, - "PCAN_LANBUS10": PCAN_LANBUS10, - "PCAN_LANBUS11": PCAN_LANBUS11, - "PCAN_LANBUS12": PCAN_LANBUS12, - "PCAN_LANBUS13": PCAN_LANBUS13, - "PCAN_LANBUS14": PCAN_LANBUS14, - "PCAN_LANBUS15": PCAN_LANBUS15, - "PCAN_LANBUS16": PCAN_LANBUS16, -} - - class PcanBus(BusABC): def __init__( self, @@ -245,14 +194,14 @@ def __init__( """ self.channel_info = str(channel) self.fd = kwargs.get("fd", False) - pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) + pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K) hwtype = PCAN_TYPE_ISA ioport = 0x02A0 interrupt = 11 - if type(channel) != int: - channel = pcan_channel_names[channel] + if not isinstance(channel, int): + channel = PCAN_CHANNEL_NAMES[channel] self.m_objPCANBasic = PCANBasic() self.m_PcanHandle = channel @@ -260,7 +209,7 @@ def __init__( if state is BusState.ACTIVE or state is BusState.PASSIVE: self.state = state else: - raise ArgumentError("BusState must be Active or Passive") + raise ValueError("BusState must be Active or Passive") if self.fd: f_clock_val = kwargs.get("f_clock", None) @@ -271,7 +220,7 @@ def __init__( fd_parameters_values = [f_clock] + [ "{}={}".format(key, kwargs.get(key, None)) - for key in pcan_fd_parameter_list + for key in PCAN_FD_PARAMETER_LIST if kwargs.get(key, None) is not None ] @@ -286,7 +235,7 @@ def __init__( ) if result != PCAN_ERROR_OK: - raise PcanError(self._get_formatted_error(result)) + raise PcanCanInitializationError(self._get_formatted_error(result)) result = self.m_objPCANBasic.SetValue( self.m_PcanHandle, PCAN_ALLOW_ERROR_FRAMES, PCAN_PARAMETER_ON @@ -294,7 +243,7 @@ def __init__( if result != PCAN_ERROR_OK: if platform.system() != "Darwin": - raise PcanError(self._get_formatted_error(result)) + raise PcanCanInitializationError(self._get_formatted_error(result)) else: # TODO Remove Filter when MACCan actually supports it: # https://github.com/mac-can/PCBUSB-Library/ @@ -308,7 +257,7 @@ def __init__( self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event ) if result != PCAN_ERROR_OK: - raise PcanError(self._get_formatted_error(result)) + raise PcanCanInitializationError(self._get_formatted_error(result)) super().__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs) @@ -407,7 +356,7 @@ def set_device_number(self, device_number): ) != PCAN_ERROR_OK ): - raise ValueError + raise ValueError() except ValueError: log.error("Invalid value '%s' for device number.", device_number) return False @@ -445,7 +394,7 @@ def _recv_internal(self, timeout): log.warning(self._get_formatted_error(result[0])) return None, False elif result[0] != PCAN_ERROR_OK: - raise PcanError(self._get_formatted_error(result[0])) + raise PcanCanOperationError(self._get_formatted_error(result[0])) theMsg = result[1] itsTimeStamp = result[2] @@ -554,7 +503,9 @@ def send(self, msg, timeout=None): result = self.m_objPCANBasic.Write(self.m_PcanHandle, CANMsg) if result != PCAN_ERROR_OK: - raise PcanError("Failed to send: " + self._get_formatted_error(result)) + raise PcanCanOperationError( + "Failed to send: " + self._get_formatted_error(result) + ) def flash(self, flash): """ @@ -646,15 +597,22 @@ def _detect_available_configs(): def status_string(self) -> Optional[str]: """ Query the PCAN bus status. - :return: The status in string. + + :return: The status description, if any was found. """ - if self.status() in PCAN_DICT_STATUS: + try: return PCAN_DICT_STATUS[self.status()] - else: + except KeyError: return None class PcanError(CanError): - """ - A generic error on a PCAN bus. - """ + """A generic error on a PCAN bus.""" + + +class PcanCanOperationError(CanOperationError, PcanError): + """Like :class:`can.exceptions.CanOperationError`, but specific to Pcan.""" + + +class PcanCanInitializationError(CanInitializationError, PcanError): + """Like :class:`can.exceptions.CanInitializationError`, but specific to Pcan.""" diff --git a/test/test_pcan.py b/test/test_pcan.py index 595d72b48..b9cecff26 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -42,9 +42,8 @@ def test_bus_creation(self) -> None: self.mock_pcan.InitializeFD.assert_not_called() def test_bus_creation_state_error(self) -> None: - with self.assertRaises(ctypes.ArgumentError): - self.bus = can.Bus(bustype="pcan", state=BusState.ERROR) - self.assertIsInstance(self.bus, PcanBus) + with self.assertRaises(ValueError): + can.Bus(bustype="pcan", state=BusState.ERROR) def test_bus_creation_fd(self) -> None: self.bus = can.Bus(bustype="pcan", fd=True) @@ -66,18 +65,18 @@ def test_bus_creation_fd(self) -> None: ] ) def test_get_formatted_error(self, name, status1, status2, expected_result: str): + with self.subTest(name): + self.bus = can.Bus(bustype="pcan") + self.mock_pcan.GetErrorText = Mock( + side_effect=[ + (status1, expected_result.encode("utf-8", errors="replace")), + (status2, expected_result.encode("utf-8", errors="replace")), + ] + ) - self.bus = can.Bus(bustype="pcan") - self.mock_pcan.GetErrorText = Mock( - side_effect=[ - (status1, expected_result.encode("utf-8", errors="replace")), - (status2, expected_result.encode("utf-8", errors="replace")), - ] - ) - - complete_text = self.bus._get_formatted_error(PCAN_ERROR_BUSHEAVY) + complete_text = self.bus._get_formatted_error(PCAN_ERROR_BUSHEAVY) - self.assertEqual(complete_text, expected_result) + self.assertEqual(complete_text, expected_result) def test_status(self) -> None: self.bus = can.Bus(bustype="pcan") @@ -88,43 +87,47 @@ def test_status(self) -> None: [("no_error", PCAN_ERROR_OK, True), ("error", PCAN_ERROR_UNKNOWN, False)] ) def test_status_is_ok(self, name, status, expected_result) -> None: - self.mock_pcan.GetStatus = Mock(return_value=status) - self.bus = can.Bus(bustype="pcan") - self.assertEqual(self.bus.status_is_ok(), expected_result) - self.mock_pcan.GetStatus.assert_called_once_with(PCAN_USBBUS1) + with self.subTest(name): + self.mock_pcan.GetStatus = Mock(return_value=status) + self.bus = can.Bus(bustype="pcan") + self.assertEqual(self.bus.status_is_ok(), expected_result) + self.mock_pcan.GetStatus.assert_called_once_with(PCAN_USBBUS1) @parameterized.expand( [("no_error", PCAN_ERROR_OK, True), ("error", PCAN_ERROR_UNKNOWN, False)] ) def test_reset(self, name, status, expected_result) -> None: - self.mock_pcan.Reset = Mock(return_value=status) - self.bus = can.Bus(bustype="pcan", fd=True) - self.assertEqual(self.bus.reset(), expected_result) - self.mock_pcan.Reset.assert_called_once_with(PCAN_USBBUS1) + with self.subTest(name): + self.mock_pcan.Reset = Mock(return_value=status) + self.bus = can.Bus(bustype="pcan", fd=True) + self.assertEqual(self.bus.reset(), expected_result) + self.mock_pcan.Reset.assert_called_once_with(PCAN_USBBUS1) @parameterized.expand( [("no_error", PCAN_ERROR_OK, 1), ("error", PCAN_ERROR_UNKNOWN, None)] ) def test_get_device_number(self, name, status, expected_result) -> None: - self.mock_pcan.GetValue = Mock(return_value=(status, 1)) - self.bus = can.Bus(bustype="pcan", fd=True) - self.assertEqual(self.bus.get_device_number(), expected_result) - self.mock_pcan.GetValue.assert_called_once_with( - PCAN_USBBUS1, PCAN_DEVICE_NUMBER - ) + with self.subTest(name): + self.mock_pcan.GetValue = Mock(return_value=(status, 1)) + self.bus = can.Bus(bustype="pcan", fd=True) + self.assertEqual(self.bus.get_device_number(), expected_result) + self.mock_pcan.GetValue.assert_called_once_with( + PCAN_USBBUS1, PCAN_DEVICE_NUMBER + ) @parameterized.expand( [("no_error", PCAN_ERROR_OK, True), ("error", PCAN_ERROR_UNKNOWN, False)] ) def test_set_device_number(self, name, status, expected_result) -> None: - self.bus = can.Bus(bustype="pcan") - self.mock_pcan.SetValue = Mock(return_value=status) - self.assertEqual(self.bus.set_device_number(3), expected_result) - # check last SetValue call - self.assertEqual( - self.mock_pcan.SetValue.call_args_list[-1][0], - (PCAN_USBBUS1, PCAN_DEVICE_NUMBER, 3), - ) + with self.subTest(name): + self.bus = can.Bus(bustype="pcan") + self.mock_pcan.SetValue = Mock(return_value=status) + self.assertEqual(self.bus.set_device_number(3), expected_result) + # check last SetValue call + self.assertEqual( + self.mock_pcan.SetValue.call_args_list[-1][0], + (PCAN_USBBUS1, PCAN_DEVICE_NUMBER, 3), + ) def test_recv(self): data = (ctypes.c_ubyte * 8)(*[x for x in range(8)]) @@ -219,32 +222,33 @@ def test_send_fd(self) -> None: ] ) def test_send_type(self, name, msg_type, expected_value) -> None: - ( - is_extended_id, - is_remote_frame, - is_error_frame, - is_fd, - bitrate_switch, - error_state_indicator, - ) = msg_type - - self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_OK) - - self.bus = can.Bus(bustype="pcan") - msg = can.Message( - arbitration_id=0xC0FFEF, - data=[1, 2, 3, 4, 5, 6, 7, 8], - is_extended_id=is_extended_id, - is_remote_frame=is_remote_frame, - is_error_frame=is_error_frame, - bitrate_switch=bitrate_switch, - error_state_indicator=error_state_indicator, - is_fd=is_fd, - ) - self.bus.send(msg) - # self.mock_m_objPCANBasic.Write.assert_called_once() - CANMsg = self.mock_pcan.Write.call_args_list[0][0][1] - self.assertEqual(CANMsg.MSGTYPE, expected_value.value) + with self.subTest(name): + ( + is_extended_id, + is_remote_frame, + is_error_frame, + is_fd, + bitrate_switch, + error_state_indicator, + ) = msg_type + + self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_OK) + + self.bus = can.Bus(bustype="pcan") + msg = can.Message( + arbitration_id=0xC0FFEF, + data=[1, 2, 3, 4, 5, 6, 7, 8], + is_extended_id=is_extended_id, + is_remote_frame=is_remote_frame, + is_error_frame=is_error_frame, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator, + is_fd=is_fd, + ) + self.bus.send(msg) + # self.mock_m_objPCANBasic.Write.assert_called_once() + CANMsg = self.mock_pcan.Write.call_args_list[0][0][1] + self.assertEqual(CANMsg.MSGTYPE, expected_value.value) def test_send_error(self) -> None: self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_BUSHEAVY) @@ -258,13 +262,14 @@ def test_send_error(self) -> None: @parameterized.expand([("on", True), ("off", False)]) def test_flash(self, name, flash) -> None: - self.bus = can.Bus(bustype="pcan") - self.bus.flash(flash) - call_list = self.mock_pcan.SetValue.call_args_list - last_call_args_list = call_list[-1][0] - self.assertEqual( - last_call_args_list, (PCAN_USBBUS1, PCAN_CHANNEL_IDENTIFYING, flash) - ) + with self.subTest(name): + self.bus = can.Bus(bustype="pcan") + self.bus.flash(flash) + call_list = self.mock_pcan.SetValue.call_args_list + last_call_args_list = call_list[-1][0] + self.assertEqual( + last_call_args_list, (PCAN_USBBUS1, PCAN_CHANNEL_IDENTIFYING, flash) + ) def test_shutdown(self) -> None: self.bus = can.Bus(bustype="pcan") @@ -278,14 +283,16 @@ def test_shutdown(self) -> None: ] ) def test_state(self, name, bus_state: BusState, expected_parameter) -> None: - self.bus = can.Bus(bustype="pcan") + with self.subTest(name): + self.bus = can.Bus(bustype="pcan") - self.bus.state = bus_state - call_list = self.mock_pcan.SetValue.call_args_list - last_call_args_list = call_list[-1][0] - self.assertEqual( - last_call_args_list, (PCAN_USBBUS1, PCAN_LISTEN_ONLY, expected_parameter) - ) + self.bus.state = bus_state + call_list = self.mock_pcan.SetValue.call_args_list + last_call_args_list = call_list[-1][0] + self.assertEqual( + last_call_args_list, + (PCAN_USBBUS1, PCAN_LISTEN_ONLY, expected_parameter), + ) def test_detect_available_configs(self) -> None: self.mock_pcan.GetValue = Mock( @@ -296,10 +303,11 @@ def test_detect_available_configs(self) -> None: @parameterized.expand([("valid", PCAN_ERROR_OK, "OK"), ("invalid", 0x00005, None)]) def test_status_string(self, name, status, expected_result) -> None: - self.bus = can.Bus(bustype="pcan") - self.mock_pcan.GetStatus = Mock(return_value=status) - self.assertEqual(self.bus.status_string(), expected_result) - self.mock_pcan.GetStatus.assert_called() + with self.subTest(name): + self.bus = can.Bus(bustype="pcan") + self.mock_pcan.GetStatus = Mock(return_value=status) + self.assertEqual(self.bus.status_string(), expected_result) + self.mock_pcan.GetStatus.assert_called() if __name__ == "__main__": From 8aa7166cbe5de5b5697cb0f313f15c0d7e65c973 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Tue, 7 Dec 2021 15:14:01 -0500 Subject: [PATCH 0767/1235] Truncating message data if over max frame length (#1181) Fixes #1177 --- can/interfaces/ics_neovi/neovi_bus.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index b4b7b4bb2..b8ae5eae9 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -417,6 +417,18 @@ def send(self, msg, timeout=0): """ if not ics.validate_hobject(self.dev): raise CanOperationError("bus not open") + + # Check for valid DLC to avoid passing extra long data to the driver + if msg.is_fd: + if msg.dlc > 64: + raise ValueError( + f"DLC was {msg.dlc} but it should be <= 64 for CAN FD frames" + ) + elif msg.dlc > 8: + raise ValueError( + f"DLC was {msg.dlc} but it should be <= 8 for normal CAN frames" + ) + message = ics.SpyMessage() flag0 = 0 @@ -434,8 +446,8 @@ def send(self, msg, timeout=0): flag3 |= ics.SPY_STATUS3_CANFD_ESI message.ArbIDOrHeader = msg.arbitration_id - msg_data = msg.data - message.NumberBytesData = len(msg_data) + msg_data = msg.data[: msg.dlc] + message.NumberBytesData = msg.dlc message.Data = tuple(msg_data[:8]) if msg.is_fd and len(msg_data) > 8: message.ExtraDataPtrEnabled = 1 From 278e7e80d4e6ae02cc26bbf5a6962812ca5b25a0 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 8 Dec 2021 01:36:30 +0100 Subject: [PATCH 0768/1235] Fix broken link in README (#1182) --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9000c5c77..ac5537f7e 100644 --- a/README.rst +++ b/README.rst @@ -18,7 +18,7 @@ python-can :alt: Downloads on PePy .. |downloads_monthly| image:: https://pepy.tech/badge/python-can/month - :target: https://pepy.tech/project/python-can/month + :target: https://pepy.tech/project/python-can :alt: Monthly downloads on PePy .. |formatter| image:: https://img.shields.io/badge/code%20style-black-000000.svg From 6c8be9e1a513f4607d9282b3cc8ad5da96b14816 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 8 Dec 2021 21:46:16 +0100 Subject: [PATCH 0769/1235] fix f-string --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index 29c97a39b..854d211a1 100644 --- a/can/message.py +++ b/can/message.py @@ -130,7 +130,7 @@ def __str__(self) -> str: field_strings.append(flag_string) - field_strings.append("DLC: {self.dlc:2d}") + field_strings.append(f"DLC: {self.dlc:2d}") data_strings = [] if self.data is not None: for index in range(0, min(self.dlc, len(self.data))): From 9af60f6e96ec6444f823fb1ce714876ca01a0327 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 12 Dec 2021 20:52:54 +0100 Subject: [PATCH 0770/1235] turn XL_BusTypes into IntFlag, update other enum values (#1184) --- can/interfaces/vector/xldefine.py | 45 +++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index d31c978ab..be40a15a9 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -24,7 +24,23 @@ class XL_AcceptanceFilter(IntEnum): class XL_BusCapabilities(IntFlag): XL_BUS_COMPATIBLE_CAN = 1 - XL_BUS_ACTIVE_CAP_CAN = 65536 + XL_BUS_ACTIVE_CAP_CAN = 1 << 16 + XL_BUS_COMPATIBLE_LIN = 2 + XL_BUS_ACTIVE_CAP_LIN = 2 << 16 + XL_BUS_COMPATIBLE_FLEXRAY = 4 + XL_BUS_ACTIVE_CAP_FLEXRAY = 4 << 16 + XL_BUS_COMPATIBLE_MOST = 16 + XL_BUS_ACTIVE_CAP_MOST = 16 << 16 + XL_BUS_COMPATIBLE_DAIO = 64 + XL_BUS_ACTIVE_CAP_DAIO = 64 << 16 + XL_BUS_COMPATIBLE_J1708 = 256 + XL_BUS_ACTIVE_CAP_J1708 = 256 << 16 + XL_BUS_COMPATIBLE_KLINE = 2048 + XL_BUS_ACTIVE_CAP_KLINE = 2048 << 16 + XL_BUS_COMPATIBLE_ETHERNET = 4096 + XL_BUS_ACTIVE_CAP_ETHERNET = 4096 << 16 + XL_BUS_COMPATIBLE_A429 = 8192 + XL_BUS_ACTIVE_CAP_A429 = 8192 << 16 class XL_BusStatus(IntEnum): @@ -34,7 +50,7 @@ class XL_BusStatus(IntEnum): XL_CHIPSTAT_ERROR_ACTIVE = 8 -class XL_BusTypes(IntEnum): +class XL_BusTypes(IntFlag): XL_BUS_TYPE_NONE = 0 # =0x00000000 XL_BUS_TYPE_CAN = 1 # =0x00000001 XL_BUS_TYPE_LIN = 2 # =0x00000002 @@ -80,7 +96,7 @@ class XL_CANFD_RX_EventTags(IntEnum): XL_CAN_EV_TAG_CHIP_STATE = 1033 -class XL_CANFD_RX_MessageFlags(IntEnum): +class XL_CANFD_RX_MessageFlags(IntFlag): XL_CAN_RXMSG_FLAG_NONE = 0 XL_CAN_RXMSG_FLAG_EDL = 1 XL_CAN_RXMSG_FLAG_BRS = 2 @@ -97,7 +113,7 @@ class XL_CANFD_TX_EventTags(IntEnum): XL_CAN_EV_TAG_TX_ERRFR = 1089 # =0x0441 -class XL_CANFD_TX_MessageFlags(IntEnum): +class XL_CANFD_TX_MessageFlags(IntFlag): XL_CAN_TXMSG_FLAG_NONE = 0 XL_CAN_TXMSG_FLAG_EDL = 1 XL_CAN_TXMSG_FLAG_BRS = 2 @@ -187,14 +203,32 @@ class XL_Status(IntEnum): XL_ERR_INVALID_PORT = 118 # =0x0076 XL_ERR_HW_NOT_READY = 120 # =0x0078 XL_ERR_CMD_TIMEOUT = 121 # =0x0079 + XL_ERR_CMD_HANDLING = 122 # = 0x007A XL_ERR_HW_NOT_PRESENT = 129 # =0x0081 XL_ERR_NOTIFY_ALREADY_ACTIVE = 131 # =0x0083 + XL_ERR_INVALID_TAG = 132 # = 0x0084 + XL_ERR_INVALID_RESERVED_FLD = 133 # = 0x0085 + XL_ERR_INVALID_SIZE = 134 # = 0x0086 + XL_ERR_INSUFFICIENT_BUFFER = 135 # = 0x0087 + XL_ERR_ERROR_CRC = 136 # = 0x0088 + XL_ERR_BAD_EXE_FORMAT = 137 # = 0x0089 + XL_ERR_NO_SYSTEM_RESOURCES = 138 # = 0x008A + XL_ERR_NOT_FOUND = 139 # = 0x008B + XL_ERR_INVALID_ADDRESS = 140 # = 0x008C + XL_ERR_REQ_NOT_ACCEP = 141 # = 0x008D + XL_ERR_INVALID_LEVEL = 142 # = 0x008E + XL_ERR_NO_DATA_DETECTED = 143 # = 0x008F + XL_ERR_INTERNAL_ERROR = 144 # = 0x0090 + XL_ERR_UNEXP_NET_ERR = 145 # = 0x0091 + XL_ERR_INVALID_USER_BUFFER = 146 # = 0x0092 + XL_ERR_INVALID_PORT_ACCESS_TYPE = 147 # = 0x0093 XL_ERR_NO_RESOURCES = 152 # =0x0098 XL_ERR_WRONG_CHIP_TYPE = 153 # =0x0099 XL_ERR_WRONG_COMMAND = 154 # =0x009A XL_ERR_INVALID_HANDLE = 155 # =0x009B XL_ERR_RESERVED_NOT_ZERO = 157 # =0x009D XL_ERR_INIT_ACCESS_MISSING = 158 # =0x009E + XL_ERR_WRONG_VERSION = 160 # = 0x00A0 XL_ERR_CANNOT_OPEN_DRIVER = 201 # =0x00C9 XL_ERR_WRONG_BUS_TYPE = 202 # =0x00CA XL_ERR_DLL_NOT_FOUND = 203 # =0x00CB @@ -276,8 +310,9 @@ class XL_HardwareType(IntEnum): XL_HWTYPE_VT6306 = 107 XL_HWTYPE_VT6104A = 108 XL_HWTYPE_VN5430 = 109 + XL_HWTYPE_VTSSERVICE = 110 XL_HWTYPE_VN1530 = 112 XL_HWTYPE_VN1531 = 113 XL_HWTYPE_VX1161A = 114 XL_HWTYPE_VX1161B = 115 - XL_MAX_HWTYPE = 119 + XL_MAX_HWTYPE = 120 From b6934944adb73e833cf5e4112d22489e8d6f4a30 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 13 Dec 2021 08:56:52 +1300 Subject: [PATCH 0771/1235] Update mergify rules Remove request for me to review all the things --- .mergify.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index c9b184d08..52b243cd3 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -18,16 +18,6 @@ pull_request_rules: queue: name: default - - name: Request Brian to review changes to the core API - conditions: - - "-files~=^can/interfaces/$" - - "-closed" - - "author!=hardbyte" - actions: - request_reviews: - users: - - hardbyte - - name: Delete head branch after merge conditions: - merged From c469ce9449025cd79199c43464899866050e7d82 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 13 Dec 2021 11:24:37 +0100 Subject: [PATCH 0772/1235] Implement check for minimum version of pcan library --- can/interfaces/pcan/pcan.py | 19 +++++++++++++++++++ setup.py | 1 + 2 files changed, 20 insertions(+) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 959874a64..b1fef17d1 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -9,6 +9,8 @@ from typing import Optional +from packaging import version + from ...message import Message from ...bus import BusABC, BusState from ...util import len2dlc, dlc2len @@ -19,6 +21,7 @@ PCAN_BITRATES, PCAN_FD_PARAMETER_LIST, PCAN_CHANNEL_NAMES, + PCAN_NONEBUS, PCAN_BAUD_500K, PCAN_TYPE_ISA, PCANBasic, @@ -26,6 +29,7 @@ PCAN_ALLOW_ERROR_FRAMES, PCAN_PARAMETER_ON, PCAN_RECEIVE_EVENT, + PCAN_API_VERSION, PCAN_DEVICE_NUMBER, PCAN_ERROR_QRCVEMPTY, PCAN_ERROR_BUSLIGHT, @@ -58,6 +62,8 @@ # Set up logging log = logging.getLogger("can.pcan") +MIN_PCAN_API_VERSION = version.parse("4.2.0") + try: # use the "uptime" library if available @@ -206,6 +212,19 @@ def __init__( self.m_objPCANBasic = PCANBasic() self.m_PcanHandle = channel + error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION) + if error != PCAN_ERROR_OK: + raise CanInitializationError( + F"Failed to read pcan basic api version" + ) + + apv = version.parse(value.decode('ascii')) + if apv < MIN_PCAN_API_VERSION: + raise CanInitializationError( + F"Minimum version of pcan api is {MIN_PCAN_API_VERSION}." + F" Installed version is {apv}. Consider upgrade of pcan basic package" + ) + if state is BusState.ACTIVE or state is BusState.PASSIVE: self.state = state else: diff --git a/setup.py b/setup.py index 31318ac06..fe0557d30 100644 --- a/setup.py +++ b/setup.py @@ -90,6 +90,7 @@ "typing_extensions>=3.10.0.0", 'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"', 'msgpack~=1.0.0;platform_system!="Windows"', + "packaging", ], extras_require=extras_require, ) From 56ad81c03205f53c104132ce0fbb504b3dac036b Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 13 Dec 2021 10:25:16 +0000 Subject: [PATCH 0773/1235] Format code with black --- can/interfaces/pcan/pcan.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index b1fef17d1..27e8e4875 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -106,7 +106,7 @@ def __init__( state=BusState.ACTIVE, bitrate=500000, *args, - **kwargs + **kwargs, ): """A PCAN USB interface to CAN. @@ -214,15 +214,13 @@ def __init__( error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION) if error != PCAN_ERROR_OK: - raise CanInitializationError( - F"Failed to read pcan basic api version" - ) + raise CanInitializationError(f"Failed to read pcan basic api version") - apv = version.parse(value.decode('ascii')) + apv = version.parse(value.decode("ascii")) if apv < MIN_PCAN_API_VERSION: raise CanInitializationError( - F"Minimum version of pcan api is {MIN_PCAN_API_VERSION}." - F" Installed version is {apv}. Consider upgrade of pcan basic package" + f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}." + f" Installed version is {apv}. Consider upgrade of pcan basic package" ) if state is BusState.ACTIVE or state is BusState.PASSIVE: From 6a3da0fe0a6fb60176e57ad44ed63679c5dbb123 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 13 Dec 2021 11:12:04 +0100 Subject: [PATCH 0774/1235] Fix spell error `touple` to `tuple` --- can/interfaces/pcan/basic.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 9fe7d1257..38ded44f6 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -814,7 +814,7 @@ def Read(self, Channel): """Reads a CAN message from the receive queue of a PCAN Channel Remarks: - The return value of this method is a 3-touple, where + The return value of this method is a 3-tuple, where the first value is the result (TPCANStatus) of the method. The order of the values are: [0]: A TPCANStatus error code @@ -843,7 +843,7 @@ def ReadFD(self, Channel): """Reads a CAN message from the receive queue of a FD capable PCAN Channel Remarks: - The return value of this method is a 3-touple, where + The return value of this method is a 3-tuple, where the first value is the result (TPCANStatus) of the method. The order of the values are: [0]: A TPCANStatus error code @@ -943,7 +943,7 @@ def GetValue(self, Channel, Parameter): of Hardware (PCAN Channel) being used. If a parameter is not available, a PCAN_ERROR_ILLPARAMTYPE error will be returned. - The return value of this method is a 2-touple, where + The return value of this method is a 2-tuple, where the first value is the result (TPCANStatus) of the method and the second one, the asked value @@ -1038,7 +1038,7 @@ def GetErrorText(self, Error, Language=0): Neutral (0x00), German (0x07), English (0x09), Spanish (0x0A), Italian (0x10) and French (0x0C) - The return value of this method is a 2-touple, where + The return value of this method is a 2-tuple, where the first value is the result (TPCANStatus) of the method and the second one, the error text @@ -1063,7 +1063,7 @@ def LookUpChannel(self, Parameters): Remarks: - The return value of this method is a 2-touple, where + The return value of this method is a 2-tuple, where the first value is the result (TPCANStatus) of the method and the second one a TPCANHandle value From f11a41f05704d5e7312608b847d9789ee4b188df Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Mon, 13 Dec 2021 14:52:33 -0500 Subject: [PATCH 0775/1235] Setting is_error_frame message property in neovi interface --- can/interfaces/ics_neovi/neovi_bus.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index b8ae5eae9..95a5bc4c5 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -364,6 +364,9 @@ def _ics_msg_to_message(self, ics_msg): is_remote_frame=bool( ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME ), + is_error_frame=bool( + ics_msg.StatusBitField2 & ics.SPY_STATUS2_ERROR_FRAME + ), error_state_indicator=bool( ics_msg.StatusBitField3 & ics.SPY_STATUS3_CANFD_ESI ), @@ -384,6 +387,9 @@ def _ics_msg_to_message(self, ics_msg): is_remote_frame=bool( ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME ), + is_error_frame=bool( + ics_msg.StatusBitField2 & ics.SPY_STATUS2_ERROR_FRAME + ), channel=ics_msg.NetworkID, ) From 73057d8895adfa2731ce875c1226ec66067b626e Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 14 Dec 2021 08:37:39 +0100 Subject: [PATCH 0776/1235] Specific Exceptions: Adapting nixnet interface (#1154) Part of #1046 --- can/interfaces/nixnet.py | 52 +++++++++++++++------------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index 4e3caa2ad..f1def62ad 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -13,7 +13,9 @@ import time import struct -from can import CanError, BusABC, Message +from can import BusABC, Message +from ..exceptions import CanInitializationError, CanOperationError + logger = logging.getLogger(__name__) @@ -28,18 +30,15 @@ database, XnetError, ) - except ImportError: - logger.error("Error, NIXNET python module cannot be loaded.") - raise + except ImportError as error: + raise ImportError("NIXNET python module cannot be loaded") from error else: - logger.error("NI-XNET interface is only available on Windows systems") - raise NotImplementedError("NiXNET is not supported on not Win32 platforms") + raise NotImplementedError("NiXNET only supported on Win32 platforms") class NiXNETcanBus(BusABC): """ The CAN Bus implemented for the NI-XNET interface. - """ def __init__( @@ -52,7 +51,7 @@ def __init__( brs=False, can_termination=False, log_errors=True, - **kwargs + **kwargs, ): """ :param str channel: @@ -69,9 +68,8 @@ def __init__( ``is_error_frame`` set to True and ``arbitration_id`` will identify the error (default True) - :raises can.interfaces.nixnet.NiXNETError: + :raises can.exceptions.CanInitializationError: If starting communication fails - """ self._rx_queue = [] self.channel = channel @@ -120,8 +118,10 @@ def __init__( self.__session_send.start() self.__session_receive.start() - except errors.XnetError as err: - raise NiXNETError(function="__init__", error_message=err.args[0]) from None + except errors.XnetError as error: + raise CanInitializationError( + f"{error.args[0]} ({error.error_type})", error.error_code + ) from None self._is_filtered = False super(NiXNETcanBus, self).__init__( @@ -129,7 +129,7 @@ def __init__( can_filters=can_filters, bitrate=bitrate, log_errors=log_errors, - **kwargs + **kwargs, ) def _recv_internal(self, timeout): @@ -160,8 +160,7 @@ def _recv_internal(self, timeout): ) return msg, self._filters is None - except Exception as e: - # print('Error: ', e) + except Exception: return None, self._filters is None def send(self, msg, timeout=None): @@ -174,7 +173,7 @@ def send(self, msg, timeout=None): :param float timeout: Max time to wait for the device to be ready in seconds, None if time is infinite - :raises can.interfaces.nixnet.NiXNETError: + :raises can.exceptions.CanOperationError: If writing to transmit buffer fails. It does not wait for message to be ACKed currently. """ @@ -201,8 +200,10 @@ def send(self, msg, timeout=None): try: self.__session_send.frames.write([can_frame], timeout) - except errors.XnetError as err: - raise NiXNETError(function="send", error_message=err.args[0]) from None + except errors.XnetError as error: + raise CanOperationError( + f"{error.args[0]} ({error.error_type})", error.error_code + ) from None def reset(self): """ @@ -254,18 +255,3 @@ def _detect_available_configs(): logger.debug("An error occured while searching for configs: %s", str(error)) return configs - - -# To-Do review error management, I don't like this implementation -class NiXNETError(CanError): - """Error from NI-XNET driver.""" - - def __init__(self, function="", error_message=""): - super(NiXNETError, self).__init__() - #: Function that failed - self.function = function - #: Arguments passed to function - self.error_message = error_message - - def __str__(self): - return "Function %s failed:\n%s" % (self.function, self.error_message) From e3928deb3c58e1ef6e13cc348c7ed2bed4a62141 Mon Sep 17 00:00:00 2001 From: Felix Nieuwenhuizen Date: Thu, 16 Dec 2021 17:22:35 +0100 Subject: [PATCH 0777/1235] incorporate review feedback --- doc/interfaces/etas.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/interfaces/etas.rst b/doc/interfaces/etas.rst index daee27356..2df1e7557 100644 --- a/doc/interfaces/etas.rst +++ b/doc/interfaces/etas.rst @@ -4,7 +4,7 @@ ETAS This interface adds support for CAN interfaces by `ETAS`_. The ETAS BOA_ (Basic Open API) is used. Install the "ETAS ECU and Bus Interfaces – Distribution Package". -Only Windows is supported. +Only Windows is supported by this interface. The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 USB modules. To use these under Linux, please refer to :ref:socketcan. Bus --- From 5ff0f157156fef9bace295af06b4506f2a5ed7ba Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 16 Dec 2021 19:15:22 +0100 Subject: [PATCH 0778/1235] Update doc/interfaces/etas.rst --- doc/interfaces/etas.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/interfaces/etas.rst b/doc/interfaces/etas.rst index 2df1e7557..cc3cbdea4 100644 --- a/doc/interfaces/etas.rst +++ b/doc/interfaces/etas.rst @@ -4,7 +4,9 @@ ETAS This interface adds support for CAN interfaces by `ETAS`_. The ETAS BOA_ (Basic Open API) is used. Install the "ETAS ECU and Bus Interfaces – Distribution Package". -Only Windows is supported by this interface. The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 USB modules. To use these under Linux, please refer to :ref:socketcan. +Only Windows is supported by this interface. +The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 USB modules. +To use these under Linux, please refer to :ref:`socketcan`. Bus --- From 0f80d2548cfd6aaab124966f87b2424346d12715 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 09:50:42 +0100 Subject: [PATCH 0779/1235] Mover read of api version to separate function --- can/interfaces/pcan/pcan.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 27e8e4875..9380e0663 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -212,11 +212,7 @@ def __init__( self.m_objPCANBasic = PCANBasic() self.m_PcanHandle = channel - error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION) - if error != PCAN_ERROR_OK: - raise CanInitializationError(f"Failed to read pcan basic api version") - - apv = version.parse(value.decode("ascii")) + apv = self.get_api_version() if apv < MIN_PCAN_API_VERSION: raise CanInitializationError( f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}." @@ -321,6 +317,13 @@ def bits(n): return complete_text + def get_api_version(self): + error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION) + if error != PCAN_ERROR_OK: + raise CanInitializationError(f"Failed to read pcan basic api version") + + return version.parse(value.decode("ascii")) + def status(self): """ Query the PCAN bus status. From ae260cdf3a1776dd863ad096bcde619da91d9deb Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 09:52:30 +0100 Subject: [PATCH 0780/1235] Move check of api version to separate method --- can/interfaces/pcan/pcan.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 9380e0663..5ffcb94bc 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -212,12 +212,7 @@ def __init__( self.m_objPCANBasic = PCANBasic() self.m_PcanHandle = channel - apv = self.get_api_version() - if apv < MIN_PCAN_API_VERSION: - raise CanInitializationError( - f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}." - f" Installed version is {apv}. Consider upgrade of pcan basic package" - ) + self.check_api_version() if state is BusState.ACTIVE or state is BusState.PASSIVE: self.state = state @@ -324,6 +319,14 @@ def get_api_version(self): return version.parse(value.decode("ascii")) + def check_api_version(self): + apv = self.get_api_version() + if apv < MIN_PCAN_API_VERSION: + raise CanInitializationError( + f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}." + f" Installed version is {apv}. Consider upgrade of pcan basic package" + ) + def status(self): """ Query the PCAN bus status. From fe3a2c43a9a23394baf4648837cb5cf9b41125a9 Mon Sep 17 00:00:00 2001 From: Simon Tegelid Date: Tue, 14 Dec 2021 11:27:33 +0100 Subject: [PATCH 0781/1235] Add can.logconvert to script docs --- doc/scripts.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/scripts.rst b/doc/scripts.rst index 2b8607667..ace8c1e39 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -53,3 +53,8 @@ The full usage page can be seen below: .. command-output:: python -m can.viewer -h + +can.logconvert +---------- + +.. command-output:: python -m can.logconvert -h From bc66b5758d238794a29ff08073305689635a8eec Mon Sep 17 00:00:00 2001 From: Simon Tegelid Date: Fri, 17 Dec 2021 12:40:49 +0100 Subject: [PATCH 0782/1235] Add preserve timestamps to virtual Add an option to virtual interfaces to preserve message timestamps on transmissions. This is useful in test setups fed by log files. --- can/interfaces/virtual.py | 4 +++- doc/interfaces/virtual.rst | 29 +++++++++++++++++++++++- test/test_interface_virtual.py | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 test/test_interface_virtual.py diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index a903435ac..ffd5b0241 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -59,6 +59,7 @@ def __init__( channel: Any = None, receive_own_messages: bool = False, rx_queue_size: int = 0, + preserve_timestamps: bool = False, **kwargs: Any, ) -> None: super().__init__( @@ -69,6 +70,7 @@ def __init__( self.channel_id = channel self.channel_info = f"Virtual bus channel {self.channel_id}" self.receive_own_messages = receive_own_messages + self.preserve_timestamps = preserve_timestamps self._open = True with channels_lock: @@ -103,7 +105,7 @@ def _recv_internal( def send(self, msg: Message, timeout: Optional[float] = None) -> None: self._check_if_open() - timestamp = time.time() + timestamp = msg.timestamp if self.preserve_timestamps else time.time() # Add message to all listening on this channel all_sent = True for bus_queue in self.channel: diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst index b3fa7b38e..9258c9bbd 100644 --- a/doc/interfaces/virtual.rst +++ b/doc/interfaces/virtual.rst @@ -85,7 +85,7 @@ Example ------- .. code-block:: python - + import can bus1 = can.interface.Bus('test', bustype='virtual') @@ -100,6 +100,33 @@ Example assert msg1.data == msg2.data assert msg1.timestamp != msg2.timestamp +.. code-block:: python + + import can + + bus1 = can.interface.Bus('test', bustype='virtual', preserve_timestamps=True) + bus2 = can.interface.Bus('test', bustype='virtual') + + msg1 = can.Message(timestamp=1639740470.051948, arbitration_id=0xabcde, data=[1,2,3]) + + # Messages sent on bus1 will have their timestamps preserved when received + # on bus2 + bus1.send(msg1) + msg2 = bus2.recv() + + assert msg1.arbitration_id == msg2.arbitration_id + assert msg1.data == msg2.data + assert msg1.timestamp == msg2.timestamp + + # Messages sent on bus2 will not have their timestamps preserved when + # received on bus1 + bus2.send(msg1) + msg3 = bus1.recv() + + assert msg1.arbitration_id == msg3.arbitration_id + assert msg1.data == msg3.data + assert msg1.timestamp != msg3.timestamp + Bus Class Documentation ----------------------- diff --git a/test/test_interface_virtual.py b/test/test_interface_virtual.py new file mode 100644 index 000000000..009722779 --- /dev/null +++ b/test/test_interface_virtual.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module tests :meth:`can.interface.virtual`. +""" + +import unittest + +from can import Bus, Message + +EXAMPLE_MSG1 = Message(timestamp=1639739471.5565314, arbitration_id=0x481, data=b"\x01") + + +class TestMessageFiltering(unittest.TestCase): + def setUp(self): + self.node1 = Bus("test", bustype="virtual", preserve_timestamps=True) + self.node2 = Bus("test", bustype="virtual") + + def tearDown(self): + self.node1.shutdown() + self.node2.shutdown() + + def test_sendmsg(self): + self.node2.send(EXAMPLE_MSG1) + r = self.node1.recv(0.1) + assert r.timestamp != EXAMPLE_MSG1.timestamp + assert r.arbitration_id == EXAMPLE_MSG1.arbitration_id + assert r.data == EXAMPLE_MSG1.data + + def test_sendmsg_preserve_timestamp(self): + self.node1.send(EXAMPLE_MSG1) + r = self.node2.recv(0.1) + assert r.timestamp == EXAMPLE_MSG1.timestamp + assert r.arbitration_id == EXAMPLE_MSG1.arbitration_id + assert r.data == EXAMPLE_MSG1.data + + +if __name__ == "__main__": + unittest.main() From 4addd5f094c3760048d271b2bdc3c25ba8581e4a Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 09:57:10 +0100 Subject: [PATCH 0783/1235] Add mock for pcan tests to suppress check of api version --- test/test_pcan.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_pcan.py b/test/test_pcan.py index b9cecff26..7d93781c0 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -26,6 +26,7 @@ def setUp(self) -> None: self.mock_pcan.Initialize.return_value = PCAN_ERROR_OK self.mock_pcan.InitializeFD = Mock(return_value=PCAN_ERROR_OK) self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_OK) + self.mock_pcan.GetValue = self._mockGetValue self.bus = None @@ -34,6 +35,17 @@ def tearDown(self) -> None: self.bus.shutdown() self.bus = None + def _mockGetValue(self, Channel, Parameter): + """ + This method is used as mock for GetValue method of PCANBasic object. + Only a subset of parameters are supported. + """ + if Parameter == PCAN_API_VERSION: + return PCAN_ERROR_OK, "4.2".encode("ascii") + raise NotImplementedError( + f"No mock return value specified for parameter {Parameter}" + ) + def test_bus_creation(self) -> None: self.bus = can.Bus(bustype="pcan") self.assertIsInstance(self.bus, PcanBus) From 16e35f1fc291f80a9932c52f99f706d6de873fa2 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 21:11:14 +0100 Subject: [PATCH 0784/1235] Change mock/init order to make tests run --- test/test_pcan.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_pcan.py b/test/test_pcan.py index 7d93781c0..74d3e4d6c 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -120,8 +120,11 @@ def test_reset(self, name, status, expected_result) -> None: ) def test_get_device_number(self, name, status, expected_result) -> None: with self.subTest(name): - self.mock_pcan.GetValue = Mock(return_value=(status, 1)) self.bus = can.Bus(bustype="pcan", fd=True) + # Mock GetValue after creation of bus to use first mock of + # GetValue in constructor + self.mock_pcan.GetValue = Mock(return_value=(status, 1)) + self.assertEqual(self.bus.get_device_number(), expected_result) self.mock_pcan.GetValue.assert_called_once_with( PCAN_USBBUS1, PCAN_DEVICE_NUMBER From 1dcc736f8f61164ca68c6ce098b90a4b4aae3bd2 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 21:17:45 +0100 Subject: [PATCH 0785/1235] Put simulated version to object attribute --- test/test_pcan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_pcan.py b/test/test_pcan.py index 74d3e4d6c..452161c07 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -27,6 +27,7 @@ def setUp(self) -> None: self.mock_pcan.InitializeFD = Mock(return_value=PCAN_ERROR_OK) self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_OK) self.mock_pcan.GetValue = self._mockGetValue + self.PCAN_API_VERSION_SIM = "4.2" self.bus = None @@ -41,7 +42,7 @@ def _mockGetValue(self, Channel, Parameter): Only a subset of parameters are supported. """ if Parameter == PCAN_API_VERSION: - return PCAN_ERROR_OK, "4.2".encode("ascii") + return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii") raise NotImplementedError( f"No mock return value specified for parameter {Parameter}" ) From 5a43bb8dc958d25b70309ce7ac466190a7d75690 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 21:22:10 +0100 Subject: [PATCH 0786/1235] Add test for wrong api version --- test/test_pcan.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_pcan.py b/test/test_pcan.py index 452161c07..2d1a67486 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -12,6 +12,7 @@ import can from can.bus import BusState +from can.exceptions import CanInitializationError from can.interfaces.pcan.basic import * from can.interfaces.pcan import PcanBus, PcanError @@ -65,6 +66,11 @@ def test_bus_creation_fd(self) -> None: self.mock_pcan.Initialize.assert_not_called() self.mock_pcan.InitializeFD.assert_called_once() + def test_api_version_error(self) -> None: + self.PCAN_API_VERSION_SIM = "1.0" + with self.assertRaises(CanInitializationError): + self.bus = can.Bus(bustype="pcan") + @parameterized.expand( [ ("no_error", PCAN_ERROR_OK, PCAN_ERROR_OK, "some ok text 1"), From 0ee529cb79a8729c3992c652ccc0315eb83f874f Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 21:26:53 +0100 Subject: [PATCH 0787/1235] Add testcase test_api_version_read_fail --- test/test_pcan.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_pcan.py b/test/test_pcan.py index 2d1a67486..459320de8 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -71,6 +71,11 @@ def test_api_version_error(self) -> None: with self.assertRaises(CanInitializationError): self.bus = can.Bus(bustype="pcan") + def test_api_version_read_fail(self) -> None: + self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_ILLOPERATION, None)) + with self.assertRaises(CanInitializationError): + self.bus = can.Bus(bustype="pcan") + @parameterized.expand( [ ("no_error", PCAN_ERROR_OK, PCAN_ERROR_OK, "some ok text 1"), From 5eaa9d147fa2b68c7a60fc497c6a18a2c644a1d5 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 00:54:55 +0100 Subject: [PATCH 0788/1235] Add unittests for logger execution (#1196) --- test/test_logger.py | 109 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 test/test_logger.py diff --git a/test/test_logger.py b/test/test_logger.py new file mode 100644 index 000000000..a046b919f --- /dev/null +++ b/test/test_logger.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module tests the functions inside of logger.py +""" + +import unittest +from unittest import mock +from unittest.mock import Mock +import sys +import can +import can.logger + +from .config import * + + +class TestLoggerScriptModule(unittest.TestCase): + def setUp(self) -> None: + # Patch VirtualBus object + patcher_virtual_bus = mock.patch("can.interfaces.virtual.VirtualBus", spec=True) + self.MockVirtualBus = patcher_virtual_bus.start() + self.addCleanup(patcher_virtual_bus.stop) + self.mock_virtual_bus = self.MockVirtualBus.return_value + self.mock_virtual_bus.shutdown = Mock() + + # Patch Logger object + patcher_logger = mock.patch("can.logger.Logger", spec=True) + self.MockLogger = patcher_logger.start() + self.addCleanup(patcher_logger.stop) + self.mock_logger = self.MockLogger.return_value + self.mock_logger.stop = Mock() + + self.MockLoggerUse = self.MockLogger + self.loggerToUse = self.mock_logger + + # Patch SizedRotatingLogger object + patcher_logger_sized = mock.patch("can.logger.SizedRotatingLogger", spec=True) + self.MockLoggerSized = patcher_logger_sized.start() + self.addCleanup(patcher_logger_sized.stop) + self.mock_logger_sized = self.MockLoggerSized.return_value + self.mock_logger_sized.stop = Mock() + + self.testmsg = can.Message( + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True + ) + + self.baseargs = [sys.argv[0], "-i", "virtual"] + + def assertSuccessfullCleanup(self): + self.MockVirtualBus.assert_called_once() + self.mock_virtual_bus.shutdown.assert_called_once() + + self.MockLoggerUse.assert_called_once() + self.loggerToUse.stop.assert_called_once() + + def test_log_virtual(self): + self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt]) + + sys.argv = self.baseargs + can.logger.main() + self.assertSuccessfullCleanup() + self.mock_logger.assert_called_once() + + def test_log_virtual_active(self): + self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt]) + + sys.argv = self.baseargs + ["--active"] + can.logger.main() + self.assertSuccessfullCleanup() + self.mock_logger.assert_called_once() + self.assertEqual(self.mock_virtual_bus.state, can.BusState.ACTIVE) + + def test_log_virtual_passive(self): + self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt]) + + sys.argv = self.baseargs + ["--passive"] + can.logger.main() + self.assertSuccessfullCleanup() + self.mock_logger.assert_called_once() + self.assertEqual(self.mock_virtual_bus.state, can.BusState.PASSIVE) + + def test_log_virtual_with_config(self): + self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt]) + + sys.argv = self.baseargs + [ + "--bitrate", + "250000", + "--fd", + "--data_bitrate", + "2000000", + ] + can.logger.main() + self.assertSuccessfullCleanup() + self.mock_logger.assert_called_once() + + def test_log_virtual_sizedlogger(self): + self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt]) + self.MockLoggerUse = self.MockLoggerSized + self.loggerToUse = self.mock_logger_sized + + sys.argv = self.baseargs + ["--file_size", "1000000"] + can.logger.main() + self.assertSuccessfullCleanup() + self.mock_logger_sized.assert_called_once() + + +if __name__ == "__main__": + unittest.main() From f1c5c0bd672c8a2195b2027323593afb1694438d Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 07:43:33 +0100 Subject: [PATCH 0789/1235] Log warning instead of raise error when pcan api is to old --- can/interfaces/pcan/pcan.py | 2 +- test/test_pcan.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 5ffcb94bc..b8a0ebee5 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -322,7 +322,7 @@ def get_api_version(self): def check_api_version(self): apv = self.get_api_version() if apv < MIN_PCAN_API_VERSION: - raise CanInitializationError( + log.warning( f"Minimum version of pcan api is {MIN_PCAN_API_VERSION}." f" Installed version is {apv}. Consider upgrade of pcan basic package" ) diff --git a/test/test_pcan.py b/test/test_pcan.py index 459320de8..b9f318c6f 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -66,9 +66,9 @@ def test_bus_creation_fd(self) -> None: self.mock_pcan.Initialize.assert_not_called() self.mock_pcan.InitializeFD.assert_called_once() - def test_api_version_error(self) -> None: + def test_api_version_low(self) -> None: self.PCAN_API_VERSION_SIM = "1.0" - with self.assertRaises(CanInitializationError): + with self.assertLogs('can.pcan', level='WARNING') as cm: self.bus = can.Bus(bustype="pcan") def test_api_version_read_fail(self) -> None: From 8f0136a57bcf26db57cf31388d533c9189394c4a Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 07:49:25 +0100 Subject: [PATCH 0790/1235] Add test for log output --- test/test_pcan.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/test_pcan.py b/test/test_pcan.py index b9f318c6f..e8c960f6b 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -68,8 +68,16 @@ def test_bus_creation_fd(self) -> None: def test_api_version_low(self) -> None: self.PCAN_API_VERSION_SIM = "1.0" - with self.assertLogs('can.pcan', level='WARNING') as cm: + with self.assertLogs("can.pcan", level="WARNING") as cm: self.bus = can.Bus(bustype="pcan") + found_version_warning = False + for i in cm.output: + if "version" in i and "pcan" in i: + found_version_warning = True + self.assertTrue( + found_version_warning, + f"No warning was logged for incompatible api version {cm.output}", + ) def test_api_version_read_fail(self) -> None: self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_ILLOPERATION, None)) From 89deff5434b8dab404e2d4a4dd320712672b56b6 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Mon, 20 Dec 2021 19:03:00 +0100 Subject: [PATCH 0791/1235] Fix syntax highlighting in bus.rst --- doc/bus.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/bus.rst b/doc/bus.rst index 1fbe771cb..bbe52cbd6 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -80,7 +80,9 @@ This thread safe version of the :class:`~can.BusABC` class can be used by multip Sending and receiving is locked separately to avoid unnecessary delays. Conflicting calls are executed by blocking until the bus is accessible. -It can be used exactly like the normal :class:`~can.BusABC`:: +It can be used exactly like the normal :class:`~can.BusABC`: + +.. code-block:: python # 'socketcan' is only an example interface, it works with all the others too my_bus = can.ThreadSafeBus(interface='socketcan', channel='vcan0') From 0362acbcae9dcdcb97474cc97788fd1983184b84 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 19:05:51 +0100 Subject: [PATCH 0792/1235] Add test case for player.py --- test/test_player.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 test/test_player.py diff --git a/test/test_player.py b/test/test_player.py new file mode 100755 index 000000000..62fdd0271 --- /dev/null +++ b/test/test_player.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +""" +This module tests the functions inside of player.py +""" + +import unittest +from unittest import mock +from unittest.mock import Mock +import os +import sys +import can +import can.player + +from .config import * + + +class TestPlayerScriptModule(unittest.TestCase): + def setUp(self) -> None: + # Patch VirtualBus object + patcher_virtual_bus = mock.patch("can.interfaces.virtual.VirtualBus", spec=True) + self.MockVirtualBus = patcher_virtual_bus.start() + self.addCleanup(patcher_virtual_bus.stop) + self.mock_virtual_bus = self.MockVirtualBus.return_value + self.mock_virtual_bus.shutdown = Mock() + + self.baseargs = [sys.argv[0], "-i", "virtual"] + self.logfile = os.path.join(os.path.dirname(__file__), "data", "test_CanMessage.asc") + + def assertSuccessfullCleanup(self): + self.MockVirtualBus.assert_called_once() + + def test_play_virtual(self): + sys.argv = self.baseargs + [self.logfile] + can.player.main() + self.assertSuccessfullCleanup() + + +if __name__ == "__main__": + unittest.main() From ee069470224686764445cf8e8cd8e62a960a375e Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 18:06:52 +0000 Subject: [PATCH 0793/1235] Format code with black --- test/test_player.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_player.py b/test/test_player.py index 62fdd0271..1af48e429 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -26,7 +26,9 @@ def setUp(self) -> None: self.mock_virtual_bus.shutdown = Mock() self.baseargs = [sys.argv[0], "-i", "virtual"] - self.logfile = os.path.join(os.path.dirname(__file__), "data", "test_CanMessage.asc") + self.logfile = os.path.join( + os.path.dirname(__file__), "data", "test_CanMessage.asc" + ) def assertSuccessfullCleanup(self): self.MockVirtualBus.assert_called_once() From 5c72735a0d42fd10a159fec1f3cc1fb3a25da283 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 19:29:39 +0100 Subject: [PATCH 0794/1235] Add mock for sleep function to fasten up testing --- test/test_player.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_player.py b/test/test_player.py index 1af48e429..70a507ebe 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -25,6 +25,11 @@ def setUp(self) -> None: self.mock_virtual_bus = self.MockVirtualBus.return_value self.mock_virtual_bus.shutdown = Mock() + # Patch time sleep object + patcher_sleep = mock.patch("can.io.player.sleep", spec=True) + self.MockSleep = patcher_sleep.start() + self.addCleanup(patcher_sleep.stop) + self.baseargs = [sys.argv[0], "-i", "virtual"] self.logfile = os.path.join( os.path.dirname(__file__), "data", "test_CanMessage.asc" From fec26fbdd6bf8008ea67ac12029ed6ec64b558ae Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 19:30:33 +0100 Subject: [PATCH 0795/1235] Add test for verbose mode --- test/test_player.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_player.py b/test/test_player.py index 70a507ebe..aeb63764b 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -43,6 +43,11 @@ def test_play_virtual(self): can.player.main() self.assertSuccessfullCleanup() + def test_play_virtual_verbose(self): + sys.argv = self.baseargs + ["-v", self.logfile] + can.player.main() + self.assertSuccessfullCleanup() + if __name__ == "__main__": unittest.main() From fefdd53972a5cbf2697b99df7211522b1b51e6ae Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 19:42:18 +0100 Subject: [PATCH 0796/1235] Add call count assert --- test/test_player.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_player.py b/test/test_player.py index aeb63764b..8fa1e45ef 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -41,11 +41,13 @@ def assertSuccessfullCleanup(self): def test_play_virtual(self): sys.argv = self.baseargs + [self.logfile] can.player.main() + self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() def test_play_virtual_verbose(self): sys.argv = self.baseargs + ["-v", self.logfile] can.player.main() + self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() From 0907785c9025e49bcff09f77a2c7e9ba70475309 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 19:42:42 +0100 Subject: [PATCH 0797/1235] Add test for Keyboard interrupt during execution --- test/test_player.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_player.py b/test/test_player.py index 8fa1e45ef..12a20ccd8 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -50,6 +50,14 @@ def test_play_virtual_verbose(self): self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() + def test_play_virtual_exit(self): + self.MockSleep.side_effect = KeyboardInterrupt + + sys.argv = self.baseargs + [self.logfile] + can.player.main() + self.assertEqual(self.MockSleep.call_count, 1) + self.assertSuccessfullCleanup() + if __name__ == "__main__": unittest.main() From 4721134c40fbd33465f923ee2beae37b79ee3a34 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 20 Dec 2021 19:53:43 +0100 Subject: [PATCH 0798/1235] Add todos --- test/test_player.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_player.py b/test/test_player.py index 12a20ccd8..625c2842e 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -41,12 +41,15 @@ def assertSuccessfullCleanup(self): def test_play_virtual(self): sys.argv = self.baseargs + [self.logfile] can.player.main() + # TODO: add test two messages sent self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() def test_play_virtual_verbose(self): sys.argv = self.baseargs + ["-v", self.logfile] can.player.main() + # TODO: add test two messages sent + # TODO: add test message was printed self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() @@ -55,9 +58,15 @@ def test_play_virtual_exit(self): sys.argv = self.baseargs + [self.logfile] can.player.main() + # TODO: add test one message sent self.assertEqual(self.MockSleep.call_count, 1) self.assertSuccessfullCleanup() + def test_play_error_frame(self): + # TODO: implement + sys.argv = self.baseargs + ["--error-frames", self.logfile] + can.player.main() + if __name__ == "__main__": unittest.main() From 1d11a04d3fbb4eb9529d5180ac6fe28a6cc6931c Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 26 Dec 2021 14:28:24 +0100 Subject: [PATCH 0799/1235] Implement check for successfull cleanup and send message call count --- test/test_player.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_player.py b/test/test_player.py index 625c2842e..eb8697a36 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -23,7 +23,7 @@ def setUp(self) -> None: self.MockVirtualBus = patcher_virtual_bus.start() self.addCleanup(patcher_virtual_bus.stop) self.mock_virtual_bus = self.MockVirtualBus.return_value - self.mock_virtual_bus.shutdown = Mock() + self.mock_virtual_bus.__enter__ = Mock(return_value=self.mock_virtual_bus) # Patch time sleep object patcher_sleep = mock.patch("can.io.player.sleep", spec=True) @@ -37,11 +37,12 @@ def setUp(self) -> None: def assertSuccessfullCleanup(self): self.MockVirtualBus.assert_called_once() + self.mock_virtual_bus.__exit__.assert_called_once() def test_play_virtual(self): sys.argv = self.baseargs + [self.logfile] can.player.main() - # TODO: add test two messages sent + self.assertEqual(self.mock_virtual_bus.send.call_count, 2) self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() From bae496efa8f5de98f8d4c1f991a87636515791c0 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 26 Dec 2021 14:31:37 +0100 Subject: [PATCH 0800/1235] Add assumptions for test cases --- test/test_player.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_player.py b/test/test_player.py index eb8697a36..214f017c9 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -49,18 +49,18 @@ def test_play_virtual(self): def test_play_virtual_verbose(self): sys.argv = self.baseargs + ["-v", self.logfile] can.player.main() - # TODO: add test two messages sent # TODO: add test message was printed + self.assertEqual(self.mock_virtual_bus.send.call_count, 2) self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() def test_play_virtual_exit(self): - self.MockSleep.side_effect = KeyboardInterrupt + self.MockSleep.side_effect = [None, KeyboardInterrupt] sys.argv = self.baseargs + [self.logfile] can.player.main() - # TODO: add test one message sent - self.assertEqual(self.MockSleep.call_count, 1) + self.assertEqual(self.mock_virtual_bus.send.call_count, 1) + self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() def test_play_error_frame(self): From c41845fb96c6611b99364c3480ed991bbe3889b8 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 26 Dec 2021 14:42:28 +0100 Subject: [PATCH 0801/1235] Add check for output on screen --- test/test_player.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/test_player.py b/test/test_player.py index 214f017c9..65fa5b6b9 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -10,6 +10,7 @@ from unittest.mock import Mock import os import sys +import io import can import can.player @@ -48,8 +49,10 @@ def test_play_virtual(self): def test_play_virtual_verbose(self): sys.argv = self.baseargs + ["-v", self.logfile] - can.player.main() - # TODO: add test message was printed + with unittest.mock.patch('sys.stdout', new_callable=io.StringIO) as mock_stdout: + can.player.main() + self.assertIn('09 08 07 06 05 04 03 02', mock_stdout.getvalue()) + self.assertIn('05 0c 00 00 00 00 00 00', mock_stdout.getvalue()) self.assertEqual(self.mock_virtual_bus.send.call_count, 2) self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() From f6816b90f7f493e49bec7a97fea7a8c503e6308c Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 26 Dec 2021 14:56:10 +0100 Subject: [PATCH 0802/1235] Implement test for replay with error frames --- test/data/logfile_errorframes.asc | 21 +++++++++++++++++++++ test/test_player.py | 19 +++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 test/data/logfile_errorframes.asc diff --git a/test/data/logfile_errorframes.asc b/test/data/logfile_errorframes.asc new file mode 100644 index 000000000..bcb5584a7 --- /dev/null +++ b/test/data/logfile_errorframes.asc @@ -0,0 +1,21 @@ +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0 + 0.015991 CAN 2 Status:chip status error active + 2.501000 1 ErrorFrame + 2.501010 1 ErrorFrame ECC: 10100010 + 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300 + 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x + 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x + 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x + 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 + 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x + 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x + 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x +End TriggerBlock diff --git a/test/test_player.py b/test/test_player.py index 65fa5b6b9..ab275b81a 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -66,10 +66,25 @@ def test_play_virtual_exit(self): self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() + def test_play_skip_error_frame(self): + logfile = os.path.join( + os.path.dirname(__file__), "data", "logfile_errorframes.asc" + ) + sys.argv = self.baseargs + ["-v", logfile] + can.player.main() + self.assertEqual(self.mock_virtual_bus.send.call_count, 9) + self.assertEqual(self.MockSleep.call_count, 12) + self.assertSuccessfullCleanup() + def test_play_error_frame(self): - # TODO: implement - sys.argv = self.baseargs + ["--error-frames", self.logfile] + logfile = os.path.join( + os.path.dirname(__file__), "data", "logfile_errorframes.asc" + ) + sys.argv = self.baseargs + ["-v", "--error-frames", logfile] can.player.main() + self.assertEqual(self.mock_virtual_bus.send.call_count, 12) + self.assertEqual(self.MockSleep.call_count, 12) + self.assertSuccessfullCleanup() if __name__ == "__main__": From dcbb9631a742a8951ca19f3f8083300217abfae9 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 26 Dec 2021 13:57:12 +0000 Subject: [PATCH 0803/1235] Format code with black --- test/test_player.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_player.py b/test/test_player.py index ab275b81a..c1048aae6 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -49,10 +49,10 @@ def test_play_virtual(self): def test_play_virtual_verbose(self): sys.argv = self.baseargs + ["-v", self.logfile] - with unittest.mock.patch('sys.stdout', new_callable=io.StringIO) as mock_stdout: + with unittest.mock.patch("sys.stdout", new_callable=io.StringIO) as mock_stdout: can.player.main() - self.assertIn('09 08 07 06 05 04 03 02', mock_stdout.getvalue()) - self.assertIn('05 0c 00 00 00 00 00 00', mock_stdout.getvalue()) + self.assertIn("09 08 07 06 05 04 03 02", mock_stdout.getvalue()) + self.assertIn("05 0c 00 00 00 00 00 00", mock_stdout.getvalue()) self.assertEqual(self.mock_virtual_bus.send.call_count, 2) self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfullCleanup() From 558b2974187d65911c0ba19e9f6893cece20745d Mon Sep 17 00:00:00 2001 From: TJ Date: Sat, 1 Jan 2022 12:53:21 -0800 Subject: [PATCH 0804/1235] Add str conversion to config val --- can/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/util.py b/can/util.py index e43a4d09d..9400259ce 100644 --- a/can/util.py +++ b/can/util.py @@ -232,7 +232,7 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: "btr1", ): if key in config: - timing_conf[key] = int(config[key], base=0) + timing_conf[key] = int(str(config[key]), base=0) del config[key] if timing_conf: timing_conf["bitrate"] = config["bitrate"] From eb8d92d797ad3517eb79743e6477392a56a1dc2e Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 10 Jan 2022 19:58:17 +0100 Subject: [PATCH 0805/1235] Fix typo --- can/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/logger.py b/can/logger.py index d078cf2d3..053001968 100644 --- a/can/logger.py +++ b/can/logger.py @@ -61,7 +61,7 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: "extra_args", nargs=argparse.REMAINDER, help="""\ - The remainding arguments will be used for the interface initialisation. + The remaining arguments will be used for the interface initialisation. For example, `-i vector -c 1 --app-name=MyCanApp` is the equivalent to opening the bus with `Bus('vector', channel=1, app_name='MyCanApp')` """, From 2e24af08326ecd69fba9f02fed7b9c26f233c92b Mon Sep 17 00:00:00 2001 From: Daniel Hrisca Date: Tue, 11 Jan 2022 00:33:38 +0200 Subject: [PATCH 0806/1235] Fix vector get application config (#977) * fixes #732: add support for VN8900 xlGetChannelTime function * add another level of try/except according to the review * format using black * the application channels needs to be provided as a ctypes variable * fix mock in test_vector.py Co-authored-by: zariiii9003 --- can/interfaces/vector/canlib.py | 1 + test/test_vector.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 21c6d0f1f..1313b00b7 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -688,6 +688,7 @@ def get_application_config( hw_type = ctypes.c_uint() hw_index = ctypes.c_uint() hw_channel = ctypes.c_uint() + app_channel = ctypes.c_uint(app_channel) xldriver.xlGetApplConfig( app_name.encode(), diff --git a/test/test_vector.py b/test/test_vector.py index fc77dd48d..f7b1f99ce 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -356,7 +356,7 @@ def xlGetApplConfig( bus_type: ctypes.c_uint, ) -> int: hw_type.value = 1 - hw_channel.value = app_channel + hw_channel.value = 0 return 0 From 7da73032752a9aee22ac1e1d2717ae4fd78171df Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Tue, 11 Jan 2022 20:27:34 +0100 Subject: [PATCH 0807/1235] Remove useless import Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- test/test_player.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_player.py b/test/test_player.py index c1048aae6..ca590a898 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -14,7 +14,6 @@ import can import can.player -from .config import * class TestPlayerScriptModule(unittest.TestCase): From 2cb76b51f8178d5c5b35d3a0a41e4651e35ef493 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Tue, 11 Jan 2022 19:28:31 +0000 Subject: [PATCH 0808/1235] Format code with black --- test/test_player.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_player.py b/test/test_player.py index ca590a898..a3b88d65a 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -15,7 +15,6 @@ import can.player - class TestPlayerScriptModule(unittest.TestCase): def setUp(self) -> None: # Patch VirtualBus object From 5c6480bd03c3e0daf6891bd06baf8ca642ea304c Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Tue, 11 Jan 2022 20:31:59 +0100 Subject: [PATCH 0809/1235] Correct spell error --- test/test_player.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_player.py b/test/test_player.py index a3b88d65a..d510a3d00 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -34,7 +34,7 @@ def setUp(self) -> None: os.path.dirname(__file__), "data", "test_CanMessage.asc" ) - def assertSuccessfullCleanup(self): + def assertSuccessfulCleanup(self): self.MockVirtualBus.assert_called_once() self.mock_virtual_bus.__exit__.assert_called_once() @@ -43,7 +43,7 @@ def test_play_virtual(self): can.player.main() self.assertEqual(self.mock_virtual_bus.send.call_count, 2) self.assertEqual(self.MockSleep.call_count, 2) - self.assertSuccessfullCleanup() + self.assertSuccessfulCleanup() def test_play_virtual_verbose(self): sys.argv = self.baseargs + ["-v", self.logfile] @@ -53,7 +53,7 @@ def test_play_virtual_verbose(self): self.assertIn("05 0c 00 00 00 00 00 00", mock_stdout.getvalue()) self.assertEqual(self.mock_virtual_bus.send.call_count, 2) self.assertEqual(self.MockSleep.call_count, 2) - self.assertSuccessfullCleanup() + self.assertSuccessfulCleanup() def test_play_virtual_exit(self): self.MockSleep.side_effect = [None, KeyboardInterrupt] @@ -62,7 +62,7 @@ def test_play_virtual_exit(self): can.player.main() self.assertEqual(self.mock_virtual_bus.send.call_count, 1) self.assertEqual(self.MockSleep.call_count, 2) - self.assertSuccessfullCleanup() + self.assertSuccessfulCleanup() def test_play_skip_error_frame(self): logfile = os.path.join( @@ -72,7 +72,7 @@ def test_play_skip_error_frame(self): can.player.main() self.assertEqual(self.mock_virtual_bus.send.call_count, 9) self.assertEqual(self.MockSleep.call_count, 12) - self.assertSuccessfullCleanup() + self.assertSuccessfulCleanup() def test_play_error_frame(self): logfile = os.path.join( @@ -82,7 +82,7 @@ def test_play_error_frame(self): can.player.main() self.assertEqual(self.mock_virtual_bus.send.call_count, 12) self.assertEqual(self.MockSleep.call_count, 12) - self.assertSuccessfullCleanup() + self.assertSuccessfulCleanup() if __name__ == "__main__": From 07855c83ba4da8202bf17c4867fa1f6ecb62a683 Mon Sep 17 00:00:00 2001 From: TJ Date: Thu, 13 Jan 2022 20:40:51 -0800 Subject: [PATCH 0810/1235] Add unittest for timing conf --- test/test_util.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/test_util.py b/test/test_util.py index bbfb9d580..7e3b10604 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -1,7 +1,7 @@ import unittest import warnings -from can.util import _rename_kwargs +from can.util import _create_bus_config, _rename_kwargs class RenameKwargsTest(unittest.TestCase): @@ -47,3 +47,18 @@ def test_with_new_and_alias_present(self): aliases = {"old_a": "a", "old_b": "b", "z": None} with self.assertRaises(TypeError): self._test(kwargs, aliases) + + +class TestBusConfig(unittest.TestCase): + base_config = dict(interface="socketcan", bitrate=500_000) + + def test_timing_can_use_int(self): + """ + Test that an exception is not raised when using + integers for timing values in config. + """ + timing_conf = dict(tseg1=5, tseg2=10, sjw=25) + try: + _create_bus_config({**self.base_config, **timing_conf}) + except TypeError as e: + self.fail(e) From 6f94d6b2791bda91bb80a332bd59c66dc11617ad Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sat, 15 Jan 2022 11:43:15 +0100 Subject: [PATCH 0811/1235] Update workflow versions, platforms, library and tool versions and README to Python 3.7 --- .github/workflows/build.yml | 16 ++++++---------- .github/workflows/format-code.yml | 2 +- README.rst | 4 ++-- requirements-lint.txt | 6 +++--- setup.py | 5 ++--- tox.ini | 6 +++--- 6 files changed, 17 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb4c9a03b..95b295f90 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,16 +13,12 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] experimental: [false] - python-version: ["3.6", "3.7", "3.8", "3.9", "pypy-3.7"] + python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"] include: - # Skipping Py 3.10 on Windows until windows-curses has a cp310 wheel, - # see https://github.com/zephyrproject-rtos/windows-curses/issues/26 + # Only test on a single configuration while there are just pre-releases - os: ubuntu-latest - experimental: false - python-version: "3.10" - - os: macos-latest - experimental: false - python-version: "3.10" + experimental: true + python-version: "3.11.0-alpha.3" fail-fast: false steps: - uses: actions/checkout@v2 @@ -38,7 +34,7 @@ jobs: run: | tox -e gh - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: fail_ci_if_error: true @@ -46,7 +42,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.10 + - name: Set up Python uses: actions/setup-python@v2 with: python-version: "3.10" diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index 81e8fdf03..c3356e211 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/README.rst b/README.rst index ac5537f7e..ebfa8431f 100644 --- a/README.rst +++ b/README.rst @@ -51,7 +51,7 @@ Python developers; providing common abstractions to different hardware devices, and a suite of utilities for sending and receiving messages on a can bus. -The library currently supports Python 3.6+ as well as PyPy 3 and runs +The library currently supports Python 3.7+ as well as PyPy 3 and runs on Mac, Linux and Windows. ============================== =========== @@ -59,7 +59,7 @@ Library Version Python ------------------------------ ----------- 2.x 2.6+, 3.4+ 3.x 2.7+, 3.5+ - 4.x *(currently on develop)* 3.6+ + 4.x *(currently on develop)* 3.7+ ============================== =========== diff --git a/requirements-lint.txt b/requirements-lint.txt index 9aefb7415..55d985d54 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ -pylint==2.11.1 -black==21.10b0 -mypy==0.910 +pylint==2.12.2 +black==21.12b0 +mypy==0.931 mypy-extensions==0.4.3 diff --git a/setup.py b/setup.py index 31318ac06..af47ecd4f 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,6 @@ classifiers=[ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -73,7 +72,7 @@ version=version, packages=find_packages(exclude=["test*", "doc", "scripts", "examples"]), scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), - author="Python CAN contributors", + author="python-can contributors", license="LGPL v3", package_data={ "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], @@ -82,7 +81,7 @@ }, # Installation # see https://www.python.org/dev/peps/pep-0345/#version-specifiers - python_requires=">=3.6", + python_requires=">=3.7", install_requires=[ "setuptools", "wrapt~=1.10", diff --git a/tox.ini b/tox.ini index 9964fb1e6..6b407dfeb 100644 --- a/tox.ini +++ b/tox.ini @@ -3,11 +3,11 @@ [testenv] deps = pytest==6.2.*,>=6.2.5 - pytest-timeout==2.0.1 + pytest-timeout==2.0.2 pytest-cov==3.0.0 - coverage==6.0.2 + coverage==6.2 codecov==2.1.12 - hypothesis~=6.24.0 + hypothesis~=6.35.0 pyserial~=3.5 parameterized~=0.8 From dd25e4b6feb7300028af4c5ce89a7646f85e684a Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sat, 15 Jan 2022 11:52:40 +0100 Subject: [PATCH 0812/1235] Fix code formatting job --- .github/workflows/format-code.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index c3356e211..b86789662 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip @@ -22,10 +22,7 @@ jobs: run: | black --verbose . - name: Commit Formated Code - uses: EndBug/add-and-commit@v5 - env: - # This is necessary in order to push a commit to the repo - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: EndBug/add-and-commit@v7 with: message: "Format code with black" # Ref https://git-scm.com/docs/git-add#_examples From 46ad7d46b92b3f1192c1511e67e3739becadf99f Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sat, 15 Jan 2022 12:00:27 +0100 Subject: [PATCH 0813/1235] Disable Python 3.11 pre-releases --- .github/workflows/build.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95b295f90..be222751c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,11 +14,12 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] experimental: [false] python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"] - include: + # Do not test on Python 3.11 pre-releases since wrapt causes problems: https://github.com/GrahamDumpleton/wrapt/issues/196 + # include: # Only test on a single configuration while there are just pre-releases - - os: ubuntu-latest - experimental: true - python-version: "3.11.0-alpha.3" + # - os: ubuntu-latest + # experimental: true + # python-version: "3.11.0-alpha.3" fail-fast: false steps: - uses: actions/checkout@v2 From 8641f5e02ec084b6754e38a7674aa72808a8de5c Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sat, 15 Jan 2022 12:01:07 +0100 Subject: [PATCH 0814/1235] Test that the auto-formatting tool can still commit --- can/ctypesutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 7c1e1f573..ab4cb11b5 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -14,7 +14,7 @@ try: - _LibBase = ctypes.WinDLL # type: ignore + _LibBase = ctypes.WinDLL # type: ignore _FUNCTION_TYPE = ctypes.WINFUNCTYPE # type: ignore except AttributeError: _LibBase = ctypes.CDLL From 0acabbff21248b5c76039f026d31053711918147 Mon Sep 17 00:00:00 2001 From: felixdivo Date: Sat, 15 Jan 2022 11:01:42 +0000 Subject: [PATCH 0815/1235] Format code with black --- can/ctypesutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index ab4cb11b5..7c1e1f573 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -14,7 +14,7 @@ try: - _LibBase = ctypes.WinDLL # type: ignore + _LibBase = ctypes.WinDLL # type: ignore _FUNCTION_TYPE = ctypes.WINFUNCTYPE # type: ignore except AttributeError: _LibBase = ctypes.CDLL From 954bb0295a845573d56a198c4b6c0d2d0fb8fdb7 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sat, 15 Jan 2022 21:37:48 +0100 Subject: [PATCH 0816/1235] Update README.rst This way, the information is not given twice and we actually mention **C**Python. --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index ac5537f7e..2d7f615e7 100644 --- a/README.rst +++ b/README.rst @@ -51,8 +51,7 @@ Python developers; providing common abstractions to different hardware devices, and a suite of utilities for sending and receiving messages on a can bus. -The library currently supports Python 3.6+ as well as PyPy 3 and runs -on Mac, Linux and Windows. +The library currently supports CPython as well as PyPy and runs on Mac, Linux and Windows. ============================== =========== Library Version Python From 1dd4581dc36352ffa374caccdcdd068dde03af29 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 15 Jan 2022 21:57:18 +0100 Subject: [PATCH 0817/1235] Implement test of function calls --- test/test_player.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/test_player.py b/test/test_player.py index d510a3d00..c73623c05 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -41,8 +41,29 @@ def assertSuccessfulCleanup(self): def test_play_virtual(self): sys.argv = self.baseargs + [self.logfile] can.player.main() - self.assertEqual(self.mock_virtual_bus.send.call_count, 2) + msg1 = can.Message( + timestamp=2.501, + arbitration_id=0xC8, + is_extended_id=False, + is_fd=False, + is_rx=False, + channel=1, + dlc=8, + data=[0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2], + ) + msg2 = can.Message( + timestamp=17.876708, + arbitration_id=0x6F9, + is_extended_id=False, + is_fd=False, + is_rx=True, + channel=0, + dlc=8, + data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], + ) self.assertEqual(self.MockSleep.call_count, 2) + self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0])) + self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0])) self.assertSuccessfulCleanup() def test_play_virtual_verbose(self): From e7842f4fbff7ff45a56f061c180300809ce81238 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sat, 15 Jan 2022 22:01:46 +0100 Subject: [PATCH 0818/1235] Add TL;DR --- CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index cf2a8b027..bcb2189cd 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,7 +1,7 @@ Version 4.0.0 ==== -(In development) +TL;DR: This release includes a ton of improvements from 2.5 years of development! 🎉 Test thoroughly after switching. Version 3.3.4 From 3382d5310db30359c387889ca2f76b8c71b031e2 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 15 Jan 2022 22:45:00 +0100 Subject: [PATCH 0819/1235] Limit test for correct calls to python versions >= 3.8 --- test/test_player.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_player.py b/test/test_player.py index c73623c05..a4f9ed4ad 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -62,8 +62,10 @@ def test_play_virtual(self): data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], ) self.assertEqual(self.MockSleep.call_count, 2) - self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0])) - self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0])) + if sys.version_info.major > 3 or sys.version_info.minor >= 8: + # The args argument was introduced with python 3.8 + self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0])) + self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0])) self.assertSuccessfulCleanup() def test_play_virtual_verbose(self): From 67e409805e306fef778442d8c5d65d4c185324ee Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 15 Jan 2022 21:45:54 +0000 Subject: [PATCH 0820/1235] Format code with black --- test/test_player.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_player.py b/test/test_player.py index a4f9ed4ad..c58ff936e 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -64,8 +64,12 @@ def test_play_virtual(self): self.assertEqual(self.MockSleep.call_count, 2) if sys.version_info.major > 3 or sys.version_info.minor >= 8: # The args argument was introduced with python 3.8 - self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0])) - self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0])) + self.assertTrue( + msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0]) + ) + self.assertTrue( + msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0]) + ) self.assertSuccessfulCleanup() def test_play_virtual_verbose(self): From 81827517ee24fc3ddc3fc464574611ec054a2c06 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 15 Jan 2022 22:50:04 +0100 Subject: [PATCH 0821/1235] Use lowercase parameter names Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- test/test_pcan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_pcan.py b/test/test_pcan.py index e8c960f6b..6fc21184a 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -37,15 +37,15 @@ def tearDown(self) -> None: self.bus.shutdown() self.bus = None - def _mockGetValue(self, Channel, Parameter): + def _mockGetValue(self, channel, parameter): """ This method is used as mock for GetValue method of PCANBasic object. Only a subset of parameters are supported. """ - if Parameter == PCAN_API_VERSION: + if parameter == PCAN_API_VERSION: return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii") raise NotImplementedError( - f"No mock return value specified for parameter {Parameter}" + f"No mock return value specified for parameter {parameter}" ) def test_bus_creation(self) -> None: From a5f0ef80d604643a21f6c4880564143adfcbf9fb Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 16 Jan 2022 00:01:07 +0100 Subject: [PATCH 0822/1235] simplify version check --- test/test_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_player.py b/test/test_player.py index c58ff936e..efb446e9e 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -62,7 +62,7 @@ def test_play_virtual(self): data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], ) self.assertEqual(self.MockSleep.call_count, 2) - if sys.version_info.major > 3 or sys.version_info.minor >= 8: + if sys.version_info >= (3, 8): # The args argument was introduced with python 3.8 self.assertTrue( msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0]) From 7a20405c5be293e7c293862fd096f126a4d20b89 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 16 Jan 2022 11:33:19 +0100 Subject: [PATCH 0823/1235] Change shebang and remove coding Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- test/test_player.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_player.py b/test/test_player.py index efb446e9e..2f3307420 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -# coding: utf-8 +#!/usr/bin/env python """ This module tests the functions inside of player.py From 99d352ab6a22ad2c1e9212c76d5b4db927cbde93 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 16 Jan 2022 11:41:21 +0100 Subject: [PATCH 0824/1235] Correct shebang and remove coding from all tests --- test/test_bit_timing.py | 2 ++ test/test_cantact.py | 1 - test/test_cyclic_socketcan.py | 2 ++ test/test_detect_available_configs.py | 1 - test/test_interface_ixxat.py | 2 ++ test/test_interface_ixxat_fd.py | 2 ++ test/test_load_file_config.py | 1 - test/test_logger.py | 1 - test/test_message_class.py | 1 - test/test_message_filtering.py | 1 - test/test_message_sync.py | 1 - test/test_neousys.py | 1 - test/test_robotell.py | 1 - test/test_scripts.py | 1 - test/test_slcan.py | 1 - test/test_socketcan.py | 2 ++ test/test_socketcan_helpers.py | 1 - test/test_socketcan_loopback.py | 2 ++ test/test_util.py | 2 ++ test/test_vector.py | 1 - test/test_viewer.py | 3 +-- test/zero_dlc_test.py | 1 - 22 files changed, 15 insertions(+), 16 deletions(-) diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py index 0b22e308f..2a9b1ac79 100644 --- a/test/test_bit_timing.py +++ b/test/test_bit_timing.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import can diff --git a/test/test_cantact.py b/test/test_cantact.py index 51bf569bb..e361ad1ad 100644 --- a/test/test_cantact.py +++ b/test/test_cantact.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ Tests for CANtact interfaces diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index 4e3887ad6..ca1db6bfc 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + """ This module tests multiple message cyclic send tasks. """ diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py index 6e92ad9aa..f6590c276 100644 --- a/test/test_detect_available_configs.py +++ b/test/test_detect_available_configs.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests :meth:`can.BusABC._detect_available_configs` and diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index ccd985051..76285e422 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + """ Unittest for ixxat interface. diff --git a/test/test_interface_ixxat_fd.py b/test/test_interface_ixxat_fd.py index 0aa999a21..80060a7ed 100644 --- a/test/test_interface_ixxat_fd.py +++ b/test/test_interface_ixxat_fd.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + """ Unittest for ixxat interface using fd option. diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py index 79b2e6c4b..c71e6ccd6 100644 --- a/test/test_load_file_config.py +++ b/test/test_load_file_config.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 import shutil import tempfile diff --git a/test/test_logger.py b/test/test_logger.py index a046b919f..9b749b859 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests the functions inside of logger.py diff --git a/test/test_message_class.py b/test/test_message_class.py index d0908e363..688cda24f 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 import unittest import sys diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index 18ddf9e19..addea13fd 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests :meth:`can.BusABC._matches_filters`. diff --git a/test/test_message_sync.py b/test/test_message_sync.py index 6e6a89a4d..1e2d61b24 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests :class:`can.MessageSync`. diff --git a/test/test_neousys.py b/test/test_neousys.py index c2ae535f5..f61c37655 100644 --- a/test/test_neousys.py +++ b/test/test_neousys.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 import ctypes import os diff --git a/test/test_robotell.py b/test/test_robotell.py index 58e2d9a7f..86e053f2d 100644 --- a/test/test_robotell.py +++ b/test/test_robotell.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 import unittest import can diff --git a/test/test_scripts.py b/test/test_scripts.py index 8efd70eff..a22820bd8 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests that the scripts are all callable. diff --git a/test/test_slcan.py b/test/test_slcan.py index 781fa75df..1e6282d41 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 import unittest import can diff --git a/test/test_socketcan.py b/test/test_socketcan.py index c322bbf75..a2c4faed3 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + """ Test functions in `can.interfaces.socketcan.socketcan`. """ diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index 669491f43..f3fbe6d26 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ Tests helpers in `can.interfaces.socketcan.socketcan_common`. diff --git a/test/test_socketcan_loopback.py b/test/test_socketcan_loopback.py index 17b83b268..2934eb9ea 100644 --- a/test/test_socketcan_loopback.py +++ b/test/test_socketcan_loopback.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + """ This module tests sending messages on socketcan with and without local_loopback flag diff --git a/test/test_util.py b/test/test_util.py index 7e3b10604..5768da282 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import unittest import warnings diff --git a/test/test_vector.py b/test/test_vector.py index f7b1f99ce..b1626b18c 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ Test for Vector Interface diff --git a/test/test_viewer.py b/test/test_viewer.py index 004877d7a..f2e3ef0e8 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -1,5 +1,4 @@ -#!/usr/bin/python -# coding: utf-8 +#!/usr/bin/env python # # Copyright (C) 2018 Kristian Sloth Lauszus. # diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py index 350d6aa4e..dd7c0dd49 100644 --- a/test/zero_dlc_test.py +++ b/test/zero_dlc_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ """ From 80352164b6530fa1691b974c5c84d0953bb9b7f5 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 16 Jan 2022 17:15:38 +0100 Subject: [PATCH 0825/1235] Add changelog for 4.0.0 Also change the changelog file extension to Markdown --- CHANGELOG.txt => CHANGELOG.md | 181 ++++++++++++++++++++++++++++++++++ setup.py | 2 +- 2 files changed, 182 insertions(+), 1 deletion(-) rename CHANGELOG.txt => CHANGELOG.md (50%) diff --git a/CHANGELOG.txt b/CHANGELOG.md similarity index 50% rename from CHANGELOG.txt rename to CHANGELOG.md index bcb2189cd..52b04e510 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.md @@ -3,6 +3,187 @@ Version 4.0.0 TL;DR: This release includes a ton of improvements from 2.5 years of development! 🎉 Test thoroughly after switching. +For more than two years, there was no major release of *python-can*. +However, development was very much active over most of this time, and many parts were switched out and improved. +Over this time, over 530 issues and PRs have been resolved or merged, and discussions took place in even more. +Statistics of the final diff: About 200 files changed due to ~22k additions and ~7k deletions from more than thirty contributors. + +This changelog diligently lists the major changes but does not promise to be the complete list of changes. +Therefore, users are strongly advised to thoroughly test their programs against this new version. +Re-reading the documentation for you interfaces might be helpful too as limitations and capabilities might have changed or are more explicit. +While we did try to avoid breaking changes, in some cases it was not feasible and in particular many implementation details have changed. + +Major features +-------------- + +* Type hints for the core library and some interfaces (#652 and many others) +* Support for Python 3.7-3.10+ only (dropped support for Python 2.* and 3.5-3.6) (#528 and many others) +* [Granular and unified exceptions](https://python-can.readthedocs.io/en/develop/api.html#errors) (#356, #562, #1025; overview in #1046) +* [Support for automatic configuration detection](https://python-can.readthedocs.io/en/develop/api.html#can.detect_available_configs) in most interfaces (#303, #640, #641, #811, #1077, #1085) +* Better alignment of interfaces and IO to common conventions and semantics + +New interfaces +-------------- + +* udp_multicast (#644) +* robotell (#731) +* cantact (#853) +* gs_usb (#905) +* nixnet (#968, #1154) +* neousys (#980, #1076) +* socketcand (#1140) +* etas (#1144) + +Improved interfaces +------------------- + +* socketcan + * Support for multiple Cyclic Messages in Tasks (#610) + * Socketcan crash when attempting to stop CyclicSendTask with same arbitration ID (#605, #638, #720) + * Relax restriction of arbitration ID uniqueness for CyclicSendTask (#721, #785, #930) + * Add nanosecond resolution time stamping to socketcan (#938, #1015) + * Add support for changing the loopback flag (#960) + * Socketcan timestamps are missing sub-second precision (#1021, #1029) + * Add parameter to ignore CAN error frames (#1128) +* socketcan_ctypes + * Removed and replaced by socketcan after deprecation period +* socketcan_native + * Removed and replaced by socketcan after deprecation period +* vector + * Add chip state API (#635) + * Add methods to handle non message events (#708) + * Implement XLbusParams (#718) + * Add support for VN8900 xlGetChannelTime function (#732, #733) + * Add vector hardware config popup (#774) + * Fix Vector CANlib treatment of empty app name (#796, #814) + * Make VectorError pickleable (#848) + * Add methods get_application_config(), set_application_config() and set_timer_rate() to VectorBus (#849) + * Interface arguments are now lowercase (#858) + * Fix errors using multiple Vector devices (#898, #971, #977) + * Add more interface information to channel config (#917) + * Improve timestamp accuracy on Windows (#934, #936) + * Fix error with VN8900 (#1184) +* PCAN + * Do not incorrectly reset CANMsg.MSGTYPE on remote frame (#659, #681) + * Add support for error frames (#711) + * Added keycheck for windows platform for better error message (#724) + * Added status_string method to return simple status strings (#725) + * Fix timestamp timezone offset (#777, #778) + * Add [Cygwin](https://www.cygwin.com/) support (#840) + * Update PCAN basic Python file to February 7, 2020 (#929) + * Fix compatibility with the latest macOS SDK (#947, #948, #957, #976) + * Allow numerical channel specifier (#981, #982) + * macOS: Try to find libPCBUSB.dylib before loading it (#983, #984) + * Disable command PCAN_ALLOW_ERROR_FRAMES on macOS (#985) + * Force english error messages (#986, #993, #994) + * Add set/get device number (#987) + * Timestamps are silently incorrect on Windows without uptime installed (#1053, #1093) + * Implement check for minimum version of pcan library (#1065, #1188) + * Handle case where uptime is imported successfully but returns None (#1102, #1103) +* slcan + * Fix bitrate setting (#691) + * Fix fileno crash on Windows (#924) +* ics_neovi + * Filter out Tx error messages (#854) + * Adding support for send timeout (#855) + * Raising more precise API error when set bitrate fails (#865) + * Omit the transmit exception cause for brevity (#1086) + * Raise ValueError if message data is over max frame length (#1177, #1181) + * Setting is_error_frame message property (#1189) +* ixxat + * Raise exception on busoff in recv() (#856) + * Add support for 666 kbit/s bitrate (#911) + * Add function to list hwids of available devices (#926) + * Add CAN FD support (#1119) +* seeed + * Fix fileno crash on Windows (#902) +* kvaser + * Improve timestamp accuracy on Windows (#934, #936) +* usb2can + * Fix "Error 8" on Windows and provide better error messages (#989) +* serial + * Fix "TypeError: cannot unpack non-iterable NoneType" and more robust error handling (#1000, #1010) +* canalystii + * Fix is_extended_id (#1006) + * Fix transmitting onto a busy bus (#1114) + * Replace binary library with python driver (#726, #1127) + +Other API changes and improvements +---------------------------------- + +* CAN FD frame support is pretty complete (#963) + * ASCWriter (#604) and ASCReader (#741) + * Canutils reader and writer (#1042) + * Logger, viewer and player tools can handle CAN FD (#632) + * Many bugfixes and more testing coverage +* IO + * Log rotation (#648, #874, #881, #1147) + * Add [plugin support to can.io Reader/Writer](https://python-can.readthedocs.io/en/develop/listeners.html#listener) (#783) + * ASCReader/Writer enhancements (#820) + * Adding absolute timestamps to ASC reader (#761) + * Support other base number (radix) at ASCReader (#764) + * Add [logconvert script](https://python-can.readthedocs.io/en/develop/scripts.html#can-logconvert) (#1072, #1194) + * Adding support for gzipped ASC logging file (.asc.gz) (#1138) + * Improve [IO class hierarchy](https://python-can.readthedocs.io/en/develop/internal-api.html#module-can.io.generic) (#1147) +* An [overview over various "virtual" interfaces](https://python-can.readthedocs.io/en/develop/interfaces/virtual.html#other-virtual-interfaces) (#644) +* Make ThreadBasedCyclicSendTask event based & improve timing accuracy (#656) +* Ignore error frames in can.player by default, add --error-frames option (#690) +* Add __eq__ method to can.Message (#737, #747) +* Add an error callback to ThreadBasedCyclicSendTask (#743, #781) +* Add direction to CAN messages (#773, #779, #780, #852, #966) +* Notifier no longer raises handled exceptions in rx_thread (#775, #789) but does so if no listener handles them (#1039, #1040) +* Changes to serial device number decoding (#869) +* Add a default fileno function to the BusABC (#877) +* Disallow Messages to simultaneously be "FD" and "remote" (#1049) +* Speed up interface plugin imports by removing pkg_resources (#1110) +* Avoid flooding the logger with many errors when they are the same (#1125) +* Allowing for extra config arguments in can.logger (#1142, #1170) +* Add changed byte highlighting to viewer.py (#1159) + +Other Bugfixes +-------------- + +* BLF PDU padding (#459) +* stop_all_periodic_tasks skipping every other task (#634, #637, #645) +* Preserve capitalization when reading config files (#702, #1062) +* ASCReader: Skip J1939Tp messages (#701) +* Fix crash in Canutils Log Reader when parsing RTR frames (#713) +* Various problems with the installation of the library +* ASCWriter: Fix date format to show correct day of month (#754) +* Fixes that some BLF files can't be read ( #763, #765) +* Seek for start of object instead of calculating it (#786, #803, #806) +* Only import winreg when on Windows (#800, #802) +* Find the correct Reader/Writer independently of the file extension case (#895) +* RecursionError when unpickling message object (#804, #885, #904) +* Move "filelock" to neovi dependencies (#943) +* Bus() with "fd" parameter as type bool always resolved to fd-enabled configuration (#954, #956) +* Asyncio code hits error due to deprecated loop parameter (#1005, #1013) +* Catch time before 1970 in ASCReader (#1034) +* Fix a bug where error handlers were not called correctly (#1116) +* Improved user interface of viewer script (#1118) +* Correct app_name argument in logger (#1151) +* Calling stop_all_periodic_tasks() in BusABC.shutdown() and all interfaces call it on shutdown (#1174) +* Timing configurations do not allow int (#1175) +* Some smaller bugfixes are not listed here since the problems were never part of a proper release + +Behind the scenes & Quality assurance +------------------------------------- + +* We publish both source distributions (`sdist`) and binary wheels (`bdist_wheel`) (#1059, #1071) +* Many interfaces were partly rewritten to modernize the code or to better handle errors +* Performance improvements +* Dependencies have changed +* Derive type information in Sphinx docs directly from type hints (#654) +* Better documentation in many, many places; This includes the examples, README and python-can developer resources +* Add issue templates (#1008, #1017, #1018, #1178) +* Many continuous integration (CI) discussions & improvements (for example: #951, #940, #1032) + * Use the [mypy](https://github.com/python/mypy) static type checker (#598, #651) + * Use [tox](https://tox.wiki/en/latest/) for testing (#582, #833, #870) + * Use [Mergify](https://mergify.com/) (#821, #835, #937) + * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and [Travis CI](https://travis-ci.org/), ended up with [GitHub Actions](https://docs.github.com/en/actions) only (#827) + * Use the [black](https://black.readthedocs.io/en/stable/) auto-formatter (#950) + * [Good test coverage](https://app.codecov.io/gh/hardbyte/python-can/branch/develop) for all but the interfaces +* Testing: Many of the new features directly added tests, and coverage of existing code was improved too (for example: #1031, #581, #585, #586, #942, #1196, #1198) Version 3.3.4 ==== diff --git a/setup.py b/setup.py index 31318ac06..1fa5c7f6b 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ author="Python CAN contributors", license="LGPL v3", package_data={ - "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], + "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.md"], "doc": ["*.*"], "examples": ["*.py"], }, From 2ba9bd39e8a57afb68f1ac5de5208e925f86adb5 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 16 Jan 2022 20:13:03 +0100 Subject: [PATCH 0826/1235] React to #1219 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b04e510..80c765f93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -180,7 +180,7 @@ Behind the scenes & Quality assurance * Use the [mypy](https://github.com/python/mypy) static type checker (#598, #651) * Use [tox](https://tox.wiki/en/latest/) for testing (#582, #833, #870) * Use [Mergify](https://mergify.com/) (#821, #835, #937) - * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and [Travis CI](https://travis-ci.org/), ended up with [GitHub Actions](https://docs.github.com/en/actions) only (#827) + * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and partly [Travis CI](https://travis-ci.org/), ended up with [GitHub Actions](https://docs.github.com/en/actions) only (#827) * Use the [black](https://black.readthedocs.io/en/stable/) auto-formatter (#950) * [Good test coverage](https://app.codecov.io/gh/hardbyte/python-can/branch/develop) for all but the interfaces * Testing: Many of the new features directly added tests, and coverage of existing code was improved too (for example: #1031, #581, #585, #586, #942, #1196, #1198) From 7226fa373ee51800ef7a77867af1e8155c14ebc5 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 16 Jan 2022 20:35:31 +0100 Subject: [PATCH 0827/1235] Typos --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80c765f93..829f70ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,8 @@ Statistics of the final diff: About 200 files changed due to ~22k additions and This changelog diligently lists the major changes but does not promise to be the complete list of changes. Therefore, users are strongly advised to thoroughly test their programs against this new version. -Re-reading the documentation for you interfaces might be helpful too as limitations and capabilities might have changed or are more explicit. -While we did try to avoid breaking changes, in some cases it was not feasible and in particular many implementation details have changed. +Re-reading the documentation for your interfaces might be helpful too as limitations and capabilities might have changed or are more explicit. +While we did try to avoid breaking changes, in some cases it was not feasible and in particular, many implementation details have changed. Major features -------------- From e86b8656b2d22fd2ca1f407ca5c448534ffd7b02 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 17 Jan 2022 14:14:40 -0500 Subject: [PATCH 0828/1235] Moved #1125 to the neovi section --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 829f70ac4..b5676c96d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Improved interfaces * Filter out Tx error messages (#854) * Adding support for send timeout (#855) * Raising more precise API error when set bitrate fails (#865) + * Avoid flooding the logger with many errors when they are the same (#1125) * Omit the transmit exception cause for brevity (#1086) * Raise ValueError if message data is over max frame length (#1177, #1181) * Setting is_error_frame message property (#1189) @@ -136,7 +137,6 @@ Other API changes and improvements * Add a default fileno function to the BusABC (#877) * Disallow Messages to simultaneously be "FD" and "remote" (#1049) * Speed up interface plugin imports by removing pkg_resources (#1110) -* Avoid flooding the logger with many errors when they are the same (#1125) * Allowing for extra config arguments in can.logger (#1142, #1170) * Add changed byte highlighting to viewer.py (#1159) From a4154975696c171118115438884547be0ca8f977 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 18 Jan 2022 10:29:16 +0100 Subject: [PATCH 0829/1235] Update CHANGELOG.md Message.__eq__ is gone. No need to mention it here. Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5676c96d..da451f157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,7 +129,6 @@ Other API changes and improvements * An [overview over various "virtual" interfaces](https://python-can.readthedocs.io/en/develop/interfaces/virtual.html#other-virtual-interfaces) (#644) * Make ThreadBasedCyclicSendTask event based & improve timing accuracy (#656) * Ignore error frames in can.player by default, add --error-frames option (#690) -* Add __eq__ method to can.Message (#737, #747) * Add an error callback to ThreadBasedCyclicSendTask (#743, #781) * Add direction to CAN messages (#773, #779, #780, #852, #966) * Notifier no longer raises handled exceptions in rx_thread (#775, #789) but does so if no listener handles them (#1039, #1040) From f7f9a8db3c137584fee48fba14e8589e71942bb4 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:27:50 +0100 Subject: [PATCH 0830/1235] move static code analysis to Github actions --- .github/workflows/build.yml | 39 +++++++++++++++++++++++++++++++++++++ .travis.yml | 17 ---------------- requirements-lint.txt | 1 + setup.cfg | 2 +- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be222751c..21eec2048 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,45 @@ jobs: with: fail_ci_if_error: true + static-code-analysis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install -r requirements-lint.txt + - name: mypy 3.7 + continue-on-error: true + run: | + mypy --python-version 3.7 . + - name: mypy 3.8 + continue-on-error: true + run: | + mypy --python-version 3.8 . + - name: mypy 3.9 + continue-on-error: true + run: | + mypy --python-version 3.9 . + - name: mypy 3.10 + continue-on-error: true + run: | + mypy --python-version 3.10 . + - name: pylint + continue-on-error: true + run: | + pylint --rcfile=.pylintrc \ + can/**.py \ + setup.py \ + doc.conf \ + scripts/**.py \ + examples/**.py + format: runs-on: ubuntu-latest steps: diff --git a/.travis.yml b/.travis.yml index 77eef3699..fb24f8e9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,23 +74,6 @@ jobs: # -a Write all files # -n nitpicky - python -m sphinx -an doc build - - stage: linter - name: "Linter Checks" - python: "3.9" - before_install: - - travis_retry pip install -r requirements-lint.txt - script: - # ------------- - # pylint checking: - # check the entire main codebase (except the tests) - - pylint --rcfile=.pylintrc can/**.py setup.py doc.conf scripts/**.py examples/**.py - # ------------- - # mypy checking: - - mypy - can/*.py - can/io/**.py - scripts/**.py - examples/**.py - stage: deploy name: "PyPi Deployment" python: "3.9" diff --git a/requirements-lint.txt b/requirements-lint.txt index 55d985d54..e9ad9106c 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -2,3 +2,4 @@ pylint==2.12.2 black==21.12b0 mypy==0.931 mypy-extensions==0.4.3 +types-setuptools diff --git a/setup.cfg b/setup.cfg index 84d734573..48a688026 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,6 @@ license_file = LICENSE.txt [mypy] -python_version = 3.7 warn_return_any = True warn_unused_configs = True ignore_missing_imports = True @@ -10,3 +9,4 @@ no_implicit_optional = True disallow_incomplete_defs = True warn_redundant_casts = True warn_unused_ignores = True +exclude = (^venv|^test|^can/interfaces|^setup.py$) \ No newline at end of file From bc14b95ce354025e3aed7a8b4c6e3e668a91ceb1 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:35:10 +0100 Subject: [PATCH 0831/1235] move continue-on-error --- .github/workflows/build.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 21eec2048..1f57103a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,6 +41,7 @@ jobs: static-code-analysis: runs-on: ubuntu-latest + continue-on-error: true steps: - uses: actions/checkout@v2 - name: Set up Python @@ -53,23 +54,18 @@ jobs: pip install -e . pip install -r requirements-lint.txt - name: mypy 3.7 - continue-on-error: true run: | mypy --python-version 3.7 . - name: mypy 3.8 - continue-on-error: true run: | mypy --python-version 3.8 . - name: mypy 3.9 - continue-on-error: true run: | mypy --python-version 3.9 . - name: mypy 3.10 - continue-on-error: true run: | mypy --python-version 3.10 . - name: pylint - continue-on-error: true run: | pylint --rcfile=.pylintrc \ can/**.py \ From c0da4c857b796178807531e4ea1c1887a03281bb Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 20 Jan 2022 16:43:26 +0100 Subject: [PATCH 0832/1235] make mypy happy --- can/ctypesutil.py | 8 ++++---- can/exceptions.py | 10 +++++++--- can/io/asc.py | 17 +++++++++-------- can/io/blf.py | 6 +++++- can/io/canutils.py | 4 +++- can/io/csv.py | 3 +++ can/io/generic.py | 9 +++++---- can/io/logger.py | 7 +++++-- can/io/printer.py | 7 ++++--- can/typechecking.py | 2 +- 10 files changed, 46 insertions(+), 27 deletions(-) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 7c1e1f573..4cfebb5b8 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -1,7 +1,7 @@ +# type: ignore """ This module contains common `ctypes` utils. """ - import ctypes import logging import sys @@ -14,14 +14,14 @@ try: - _LibBase = ctypes.WinDLL # type: ignore - _FUNCTION_TYPE = ctypes.WINFUNCTYPE # type: ignore + _LibBase = ctypes.WinDLL + _FUNCTION_TYPE = ctypes.WINFUNCTYPE except AttributeError: _LibBase = ctypes.CDLL _FUNCTION_TYPE = ctypes.CFUNCTYPE -class CLibrary(_LibBase): # type: ignore +class CLibrary(_LibBase): def __init__(self, library_or_path: Union[str, ctypes.CDLL]) -> None: self.func_name: Any diff --git a/can/exceptions.py b/can/exceptions.py index e8731737c..aec0dfd1d 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -14,13 +14,17 @@ For example, validating typical arguments and parameters might result in a :class:`ValueError`. This should always be documented for the function at hand. """ - - +import sys from contextlib import contextmanager from typing import Optional from typing import Type +if sys.version_info >= (3, 9): + from collections.abc import Generator +else: + from typing import Generator + class CanError(Exception): """Base class for all CAN related exceptions. @@ -106,7 +110,7 @@ class CanTimeoutError(CanError, TimeoutError): def error_check( error_message: Optional[str] = None, exception_type: Type[CanError] = CanOperationError, -) -> None: +) -> Generator[None, None, None]: """Catches any exceptions and turns them into the new type while preserving the stack trace.""" try: yield diff --git a/can/io/asc.py b/can/io/asc.py index 4e78d7528..74f630f43 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -6,7 +6,7 @@ - under `test/data/logfile.asc` """ import gzip -from typing import cast, Any, Generator, IO, List, Optional, Dict, Union +from typing import cast, Any, Generator, IO, List, Optional, Dict, Union, TextIO from datetime import datetime import time @@ -34,6 +34,8 @@ class ASCReader(BaseIOHandler): bus statistics, J1939 Transport Protocol messages) is ignored. """ + file: TextIO + FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" def __init__( @@ -205,8 +207,6 @@ def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Messag return Message(**msg_kwargs) def __iter__(self) -> Generator[Message, None, None]: - # This is guaranteed to not be None since we raise ValueError in __init__ - self.file = cast(IO[Any], self.file) self._extract_header() for line in self.file: @@ -214,10 +214,11 @@ def __iter__(self) -> Generator[Message, None, None]: if not temp or not temp[0].isdigit(): # Could be a comment continue - msg_kwargs = {} + + msg_kwargs: Dict[str, Union[float, bool, int]] = {} try: - timestamp, channel, rest_of_message = temp.split(None, 2) - timestamp = float(timestamp) + self.start_time + _timestamp, channel, rest_of_message = temp.split(None, 2) + timestamp = float(_timestamp) + self.start_time msg_kwargs["timestamp"] = timestamp if channel == "CANFD": msg_kwargs["is_fd"] = True @@ -250,6 +251,8 @@ class ASCWriter(FileIOMessageWriter, Listener): It the first message does not have a timestamp, it is set to zero. """ + file: TextIO + FORMAT_MESSAGE = "{channel} {id:<15} {dir:<4} {dtype} {data}" FORMAT_MESSAGE_FD = " ".join( [ @@ -319,8 +322,6 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: if not message: # if empty or None logger.debug("ASCWriter: ignoring empty message") return - # This is guaranteed to not be None since we raise ValueError in __init__ - self.file = cast(IO[Any], self.file) # this is the case for the very first message: if not self.header_written: diff --git a/can/io/blf.py b/can/io/blf.py index 9bb54d984..346d66cf6 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -17,7 +17,7 @@ import datetime import time import logging -from typing import List +from typing import List, BinaryIO from ..message import Message from ..listener import Listener @@ -139,6 +139,8 @@ class BLFReader(BaseIOHandler): silently ignored. """ + file: BinaryIO + def __init__(self, file: AcceptedIOType) -> None: """ :param file: a path-like object or as file-like object to read from @@ -352,6 +354,8 @@ class BLFWriter(FileIOMessageWriter, Listener): Logs CAN data to a Binary Logging File compatible with Vector's tools. """ + file: BinaryIO + #: Max log container size of uncompressed data max_container_size = 128 * 1024 diff --git a/can/io/canutils.py b/can/io/canutils.py index 5aa4ad53b..d3e122ae5 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -114,7 +114,9 @@ class CanutilsLogWriter(FileIOMessageWriter, Listener): It the first message does not have a timestamp, it is set to zero. """ - def __init__(self, file: AcceptedIOType, channel="vcan0", append=False): + def __init__( + self, file: AcceptedIOType, channel: str = "vcan0", append: bool = False + ): """ :param file: a path-like object or as file-like object to write to If this is a file-like object, is has to opened in text diff --git a/can/io/csv.py b/can/io/csv.py index 6e39e0096..fa3175a2d 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -10,6 +10,7 @@ """ from base64 import b64encode, b64decode +from typing import TextIO from can.message import Message from can.listener import Listener @@ -82,6 +83,8 @@ class CSVWriter(FileIOMessageWriter, Listener): Each line is terminated with a platform specific line separator. """ + file: TextIO + def __init__(self, file: AcceptedIOType, append: bool = False) -> None: """ :param file: a path-like object or a file-like object to write to. diff --git a/can/io/generic.py b/can/io/generic.py index 1606c444d..96ff91abf 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -45,7 +45,10 @@ def __init__( else: # pylint: disable=consider-using-with # file is some path-like object - self.file = open(cast(can.typechecking.StringPathLike, file), mode) + self.file = cast( + can.typechecking.FileLike, + open(cast(can.typechecking.StringPathLike, file), mode), + ) # for multiple inheritance super().__init__() @@ -80,9 +83,7 @@ class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): file: Union[TextIO, BinaryIO] - def __init__( - self, file: Union[can.typechecking.FileLike, TextIO, BinaryIO], mode: str = "rt" - ) -> None: + def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None: # Not possible with the type signature, but be verbose for user friendliness if file is None: raise ValueError("The given file cannot be None") diff --git a/can/io/logger.py b/can/io/logger.py index 3890e7432..6c3933294 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -15,7 +15,7 @@ ) from types import TracebackType - +from typing_extensions import Literal from pkg_resources import iter_entry_points from ..message import Message @@ -95,6 +95,9 @@ def __new__( # type: ignore f'No write support for this unknown log format "{suffix}"' ) from None + def on_message_received(self, msg: Message) -> None: + pass + class BaseRotatingLogger(Listener, BaseIOHandler, ABC): """ @@ -232,7 +235,7 @@ def __exit__( exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], - ) -> bool: + ) -> Literal[False]: return self._writer.__exit__(exc_type, exc_val, exc_tb) @abstractmethod diff --git a/can/io/printer.py b/can/io/printer.py index ec003e0eb..09c86f81f 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -4,7 +4,7 @@ import logging -from typing import Optional +from typing import Optional, cast, TextIO from ..message import Message from ..listener import Listener @@ -24,6 +24,8 @@ class Printer(BaseIOHandler, Listener): standard out """ + file: Optional[TextIO] + def __init__( self, file: Optional[AcceptedIOType] = None, append: bool = False ) -> None: @@ -35,12 +37,11 @@ def __init__( :param append: If set to `True` messages, are appended to the file, else the file is truncated """ - self.write_to_file = file is not None mode = "a" if append else "w" super().__init__(file, mode=mode) def on_message_received(self, msg: Message) -> None: - if self.write_to_file: + if self.file is not None: self.file.write(str(msg) + "\n") else: print(msg) diff --git a/can/typechecking.py b/can/typechecking.py index 627e1a86a..3e3cca833 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -27,7 +27,7 @@ Channel = typing.Union[ChannelInt, ChannelStr] # Used by the IO module -FileLike = typing.IO[typing.Any] +FileLike = typing.Union[typing.TextIO, typing.BinaryIO] StringPathLike = typing.Union[str, "os.PathLike[str]"] AcceptedIOType = typing.Union[FileLike, StringPathLike] From b75e214d6d266566094e4164b8294f06a6b949a4 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 20 Jan 2022 16:54:26 +0100 Subject: [PATCH 0833/1235] add newline at end --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 48a688026..068badd4c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,4 +9,4 @@ no_implicit_optional = True disallow_incomplete_defs = True warn_redundant_casts = True warn_unused_ignores = True -exclude = (^venv|^test|^can/interfaces|^setup.py$) \ No newline at end of file +exclude = (^venv|^test|^can/interfaces|^setup.py$) From 53e5e116177b3a4751d900fc4a6a1170fea20163 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 21 Jan 2022 10:15:06 +0100 Subject: [PATCH 0834/1235] Add Github Actions badge --- README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 84c270e0f..5eec029b2 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ python-can |release| |python_implementation| |downloads| |downloads_monthly| |formatter| -|docs| |build_travis| |coverage| |mergify| +|docs| |github-actions| |build_travis| |coverage| |mergify| .. |release| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ @@ -29,6 +29,10 @@ python-can :target: https://python-can.readthedocs.io/en/stable/ :alt: Documentation +.. |github-actions| image:: https://github.com/hardbyte/python-can/actions/workflows/build.yml/badge.svg?branch=develop + :target: https://github.com/hardbyte/python-can/actions/workflows/build.yml + :alt: Github Actions workflow status + .. |build_travis| image:: https://img.shields.io/travis/com/hardbyte/python-can/develop.svg?label=Travis%20CI :target: https://travis-ci.com/hardbyte/python-can :alt: Travis CI Server for develop branch From b66ea80d567892c7475192af9d51c71d35b39efd Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 21 Jan 2022 10:27:11 +0100 Subject: [PATCH 0835/1235] remove continue-on-error so workflow fails --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f57103a5..993247868 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,6 @@ jobs: static-code-analysis: runs-on: ubuntu-latest - continue-on-error: true steps: - uses: actions/checkout@v2 - name: Set up Python From 88cb3bbe38ffd6a989bde207f1aaeedc49f8090a Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 21 Jan 2022 10:38:17 +0100 Subject: [PATCH 0836/1235] improve ASCReader robustness --- can/io/asc.py | 19 ++++++++++++------- test/data/logfile.asc | 1 + test/logformats_test.py | 3 +++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 74f630f43..5e59483c5 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -6,7 +6,8 @@ - under `test/data/logfile.asc` """ import gzip -from typing import cast, Any, Generator, IO, List, Optional, Dict, Union, TextIO +import re +from typing import Any, Generator, List, Optional, Dict, Union, TextIO from datetime import datetime import time @@ -85,8 +86,8 @@ def _extract_header(self): self.timestamps_format = timestamp_format elif lower_case.endswith("internal events logged"): self.internal_events_logged = not lower_case.startswith("no") - elif lower_case.startswith("// version"): - # the test files include `// version 9.0.0` - not sure what this is + elif lower_case.startswith("//"): + # ignore comments continue # grab absolute timestamp elif lower_case.startswith("begin triggerblock"): @@ -210,14 +211,18 @@ def __iter__(self) -> Generator[Message, None, None]: self._extract_header() for line in self.file: - temp = line.strip() - if not temp or not temp[0].isdigit(): - # Could be a comment + line = line.strip() + + if not re.match( + r"\d+\.\d+\s+(\d+\s+(\w+\s+(Tx|Rx)|ErrorFrame)|CANFD)", + line, + re.ASCII | re.IGNORECASE, + ): continue msg_kwargs: Dict[str, Union[float, bool, int]] = {} try: - _timestamp, channel, rest_of_message = temp.split(None, 2) + _timestamp, channel, rest_of_message = line.split(None, 2) timestamp = float(_timestamp) + self.start_time msg_kwargs["timestamp"] = timestamp if channel == "CANFD": diff --git a/test/data/logfile.asc b/test/data/logfile.asc index 77ebdb78a..8e6ac0464 100644 --- a/test/data/logfile.asc +++ b/test/data/logfile.asc @@ -2,6 +2,7 @@ date Sam Sep 30 15:06:13.191 2017 base hex timestamps absolute internal events logged // version 9.0.0 +//0.000000 previous log file: logfile_errorframes.asc Begin Triggerblock Sam Sep 30 15:06:13.191 2017 0.000000 Start of measurement 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0 diff --git a/test/logformats_test.py b/test/logformats_test.py index 400bf369d..a7417fa5d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -554,6 +554,9 @@ def test_can_and_canfd_error_frames(self): actual = self._read_log_file("test_CanErrorFrames.asc") self.assertMessagesEqual(actual, expected_messages) + def test_ignore_comments(self): + _msg_list = self._read_log_file("logfile.asc") + class TestGzipASCFileFormat(ReaderWriterTest): """Tests can.GzipASCWriter and can.GzipASCReader""" From 2bc44be4da22089ed779c8c3c9990516bacf54ef Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 21 Jan 2022 11:39:13 +0100 Subject: [PATCH 0837/1235] add comment --- can/io/asc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/io/asc.py b/can/io/asc.py index 5e59483c5..db8f66358 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -218,6 +218,8 @@ def __iter__(self) -> Generator[Message, None, None]: line, re.ASCII | re.IGNORECASE, ): + # line might be a comment, chip status, + # J1939 message or some other unsupported event continue msg_kwargs: Dict[str, Union[float, bool, int]] = {} From 57bc62c58f2e8968c84ecfb33ae1c8e8fa87070d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 14 Jan 2022 16:03:35 +0100 Subject: [PATCH 0838/1235] change DLC to DL --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index 854d211a1..87cb6a199 100644 --- a/can/message.py +++ b/can/message.py @@ -130,7 +130,7 @@ def __str__(self) -> str: field_strings.append(flag_string) - field_strings.append(f"DLC: {self.dlc:2d}") + field_strings.append(f"DL: {self.dlc:2d}") data_strings = [] if self.data is not None: for index in range(0, min(self.dlc, len(self.data))): From 30c421a625436c7c7e07949bec9633eddb6c5dbb Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 21 Jan 2022 12:56:36 +0100 Subject: [PATCH 0839/1235] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da451f157..d1428075d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,6 +138,7 @@ Other API changes and improvements * Speed up interface plugin imports by removing pkg_resources (#1110) * Allowing for extra config arguments in can.logger (#1142, #1170) * Add changed byte highlighting to viewer.py (#1159) +* Change DLC to DL in Message.\_\_str\_\_() (#1212) Other Bugfixes -------------- From 2185f27977e19b9914b7b49f3ee620b2cd6f011e Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sun, 23 Jan 2022 09:20:24 +0100 Subject: [PATCH 0840/1235] Moderinze README code --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 5eec029b2..956033de1 100644 --- a/README.rst +++ b/README.rst @@ -90,7 +90,7 @@ Example usage import can # create a bus instance - # many other interfaces are supported as well (see below) + # many other interfaces are supported as well (see documentation) bus = can.Bus(interface='socketcan', channel='vcan0', receive_own_messages=True) @@ -102,7 +102,7 @@ Example usage # iterate over received messages for msg in bus: - print("{:X}: {}".format(msg.arbitration_id, msg.data)) + print(f"{msg.arbitration_id:X}: {msg.data}") # or use an asynchronous notifier notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()]) From 5088ec2d07f13894b67c327be6b5d4b4ed0c4f17 Mon Sep 17 00:00:00 2001 From: TJ Date: Mon, 17 Jan 2022 18:14:56 -0800 Subject: [PATCH 0841/1235] Add unzip method to logreader --- can/io/player.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/can/io/player.py b/can/io/player.py index f710e15d5..6712089dd 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -3,7 +3,7 @@ well as :class:`MessageSync` which plays back messages in the recorded order an time intervals. """ - +import gzip import pathlib from time import time, sleep import typing @@ -14,7 +14,7 @@ import can from .generic import BaseIOHandler, MessageReader -from .asc import ASCReader, GzipASCReader +from .asc import ASCReader from .blf import BLFReader from .canutils import CanutilsLogReader from .csv import CSVReader @@ -27,12 +27,13 @@ class LogReader(BaseIOHandler): The format is determined from the file format which can be one of: * .asc - * .asc.gz * .blf * .csv * .db * .log + Or any of the above compressed using gzip (.gz) + Exposes a simple iterator interface, to use simply: >>> for msg in LogReader("some/path/to/my_file.log"): @@ -50,7 +51,6 @@ class LogReader(BaseIOHandler): fetched_plugins = False message_readers = { ".asc": ASCReader, - ".asc.gz": GzipASCReader, ".blf": BLFReader, ".csv": CSVReader, ".db": SqliteReader, @@ -77,7 +77,11 @@ def __new__( # type: ignore ) LogReader.fetched_plugins = True - suffix = "".join(s.lower() for s in pathlib.PurePath(filename).suffixes) + suffix = pathlib.PurePath(filename).suffix.lower() + + if suffix == ".gz": + suffix, filename = LogReader.unzip(filename) + try: return typing.cast( MessageReader, @@ -88,6 +92,23 @@ def __new__( # type: ignore f'No read support for this unknown log format "{suffix}"' ) from None + @staticmethod + def unzip(zipfile: "can.typechecking.StringPathLike"): + """ + Return the suffix and io object of the decompressed file. + """ + real_suffix = pathlib.Path(zipfile).suffixes[-2].lower() + file = gzip.open(zipfile, "rt") + + # Re-open in binary mode if file not readable. + try: + file.read() + file.seek(0) + except UnicodeDecodeError: + file = gzip.open(zipfile, "rb") + + return real_suffix, file + class MessageSync: # pylint: disable=too-few-public-methods """ From 7e59007fb8e3aa9d07bec105ad0a7b42e9093f9f Mon Sep 17 00:00:00 2001 From: Teejay Date: Thu, 20 Jan 2022 20:16:54 -0800 Subject: [PATCH 0842/1235] Apply PR feedback Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> Rename method to decompress Add protection against memory error --- can/io/player.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/can/io/player.py b/can/io/player.py index 6712089dd..96d3a5b7c 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -80,7 +80,7 @@ def __new__( # type: ignore suffix = pathlib.PurePath(filename).suffix.lower() if suffix == ".gz": - suffix, filename = LogReader.unzip(filename) + suffix, filename = LogReader.decompress(filename) try: return typing.cast( @@ -93,21 +93,16 @@ def __new__( # type: ignore ) from None @staticmethod - def unzip(zipfile: "can.typechecking.StringPathLike"): + def decompress( + filename: "can.typechecking.StringPathLike", + ) -> typing.Tuple[str, typing.IO[typing.Any]]: """ Return the suffix and io object of the decompressed file. """ - real_suffix = pathlib.Path(zipfile).suffixes[-2].lower() - file = gzip.open(zipfile, "rt") - - # Re-open in binary mode if file not readable. - try: - file.read() - file.seek(0) - except UnicodeDecodeError: - file = gzip.open(zipfile, "rb") + real_suffix = pathlib.Path(filename).suffixes[-2].lower() + mode = "rb" if real_suffix == ".blf" else "rt" - return real_suffix, file + return real_suffix, gzip.open(filename, mode) class MessageSync: # pylint: disable=too-few-public-methods From 56c579159b07cca7b3da203141ff2c65876ab759 Mon Sep 17 00:00:00 2001 From: TJ Date: Thu, 20 Jan 2022 20:59:09 -0800 Subject: [PATCH 0843/1235] Add player unittest --- test/data/test_CanMessage.asc.gz | Bin 0 -> 277 bytes test/test_player.py | 14 +++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 test/data/test_CanMessage.asc.gz diff --git a/test/data/test_CanMessage.asc.gz b/test/data/test_CanMessage.asc.gz new file mode 100644 index 0000000000000000000000000000000000000000..25375d1b80fcf39afe18534e6fd792b4ee4cc2e0 GIT binary patch literal 277 zcmV+w0qXuAiwFpT3*}(|19W9`bYDYZZcSx#b75y?E@5+H09}vWYQr!LM(^_!-xH*A z{!{2pvJD0sjHNH&ELs|t*u{3r9)HTlN;hGIuyi=mpaY}R3pzd{C8&H)#a^YcsudDa zWz=iWIPoVCriFdb%h^Ns-p*^_XflDF(KGnMCV0$t9C?U#J6zcL$r{u##S}F>P6kIe zN!#_aeftn=xLWDP`ttlE1|Z~jpbOds*mo?f{pxrT*){+qaZZYmNZD4njiaoL0TrkQ zNiIPJCy;O#g>3Qf;fc){PB None: # Patch VirtualBus object patcher_virtual_bus = mock.patch("can.interfaces.virtual.VirtualBus", spec=True) @@ -29,9 +32,6 @@ def setUp(self) -> None: self.addCleanup(patcher_sleep.stop) self.baseargs = [sys.argv[0], "-i", "virtual"] - self.logfile = os.path.join( - os.path.dirname(__file__), "data", "test_CanMessage.asc" - ) def assertSuccessfulCleanup(self): self.MockVirtualBus.assert_called_once() @@ -111,5 +111,13 @@ def test_play_error_frame(self): self.assertSuccessfulCleanup() +class TestPlayerCompressedFile(TestPlayerScriptModule): + """ + Re-run tests using a compressed file. + """ + + logfile = os.path.join(os.path.dirname(__file__), "data", "test_CanMessage.asc.gz") + + if __name__ == "__main__": unittest.main() From 5e5ca2b75455ed3d1d5c9d8803ace069fb0886e6 Mon Sep 17 00:00:00 2001 From: TJ Date: Sat, 22 Jan 2022 12:25:26 -0800 Subject: [PATCH 0844/1235] Add compress method to logger --- can/io/logger.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 3890e7432..8dbd14f32 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -6,12 +6,15 @@ import pathlib from abc import ABC, abstractmethod from datetime import datetime +import gzip from typing import ( Any, Optional, Callable, cast, + IO, Type, + Tuple, ) from types import TracebackType @@ -21,7 +24,7 @@ from ..message import Message from ..listener import Listener from .generic import BaseIOHandler, FileIOMessageWriter -from .asc import ASCWriter, GzipASCWriter +from .asc import ASCWriter from .blf import BLFWriter from .canutils import CanutilsLogWriter from .csv import CSVWriter @@ -36,13 +39,14 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method The format is determined from the file format which can be one of: * .asc: :class:`can.ASCWriter` - * .asc.gz: :class:`can.CompressedASCWriter` * .blf :class:`can.BLFWriter` * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` * .log :class:`can.CanutilsLogWriter` * .txt :class:`can.Printer` + Or any of the above compressed using gzip (.gz) + 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. @@ -55,7 +59,6 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method fetched_plugins = False message_writers = { ".asc": ASCWriter, - ".asc.gz": GzipASCWriter, ".blf": BLFWriter, ".csv": CSVWriter, ".db": SqliteWriter, @@ -85,7 +88,11 @@ def __new__( # type: ignore ) Logger.fetched_plugins = True - suffix = "".join(s.lower() for s in pathlib.PurePath(filename).suffixes) + suffix = pathlib.PurePath(filename).suffix.lower() + + if suffix == ".gz": + suffix, filename = Logger.compress(filename) + try: return cast( Listener, Logger.message_writers[suffix](filename, *args, **kwargs) @@ -95,6 +102,17 @@ def __new__( # type: ignore f'No write support for this unknown log format "{suffix}"' ) from None + @staticmethod + def compress(filename: StringPathLike) -> Tuple[str, IO[Any]]: + """ + Return the suffix and io object of the decompressed file. + File will automatically recompress upon close. + """ + real_suffix = pathlib.Path(filename).suffixes[-2].lower() + mode = "ab" if real_suffix == ".blf" else "at" + + return real_suffix, gzip.open(filename, mode) + class BaseRotatingLogger(Listener, BaseIOHandler, ABC): """ From c56ff406531d9ef0d3a469a0be482d05784a82b7 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 25 Jan 2022 18:39:36 +0100 Subject: [PATCH 0845/1235] check vector with mypy --- can/interfaces/vector/canlib.py | 121 ++++++++++++++++-------------- can/interfaces/vector/xldriver.py | 1 + setup.cfg | 23 +++++- test/test_vector.py | 10 +-- 4 files changed, 92 insertions(+), 63 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 1313b00b7..4d81e8299 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,7 +10,8 @@ import logging import time import os -from typing import List, NamedTuple, Optional, Tuple, Sequence, Union +from types import ModuleType +from typing import List, NamedTuple, Optional, Tuple, Sequence, Union, Any, Dict try: # Try builtin Python 3 Windows API @@ -18,14 +19,7 @@ HAS_EVENTS = True except ImportError: - try: - # Try pywin32 package - from win32event import WaitForSingleObject, INFINITE - - HAS_EVENTS = True - except ImportError: - # Use polling instead - HAS_EVENTS = False + HAS_EVENTS = False # Import Modules # ============== @@ -36,7 +30,7 @@ deprecated_args_alias, time_perfcounter_correlation, ) -from can.typechecking import AutoDetectedConfig, CanFilters +from can.typechecking import AutoDetectedConfig, CanFilters, Channel # Define Module Logger # ==================== @@ -48,7 +42,7 @@ from . import xldefine, xlclass # Import safely Vector API module for Travis tests -xldriver = None +xldriver: Optional[ModuleType] = None try: from . import xldriver except Exception as exc: @@ -73,20 +67,20 @@ def __init__( channel: Union[int, Sequence[int], str], can_filters: Optional[CanFilters] = None, poll_interval: float = 0.01, - receive_own_messages: bool = False, - bitrate: int = None, + receive_own_messages: Optional[bool] = False, + bitrate: Optional[int] = None, rx_queue_size: int = 2 ** 14, - app_name: str = "CANalyzer", - serial: int = None, - fd: bool = False, - data_bitrate: int = None, + app_name: Optional[str] = "CANalyzer", + serial: Optional[int] = None, + fd: Optional[bool] = False, + data_bitrate: Optional[int] = None, sjw_abr: int = 2, tseg1_abr: int = 6, tseg2_abr: int = 3, sjw_dbr: int = 2, tseg1_dbr: int = 6, tseg2_dbr: int = 3, - **kwargs, + **kwargs: Any, ) -> None: """ :param channel: @@ -144,16 +138,18 @@ def __init__( if xldriver is None: raise CanInterfaceNotImplementedError("The Vector API has not been loaded") + self.xldriver = xldriver # keep reference so mypy knows it is not None self.poll_interval = poll_interval - if isinstance(channel, str): # must be checked before generic Sequence + self.channels: Sequence[int] + if isinstance(channel, int): + self.channels = [channel] + elif isinstance(channel, str): # must be checked before generic Sequence # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(",")] - elif isinstance(channel, int): - self.channels = [channel] elif isinstance(channel, Sequence): - self.channels = channel + self.channels = [int(ch) for ch in channel] else: raise TypeError( f"Invalid type for channels parameter: {type(channel).__name__}" @@ -185,12 +181,12 @@ def __init__( "None of the configured channels could be found on the specified hardware." ) - xldriver.xlOpenDriver() + self.xldriver.xlOpenDriver() self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE) self.mask = 0 self.fd = fd # Get channels masks - self.channel_masks = {} + self.channel_masks: Dict[Optional[Channel], int] = {} self.index_to_channel = {} for channel in self.channels: @@ -200,7 +196,7 @@ def __init__( app_name, channel ) LOG.debug("Channel index %d found", channel) - idx = xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) + idx = self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) if idx < 0: # Undocumented behavior! See issue #353. # If hardware is unavailable, this function returns -1. @@ -224,7 +220,7 @@ def __init__( if bitrate or fd: permission_mask.value = self.mask if fd: - xldriver.xlOpenPort( + self.xldriver.xlOpenPort( self.port_handle, self._app_name, self.mask, @@ -234,7 +230,7 @@ def __init__( xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, ) else: - xldriver.xlOpenPort( + self.xldriver.xlOpenPort( self.port_handle, self._app_name, self.mask, @@ -267,7 +263,7 @@ def __init__( self.canFdConf.tseg1Dbr = int(tseg1_dbr) self.canFdConf.tseg2Dbr = int(tseg2_dbr) - xldriver.xlCanFdSetConfiguration( + self.xldriver.xlCanFdSetConfiguration( self.port_handle, self.mask, self.canFdConf ) LOG.info( @@ -289,7 +285,7 @@ def __init__( ) else: if bitrate: - xldriver.xlCanSetChannelBitrate( + self.xldriver.xlCanSetChannelBitrate( self.port_handle, permission_mask, bitrate ) LOG.info("SetChannelBitrate: baudr.=%u", bitrate) @@ -298,16 +294,16 @@ def __init__( # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 - xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) + self.xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) if HAS_EVENTS: self.event_handle = xlclass.XLhandle() - xldriver.xlSetNotification(self.port_handle, self.event_handle, 1) + self.xldriver.xlSetNotification(self.port_handle, self.event_handle, 1) else: LOG.info("Install pywin32 to avoid polling") try: - xldriver.xlActivateChannel( + self.xldriver.xlActivateChannel( self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0 ) except VectorOperationError as error: @@ -320,17 +316,17 @@ def __init__( if time.get_clock_info("time").resolution > 1e-5: ts, perfcounter = time_perfcounter_correlation() try: - xldriver.xlGetSyncTime(self.port_handle, offset) + self.xldriver.xlGetSyncTime(self.port_handle, offset) except VectorInitializationError: - xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) + self.xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) current_perfcounter = time.perf_counter() now = ts + (current_perfcounter - perfcounter) self._time_offset = now - offset.value * 1e-9 else: try: - xldriver.xlGetSyncTime(self.port_handle, offset) + self.xldriver.xlGetSyncTime(self.port_handle, offset) except VectorInitializationError: - xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) + self.xldriver.xlGetChannelTime(self.port_handle, self.mask, offset) self._time_offset = time.time() - offset.value * 1e-9 except VectorInitializationError: @@ -339,7 +335,7 @@ def __init__( self._is_filtered = False super().__init__(channel=channel, can_filters=can_filters, **kwargs) - def _apply_filters(self, filters) -> None: + def _apply_filters(self, filters: Optional[CanFilters]) -> None: if filters: # Only up to one filter per ID type allowed if len(filters) == 1 or ( @@ -348,7 +344,7 @@ def _apply_filters(self, filters) -> None: ): try: for can_filter in filters: - xldriver.xlCanSetChannelAcceptance( + self.xldriver.xlCanSetChannelAcceptance( self.port_handle, self.mask, can_filter["can_id"], @@ -370,14 +366,14 @@ def _apply_filters(self, filters) -> None: # fallback: reset filters self._is_filtered = False try: - xldriver.xlCanSetChannelAcceptance( + self.xldriver.xlCanSetChannelAcceptance( self.port_handle, self.mask, 0x0, 0x0, xldefine.XL_AcceptanceFilter.XL_CAN_EXT, ) - xldriver.xlCanSetChannelAcceptance( + self.xldriver.xlCanSetChannelAcceptance( self.port_handle, self.mask, 0x0, @@ -417,14 +413,14 @@ def _recv_internal( else: time_left = end_time - time.time() time_left_ms = max(0, int(time_left * 1000)) - WaitForSingleObject(self.event_handle.value, time_left_ms) + WaitForSingleObject(self.event_handle.value, time_left_ms) # type: ignore else: # Wait a short time until we try again time.sleep(self.poll_interval) def _recv_canfd(self) -> Optional[Message]: xl_can_rx_event = xlclass.XLcanRxEvent() - xldriver.xlCanReceive(self.port_handle, xl_can_rx_event) + self.xldriver.xlCanReceive(self.port_handle, xl_can_rx_event) if xl_can_rx_event.tag == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK: is_rx = True @@ -470,7 +466,7 @@ def _recv_canfd(self) -> Optional[Message]: def _recv_can(self) -> Optional[Message]: xl_event = xlclass.XLevent() event_count = ctypes.c_uint(1) - xldriver.xlReceive(self.port_handle, event_count, xl_event) + self.xldriver.xlReceive(self.port_handle, event_count, xl_event) if xl_event.tag != xldefine.XL_EventTags.XL_RECEIVE_MSG: self.handle_can_event(xl_event) @@ -523,7 +519,7 @@ def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: `XL_CAN_EV_TAG_TX_ERROR`, `XL_TIMER` or `XL_CAN_EV_TAG_CHIP_STATE` tag. """ - def send(self, msg: Message, timeout: Optional[float] = None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: self._send_sequence([msg]) def _send_sequence(self, msgs: Sequence[Message]) -> int: @@ -548,7 +544,9 @@ def _send_can_msg_sequence(self, msgs: Sequence[Message]) -> int: *map(self._build_xl_event, msgs) ) - xldriver.xlCanTransmit(self.port_handle, mask, message_count, xl_event_array) + self.xldriver.xlCanTransmit( + self.port_handle, mask, message_count, xl_event_array + ) return message_count.value @staticmethod @@ -580,7 +578,7 @@ def _send_can_fd_msg_sequence(self, msgs: Sequence[Message]) -> int: ) msg_count_sent = ctypes.c_uint(0) - xldriver.xlCanTransmitEx( + self.xldriver.xlCanTransmitEx( self.port_handle, mask, message_count, msg_count_sent, xl_can_tx_event_array ) return msg_count_sent.value @@ -611,17 +609,17 @@ def _build_xl_can_tx_event(msg: Message) -> xlclass.XLcanTxEvent: return xl_can_tx_event def flush_tx_buffer(self) -> None: - xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask) + self.xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask) def shutdown(self) -> None: super().shutdown() - xldriver.xlDeactivateChannel(self.port_handle, self.mask) - xldriver.xlClosePort(self.port_handle) - xldriver.xlCloseDriver() + self.xldriver.xlDeactivateChannel(self.port_handle, self.mask) + self.xldriver.xlClosePort(self.port_handle) + self.xldriver.xlCloseDriver() def reset(self) -> None: - xldriver.xlDeactivateChannel(self.port_handle, self.mask) - xldriver.xlActivateChannel( + self.xldriver.xlDeactivateChannel(self.port_handle, self.mask) + self.xldriver.xlActivateChannel( self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0 ) @@ -657,7 +655,7 @@ def _detect_available_configs() -> List[AutoDetectedConfig]: "vector_channel_config": channel_config, } ) - return configs + return configs # type: ignore @staticmethod def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: @@ -666,6 +664,9 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: :param wait_for_finish: Time to wait for user input in milliseconds. """ + if xldriver is None: + raise CanInterfaceNotImplementedError("The Vector API has not been loaded") + xldriver.xlPopupHwConfig(ctypes.c_char_p(), ctypes.c_uint(wait_for_finish)) @staticmethod @@ -685,14 +686,17 @@ def get_application_config( :raises can.interfaces.vector.VectorInitializationError: If the application name does not exist in the Vector hardware configuration. """ + if xldriver is None: + raise CanInterfaceNotImplementedError("The Vector API has not been loaded") + hw_type = ctypes.c_uint() hw_index = ctypes.c_uint() hw_channel = ctypes.c_uint() - app_channel = ctypes.c_uint(app_channel) + _app_channel = ctypes.c_uint(app_channel) xldriver.xlGetApplConfig( app_name.encode(), - app_channel, + _app_channel, hw_type, hw_index, hw_channel, @@ -707,7 +711,7 @@ def set_application_config( hw_type: xldefine.XL_HardwareType, hw_index: int, hw_channel: int, - **kwargs, + **kwargs: Any, ) -> None: """Modify the application settings in Vector Hardware Configuration. @@ -737,6 +741,9 @@ def set_application_config( :raises can.interfaces.vector.VectorInitializationError: If the application name does not exist in the Vector hardware configuration. """ + if xldriver is None: + raise CanInterfaceNotImplementedError("The Vector API has not been loaded") + xldriver.xlSetApplConfig( app_name.encode(), app_channel, @@ -758,7 +765,7 @@ def set_timer_rate(self, timer_rate_ms: int) -> None: the timer events. """ timer_rate_10us = timer_rate_ms * 100 - xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) + self.xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) class VectorChannelConfig(NamedTuple): diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index db57d5911..3243fa4a0 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -1,3 +1,4 @@ +# type: ignore """ Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. diff --git a/setup.cfg b/setup.cfg index 068badd4c..7df42541f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,4 +9,25 @@ no_implicit_optional = True disallow_incomplete_defs = True warn_redundant_casts = True warn_unused_ignores = True -exclude = (^venv|^test|^can/interfaces|^setup.py$) +exclude = + (?x)( + venv + |^test + |^setup.py$ + |^can/interfaces/etas + |^can/interfaces/gs_usb + |^can/interfaces/ics_neovi + |^can/interfaces/iscan + |^can/interfaces/ixxat + |^can/interfaces/kvaser + |^can/interfaces/nican + |^can/interfaces/neousys + |^can/interfaces/pcan + |^can/interfaces/serial + |^can/interfaces/slcan + |^can/interfaces/socketcan + |^can/interfaces/systec + |^can/interfaces/udp_multicast + |^can/interfaces/usb2can + |^can/interfaces/virtual + ) diff --git a/test/test_vector.py b/test/test_vector.py index b1626b18c..800b8614c 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -309,7 +309,7 @@ def test_vector_error_pickle(self) -> None: with pytest.raises(error_type): raise exc_unpickled - def test_vector_subteype_error_from_generic(self) -> None: + def test_vector_subtype_error_from_generic(self) -> None: for error_type in [VectorInitializationError, VectorOperationError]: with self.subTest(f"error_type = {error_type.__name__}"): @@ -320,13 +320,13 @@ def test_vector_subteype_error_from_generic(self) -> None: generic = VectorError(error_code, error_string, function) # pickle and unpickle - specififc: VectorError = error_type.from_generic(generic) + specific: VectorError = error_type.from_generic(generic) - self.assertEqual(str(generic), str(specififc)) - self.assertEqual(error_code, specififc.error_code) + self.assertEqual(str(generic), str(specific)) + self.assertEqual(error_code, specific.error_code) with pytest.raises(error_type): - raise specififc + raise specific class TestVectorChannelConfig: From 473939121442f88daf8c48abffe4c13d92004a33 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 25 Jan 2022 23:02:01 +0100 Subject: [PATCH 0846/1235] add test and correct type annotations --- can/interfaces/vector/canlib.py | 5 +++-- setup.cfg | 1 + test/test_vector.py | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 4d81e8299..c3bc5413b 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -19,6 +19,7 @@ HAS_EVENTS = True except ImportError: + WaitForSingleObject, INFINITE = None, None HAS_EVENTS = False # Import Modules @@ -67,12 +68,12 @@ def __init__( channel: Union[int, Sequence[int], str], can_filters: Optional[CanFilters] = None, poll_interval: float = 0.01, - receive_own_messages: Optional[bool] = False, + receive_own_messages: bool = False, bitrate: Optional[int] = None, rx_queue_size: int = 2 ** 14, app_name: Optional[str] = "CANalyzer", serial: Optional[int] = None, - fd: Optional[bool] = False, + fd: bool = False, data_bitrate: Optional[int] = None, sjw_abr: int = 2, tseg1_abr: int = 6, diff --git a/setup.cfg b/setup.cfg index 7df42541f..b402ee645 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,6 +14,7 @@ exclude = venv |^test |^setup.py$ + |^can/interfaces/__init__.py |^can/interfaces/etas |^can/interfaces/gs_usb |^can/interfaces/ics_neovi diff --git a/test/test_vector.py b/test/test_vector.py index 800b8614c..338783136 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -22,6 +22,7 @@ VectorOperationError, VectorChannelConfig, ) +from test.config import IS_WINDOWS class TestVectorBus(unittest.TestCase): @@ -328,6 +329,11 @@ def test_vector_subtype_error_from_generic(self) -> None: with pytest.raises(error_type): raise specific + @unittest.skipUnless(IS_WINDOWS, "Windows specific test") + def test_winapi_availability(self) -> None: + self.assertIsNotNone(canlib.WaitForSingleObject) + self.assertIsNotNone(canlib.INFINITE) + class TestVectorChannelConfig: def test_attributes(self): From 42348084febb6d9784b0772bf422ae56edf5af1f Mon Sep 17 00:00:00 2001 From: TJ Date: Sat, 22 Jan 2022 13:18:09 -0800 Subject: [PATCH 0847/1235] Add logger unittest --- test/test_logger.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/test_logger.py b/test/test_logger.py index a046b919f..6a4472f43 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -8,7 +8,9 @@ import unittest from unittest import mock from unittest.mock import Mock +import gzip import sys +import tempfile import can import can.logger @@ -105,5 +107,38 @@ def test_log_virtual_sizedlogger(self): self.mock_logger_sized.assert_called_once() +class TestLoggerCompressedFile(unittest.TestCase): + def setUp(self) -> None: + # Patch VirtualBus object + self.patcher_virtual_bus = mock.patch( + "can.interfaces.virtual.VirtualBus", spec=True + ) + self.MockVirtualBus = self.patcher_virtual_bus.start() + self.mock_virtual_bus = self.MockVirtualBus.return_value + + self.testmsg = can.Message( + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True + ) + + self.baseargs = [sys.argv[0], "-i", "virtual"] + + def test_compressed_logfile(self): + """ + Basic test to verify Logger is able to write gzip files. + """ + self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt]) + + with tempfile.NamedTemporaryFile(suffix=".log.gz", delete=True) as compressed: + sys.argv = self.baseargs + ["--file_name", compressed.name] + can.logger.main() + with gzip.open(compressed.name, "rt") as decompressed: + last_line = decompressed.readlines()[-1] + + self.assertEqual(last_line, "(0.000000) vcan0 00C0FFEE#0019000103010401\n") + + def tearDown(self) -> None: + self.patcher_virtual_bus.stop() + + if __name__ == "__main__": unittest.main() From 57a955e06ac1e6077d998003b57bc9274f8c7d35 Mon Sep 17 00:00:00 2001 From: TJ Date: Sun, 23 Jan 2022 22:14:27 -0800 Subject: [PATCH 0848/1235] Add multiple dot suffix variant --- test/logformats_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/logformats_test.py b/test/logformats_test.py index 400bf369d..669567e47 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -48,6 +48,7 @@ def test_extension_matching(self): suffix_variants = [ suffix.upper(), suffix.lower(), + f"can.msg.ext{suffix}", "".join([c.upper() if i % 2 else c for i, c in enumerate(suffix)]), ] for suffix_variant in suffix_variants: From a2b840d63f525bd375200beb43785d62b72c85f6 Mon Sep 17 00:00:00 2001 From: Tbruno25 Date: Wed, 26 Jan 2022 04:43:14 +0000 Subject: [PATCH 0849/1235] Format code with black --- can/io/logger.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 15e8bc0c5..8f2611794 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -102,7 +102,6 @@ def __new__( # type: ignore f'No write support for this unknown log format "{suffix}"' ) from None - @staticmethod def compress(filename: StringPathLike) -> Tuple[str, IO[Any]]: """ @@ -114,7 +113,6 @@ def compress(filename: StringPathLike) -> Tuple[str, IO[Any]]: return real_suffix, gzip.open(filename, mode) - def on_message_received(self, msg: Message) -> None: pass From ac1df031cfc9a7e597cd3c4fc504b001ef9df394 Mon Sep 17 00:00:00 2001 From: TJ Date: Tue, 25 Jan 2022 20:51:00 -0800 Subject: [PATCH 0850/1235] Remove gzip classes --- can/__init__.py | 2 +- can/io/__init__.py | 2 +- can/io/asc.py | 67 ---------------------------------------------- 3 files changed, 2 insertions(+), 69 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index c95b19ebf..618ef347f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -25,7 +25,7 @@ ) from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync -from .io import ASCWriter, ASCReader, GzipASCWriter, GzipASCReader +from .io import ASCWriter, ASCReader from .io import BLFReader, BLFWriter from .io import CanutilsLogReader, CanutilsLogWriter from .io import CSVWriter, CSVReader diff --git a/can/io/__init__.py b/can/io/__init__.py index 66e3a8c56..0d3741b05 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -8,7 +8,7 @@ from .player import LogReader, MessageSync # Format specific -from .asc import ASCWriter, ASCReader, GzipASCWriter, GzipASCReader +from .asc import ASCWriter, ASCReader from .blf import BLFReader, BLFWriter from .canutils import CanutilsLogReader, CanutilsLogWriter from .csv import CSVWriter, CSVReader diff --git a/can/io/asc.py b/can/io/asc.py index db8f66358..16eaaa178 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -5,7 +5,6 @@ - https://bitbucket.org/tobylorenz/vector_asc/src/47556e1a6d32c859224ca62d075e1efcc67fa690/src/Vector/ASC/tests/unittests/data/CAN_Log_Trigger_3_2.asc?at=master&fileviewer=file-view-default - under `test/data/logfile.asc` """ -import gzip import re from typing import Any, Generator, List, Optional, Dict, Union, TextIO @@ -405,69 +404,3 @@ def on_message_received(self, msg: Message) -> None: data=" ".join(data), ) self.log_event(serialized, msg.timestamp) - - -class GzipASCReader(ASCReader): - """Gzipped version of :class:`~can.ASCReader`""" - - def __init__( - self, - file: Union[typechecking.FileLike, typechecking.StringPathLike], - base: str = "hex", - relative_timestamp: bool = True, - ): - """ - :param file: a path-like object or as file-like object to read from - If this is a file-like object, is has to opened in text - read mode, not binary read mode. - :param base: Select the base(hex or dec) of id and data. - If the header of the asc file contains base information, - this value will be overwritten. Default "hex". - :param relative_timestamp: Select whether the timestamps are - `relative` (starting at 0.0) or `absolute` (starting at - the system time). Default `True = relative`. - """ - self._fileobj = None - if file is not None and (hasattr(file, "read") and hasattr(file, "write")): - # file is None or some file-like object - self._fileobj = file - super(GzipASCReader, self).__init__( - gzip.open(file, mode="rt"), base, relative_timestamp - ) - - def stop(self) -> None: - super(GzipASCReader, self).stop() - if self._fileobj is not None: - self._fileobj.close() - - -class GzipASCWriter(ASCWriter): - """Gzipped version of :class:`~can.ASCWriter`""" - - def __init__( - self, - file: Union[typechecking.FileLike, typechecking.StringPathLike], - channel: int = 1, - compresslevel: int = 6, - ): - """ - :param file: a path-like object or as file-like object to write to - If this is a file-like object, is has to opened in text - write mode, not binary write mode. - :param channel: a default channel to use when the message does not - have a channel set - :param compresslevel: Gzip compresslevel, see - :class:`~gzip.GzipFile` for details. The default is 6. - """ - self._fileobj = None - if file is not None and (hasattr(file, "read") and hasattr(file, "write")): - # file is None or some file-like object - self._fileobj = file - super(GzipASCWriter, self).__init__( - gzip.open(file, mode="wt", compresslevel=compresslevel), channel - ) - - def stop(self) -> None: - super(GzipASCWriter, self).stop() - if self._fileobj is not None: - self._fileobj.close() From 9b563bf9c69166ccd8a6a10639d2b1e5ad2e9ee4 Mon Sep 17 00:00:00 2001 From: TJ Date: Tue, 25 Jan 2022 21:13:28 -0800 Subject: [PATCH 0851/1235] Remove unittests --- test/logformats_test.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 3d4368a47..ffb7a75a3 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -11,7 +11,6 @@ TODO: correctly set preserves_channel and adds_default_channel """ -import gzip import logging import unittest import tempfile @@ -559,34 +558,6 @@ def test_ignore_comments(self): _msg_list = self._read_log_file("logfile.asc") -class TestGzipASCFileFormat(ReaderWriterTest): - """Tests can.GzipASCWriter and can.GzipASCReader""" - - def _setup_instance(self): - super()._setup_instance_helper( - can.GzipASCWriter, - can.GzipASCReader, - binary_file=True, - check_comments=True, - preserves_channel=False, - adds_default_channel=0, - ) - - def assertIncludesComments(self, filename): - """ - Ensures that all comments are literally contained in the given file. - - :param filename: the path-like object to use - """ - if self.original_comments: - # read the entire outout file - with gzip.open(filename, "rt" if self.binary_file else "r") as file: - output_contents = file.read() - # check each, if they can be found in there literally - for comment in self.original_comments: - self.assertIn(comment, output_contents) - - class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From caeab0e80a355c2eba7de9950b5371c8eab3be5d Mon Sep 17 00:00:00 2001 From: TJ Date: Tue, 25 Jan 2022 21:54:17 -0800 Subject: [PATCH 0852/1235] fix mypy errors --- can/io/logger.py | 13 +++---------- can/io/player.py | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 8f2611794..aff19f068 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -7,15 +7,8 @@ from abc import ABC, abstractmethod from datetime import datetime import gzip -from typing import ( - Any, - Optional, - Callable, - cast, - IO, - Type, - Tuple, -) +from typing import Any, Optional, Callable, TextIO, Type, Tuple, Union, cast + from types import TracebackType from typing_extensions import Literal @@ -103,7 +96,7 @@ def __new__( # type: ignore ) from None @staticmethod - def compress(filename: StringPathLike) -> Tuple[str, IO[Any]]: + def compress(filename: StringPathLike) -> Tuple[str, Union[str, Any]]: """ Return the suffix and io object of the decompressed file. File will automatically recompress upon close. diff --git a/can/io/player.py b/can/io/player.py index 96d3a5b7c..e9a700bc0 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -95,7 +95,7 @@ def __new__( # type: ignore @staticmethod def decompress( filename: "can.typechecking.StringPathLike", - ) -> typing.Tuple[str, typing.IO[typing.Any]]: + ) -> typing.Tuple[str, typing.Union[str, typing.Any]]: """ Return the suffix and io object of the decompressed file. """ From c546bd03d6e8e6de1fdc2d2beed8df49965519c9 Mon Sep 17 00:00:00 2001 From: TJ Date: Tue, 25 Jan 2022 22:28:37 -0800 Subject: [PATCH 0853/1235] edit unittest to work on windows --- test/test_logger.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/test_logger.py b/test/test_logger.py index bb3de8524..07cf17d37 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -8,8 +8,8 @@ from unittest import mock from unittest.mock import Mock import gzip +import os import sys -import tempfile import can import can.logger @@ -118,24 +118,25 @@ def setUp(self) -> None: self.testmsg = can.Message( arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True ) - self.baseargs = [sys.argv[0], "-i", "virtual"] + self.testfile = open("coffee.log.gz", "w+") + def test_compressed_logfile(self): """ Basic test to verify Logger is able to write gzip files. """ self.mock_virtual_bus.recv = Mock(side_effect=[self.testmsg, KeyboardInterrupt]) - - with tempfile.NamedTemporaryFile(suffix=".log.gz", delete=True) as compressed: - sys.argv = self.baseargs + ["--file_name", compressed.name] - can.logger.main() - with gzip.open(compressed.name, "rt") as decompressed: - last_line = decompressed.readlines()[-1] + sys.argv = self.baseargs + ["--file_name", self.testfile.name] + can.logger.main() + with gzip.open(self.testfile.name, "rt") as testlog: + last_line = testlog.readlines()[-1] self.assertEqual(last_line, "(0.000000) vcan0 00C0FFEE#0019000103010401\n") def tearDown(self) -> None: + self.testfile.close() + os.remove(self.testfile.name) self.patcher_virtual_bus.stop() From 50071cc3b94faeede9a2136000bff059391ee4a0 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 26 Jan 2022 09:12:07 +0100 Subject: [PATCH 0854/1235] add missing type annotation --- can/interfaces/vector/canlib.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index c3bc5413b..4a90ea1bc 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -11,8 +11,20 @@ import time import os from types import ModuleType -from typing import List, NamedTuple, Optional, Tuple, Sequence, Union, Any, Dict +from typing import ( + List, + NamedTuple, + Optional, + Tuple, + Sequence, + Union, + Any, + Dict, + Callable, +) +WaitForSingleObject: Optional[Callable[[int, int], int]] +INFINITE: Optional[int] try: # Try builtin Python 3 Windows API from _winapi import WaitForSingleObject, INFINITE From 72408e5b5a0875e0313c6457ff49e669cad4bfa6 Mon Sep 17 00:00:00 2001 From: Teejay Date: Wed, 26 Jan 2022 17:35:01 -0800 Subject: [PATCH 0855/1235] Update can/io/logger.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/io/logger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/io/logger.py b/can/io/logger.py index aff19f068..8d8026e72 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -38,7 +38,9 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method * .log :class:`can.CanutilsLogWriter` * .txt :class:`can.Printer` - Or any of the above compressed using gzip (.gz) + Any of these formats can be used with gzip compression by appending + the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not + be able to read these files. The **filename** may also be *None*, to fall back to :class:`can.Printer`. From 3bdd60a6cf71e7e2d704d12c9289eda884cf4013 Mon Sep 17 00:00:00 2001 From: Teejay Date: Wed, 26 Jan 2022 17:35:12 -0800 Subject: [PATCH 0856/1235] Update can/io/logger.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/io/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/logger.py b/can/io/logger.py index 8d8026e72..e9e1f7e77 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -30,7 +30,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method """ Logs CAN messages to a file. - The format is determined from the file format which can be one of: + The format is determined from the file suffix which can be one of: * .asc: :class:`can.ASCWriter` * .blf :class:`can.BLFWriter` * .csv: :class:`can.CSVWriter` From 9e1fe3ae6d518e44a835fabea54eea344bdb79c0 Mon Sep 17 00:00:00 2001 From: Teejay Date: Wed, 26 Jan 2022 17:35:27 -0800 Subject: [PATCH 0857/1235] Update can/io/player.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/io/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/player.py b/can/io/player.py index e9a700bc0..d68b4d236 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -25,7 +25,7 @@ class LogReader(BaseIOHandler): """ Replay logged CAN messages from a file. - The format is determined from the file format which can be one of: + The format is determined from the file suffix which can be one of: * .asc * .blf * .csv From fe740504817c534fd459e252b54af8113e99837c Mon Sep 17 00:00:00 2001 From: Tbruno25 Date: Thu, 27 Jan 2022 01:37:52 +0000 Subject: [PATCH 0858/1235] Format code with black --- can/io/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index e9e1f7e77..ec73a62ec 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -38,8 +38,8 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method * .log :class:`can.CanutilsLogWriter` * .txt :class:`can.Printer` - Any of these formats can be used with gzip compression by appending - the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not + Any of these formats can be used with gzip compression by appending + the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not be able to read these files. The **filename** may also be *None*, to fall back to :class:`can.Printer`. From bfc3283e65225524d54fa81527e7775716b2a3ac Mon Sep 17 00:00:00 2001 From: TJ Date: Wed, 26 Jan 2022 21:17:30 -0800 Subject: [PATCH 0859/1235] Update can/io/player.py --- can/io/player.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/io/player.py b/can/io/player.py index d68b4d236..dc60cf115 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -32,7 +32,9 @@ class LogReader(BaseIOHandler): * .db * .log - Or any of the above compressed using gzip (.gz) + Gzip compressed files can be used as long as the original + files suffix is one of the above (e.g. filename.asc.gz). + Exposes a simple iterator interface, to use simply: From 9f7842a909f3238a992a6e00b02271637cb191da Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 07:38:16 +0100 Subject: [PATCH 0860/1235] Remove unnssesary conversion to list --- can/interfaces/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 65a9cb859..a60f3ac2f 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -51,4 +51,4 @@ } ) -VALID_INTERFACES = frozenset(list(BACKENDS.keys())) +VALID_INTERFACES = frozenset(BACKENDS.keys()) From 55c5fdfdac7e19b6662301e3ed52058b9e466c56 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 27 Jan 2022 10:35:29 +0100 Subject: [PATCH 0861/1235] improve io typing --- can/__init__.py | 14 +++++------ can/io/asc.py | 14 +++++------ can/io/blf.py | 17 ++++++------- can/io/canutils.py | 60 ++++++++++++++++++++++++++------------------- can/io/csv.py | 21 +++++++++------- can/io/generic.py | 11 ++++----- can/io/logger.py | 21 ++++++++-------- can/io/player.py | 33 ++++++++++++------------- can/io/printer.py | 11 ++++----- can/io/sqlite.py | 20 ++++++++------- can/listener.py | 28 ++++++++++----------- can/typechecking.py | 4 +-- 12 files changed, 131 insertions(+), 123 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 618ef347f..f21ffcc09 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -24,13 +24,6 @@ CanTimeoutError, ) -from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync -from .io import ASCWriter, ASCReader -from .io import BLFReader, BLFWriter -from .io import CanutilsLogReader, CanutilsLogWriter -from .io import CSVWriter, CSVReader -from .io import SqliteWriter, SqliteReader - from .util import set_logging_level from .message import Message @@ -42,6 +35,13 @@ from .interface import Bus, detect_available_configs from .bit_timing import BitTiming +from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync +from .io import ASCWriter, ASCReader +from .io import BLFReader, BLFWriter +from .io import CanutilsLogReader, CanutilsLogWriter +from .io import CSVWriter, CSVReader +from .io import SqliteWriter, SqliteReader + from .broadcastmanager import ( CyclicSendTaskABC, LimitedDurationCyclicSendTaskABC, diff --git a/can/io/asc.py b/can/io/asc.py index 16eaaa178..f8607d061 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -12,12 +12,10 @@ import time import logging -from .. import typechecking from ..message import Message -from ..listener import Listener from ..util import channel2int -from .generic import BaseIOHandler, FileIOMessageWriter -from ..typechecking import AcceptedIOType +from .generic import FileIOMessageWriter, MessageReader +from ..typechecking import StringPathLike CAN_MSG_EXT = 0x80000000 @@ -28,7 +26,7 @@ logger = logging.getLogger("can.io.asc") -class ASCReader(BaseIOHandler): +class ASCReader(MessageReader): """ Iterator of CAN messages from a ASC logging file. Meta data (comments, bus statistics, J1939 Transport Protocol messages) is ignored. @@ -40,7 +38,7 @@ class ASCReader(BaseIOHandler): def __init__( self, - file: AcceptedIOType, + file: Union[StringPathLike, TextIO], base: str = "hex", relative_timestamp: bool = True, ) -> None: @@ -248,7 +246,7 @@ def __iter__(self) -> Generator[Message, None, None]: self.stop() -class ASCWriter(FileIOMessageWriter, Listener): +class ASCWriter(FileIOMessageWriter): """Logs CAN data to an ASCII log file (.asc). The measurement starts with the timestamp of the first registered message. @@ -287,7 +285,7 @@ class ASCWriter(FileIOMessageWriter, Listener): def __init__( self, - file: AcceptedIOType, + file: Union[StringPathLike, TextIO], channel: int = 1, ) -> None: """ diff --git a/can/io/blf.py b/can/io/blf.py index 346d66cf6..e7be8979f 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -17,13 +17,12 @@ import datetime import time import logging -from typing import List, BinaryIO +from typing import List, BinaryIO, Generator, Union from ..message import Message -from ..listener import Listener from ..util import len2dlc, dlc2len, channel2int -from ..typechecking import AcceptedIOType -from .generic import BaseIOHandler, FileIOMessageWriter +from ..typechecking import StringPathLike +from .generic import FileIOMessageWriter, MessageReader class BLFParseError(Exception): @@ -131,7 +130,7 @@ def systemtime_to_timestamp(systemtime): return 0 -class BLFReader(BaseIOHandler): +class BLFReader(MessageReader): """ Iterator of CAN messages from a Binary Logging File. @@ -141,7 +140,7 @@ class BLFReader(BaseIOHandler): file: BinaryIO - def __init__(self, file: AcceptedIOType) -> None: + def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in binary @@ -162,7 +161,7 @@ def __init__(self, file: AcceptedIOType) -> None: self._tail = b"" self._pos = 0 - def __iter__(self): + def __iter__(self) -> Generator[Message, None, None]: while True: data = self.file.read(OBJ_HEADER_BASE_STRUCT.size) if not data: @@ -349,7 +348,7 @@ def _parse_data(self, data): pos = next_pos -class BLFWriter(FileIOMessageWriter, Listener): +class BLFWriter(FileIOMessageWriter): """ Logs CAN data to a Binary Logging File compatible with Vector's tools. """ @@ -364,7 +363,7 @@ class BLFWriter(FileIOMessageWriter, Listener): def __init__( self, - file: AcceptedIOType, + file: Union[StringPathLike, BinaryIO], append: bool = False, channel: int = 1, compression_level: int = -1, diff --git a/can/io/canutils.py b/can/io/canutils.py index d3e122ae5..69793212c 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -5,11 +5,11 @@ """ import logging +from typing import Generator, TextIO, Union from can.message import Message -from can.listener import Listener -from .generic import BaseIOHandler, FileIOMessageWriter -from ..typechecking import AcceptedIOType +from .generic import FileIOMessageWriter, MessageReader +from ..typechecking import AcceptedIOType, StringPathLike log = logging.getLogger("can.io.canutils") @@ -22,7 +22,7 @@ CANFD_ESI = 0x02 -class CanutilsLogReader(BaseIOHandler): +class CanutilsLogReader(MessageReader): """ Iterator over CAN messages from a .log Logging File (candump -L). @@ -32,7 +32,9 @@ class CanutilsLogReader(BaseIOHandler): ``(0.0) vcan0 001#8d00100100820100`` """ - def __init__(self, file: AcceptedIOType) -> None: + file: TextIO + + def __init__(self, file: Union[StringPathLike, TextIO]) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text @@ -40,7 +42,7 @@ def __init__(self, file: AcceptedIOType) -> None: """ super().__init__(file, mode="r") - def __iter__(self): + def __iter__(self) -> Generator[Message, None, None]: for line in self.file: # skip empty lines @@ -48,14 +50,19 @@ def __iter__(self): if not temp: continue - timestamp, channel, frame = temp.split() - timestamp = float(timestamp[1:-1]) - canId, data = frame.split("#", maxsplit=1) - if channel.isdigit(): - channel = int(channel) + channel_string: str + timestamp_string, channel_string, frame = temp.split() + timestamp = float(timestamp_string[1:-1]) + can_id_string, data = frame.split("#", maxsplit=1) + + channel: Union[int, str] + if channel_string.isdigit(): + channel = int(channel_string) + else: + channel = channel_string - isExtended = len(canId) > 3 - canId = int(canId, 16) + is_extended = len(can_id_string) > 3 + can_id = int(can_id_string, 16) is_fd = False brs = False @@ -69,35 +76,35 @@ def __iter__(self): data = data[2:] if data and data[0].lower() == "r": - isRemoteFrame = True + is_remote_frame = True if len(data) > 1: dlc = int(data[1:]) else: dlc = 0 - dataBin = None + data_bin = None else: - isRemoteFrame = False + is_remote_frame = False dlc = len(data) // 2 - dataBin = bytearray() + data_bin = bytearray() for i in range(0, len(data), 2): - dataBin.append(int(data[i : (i + 2)], 16)) + data_bin.append(int(data[i : (i + 2)], 16)) - if canId & CAN_ERR_FLAG and canId & CAN_ERR_BUSERROR: + if can_id & CAN_ERR_FLAG and can_id & CAN_ERR_BUSERROR: msg = Message(timestamp=timestamp, is_error_frame=True) else: msg = Message( timestamp=timestamp, - arbitration_id=canId & 0x1FFFFFFF, - is_extended_id=isExtended, - is_remote_frame=isRemoteFrame, + arbitration_id=can_id & 0x1FFFFFFF, + is_extended_id=is_extended, + is_remote_frame=is_remote_frame, is_fd=is_fd, bitrate_switch=brs, error_state_indicator=esi, dlc=dlc, - data=dataBin, + data=data_bin, channel=channel, ) yield msg @@ -105,7 +112,7 @@ def __iter__(self): self.stop() -class CanutilsLogWriter(FileIOMessageWriter, Listener): +class CanutilsLogWriter(FileIOMessageWriter): """Logs CAN data to an ASCII log file (.log). This class is is compatible with "candump -L". @@ -115,7 +122,10 @@ class CanutilsLogWriter(FileIOMessageWriter, Listener): """ def __init__( - self, file: AcceptedIOType, channel: str = "vcan0", append: bool = False + self, + file: Union[StringPathLike, TextIO], + channel: str = "vcan0", + append: bool = False, ): """ :param file: a path-like object or as file-like object to write to diff --git a/can/io/csv.py b/can/io/csv.py index fa3175a2d..0161b4f55 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -10,15 +10,14 @@ """ from base64 import b64encode, b64decode -from typing import TextIO +from typing import TextIO, Generator, Union from can.message import Message -from can.listener import Listener -from .generic import BaseIOHandler, FileIOMessageWriter -from ..typechecking import AcceptedIOType +from .generic import FileIOMessageWriter, MessageReader +from ..typechecking import StringPathLike -class CSVReader(BaseIOHandler): +class CSVReader(MessageReader): """Iterator over CAN messages from a .csv file that was generated by :class:`~can.CSVWriter` or that uses the same format as described there. Assumes that there is a header @@ -27,7 +26,9 @@ class CSVReader(BaseIOHandler): Any line separator is accepted. """ - def __init__(self, file: AcceptedIOType) -> None: + file: TextIO + + def __init__(self, file: Union[StringPathLike, TextIO]) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text @@ -35,7 +36,7 @@ def __init__(self, file: AcceptedIOType) -> None: """ super().__init__(file, mode="r") - def __iter__(self): + def __iter__(self) -> Generator[Message, None, None]: # skip the header line try: next(self.file) @@ -62,7 +63,7 @@ def __iter__(self): self.stop() -class CSVWriter(FileIOMessageWriter, Listener): +class CSVWriter(FileIOMessageWriter): """Writes a comma separated text file with a line for each message. Includes a header line. @@ -85,7 +86,9 @@ class CSVWriter(FileIOMessageWriter, Listener): file: TextIO - def __init__(self, file: AcceptedIOType, append: bool = False) -> None: + def __init__( + self, file: Union[StringPathLike, TextIO], append: bool = False + ) -> None: """ :param file: a path-like object or a file-like object to write to. If this is a file-like object, is has to open in text diff --git a/can/io/generic.py b/can/io/generic.py index 96ff91abf..6f18fbe65 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -5,9 +5,6 @@ Optional, cast, Iterable, - Union, - TextIO, - BinaryIO, Type, ContextManager, ) @@ -76,15 +73,17 @@ def stop(self) -> None: class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): """The base class for all writers.""" + file: Optional[can.typechecking.FileLike] + # pylint: disable=abstract-method,too-few-public-methods class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): """A specialized base class for all writers with file descriptors.""" - file: Union[TextIO, BinaryIO] + file: can.typechecking.FileLike def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None: - # Not possible with the type signature, but be verbose for user friendliness + # Not possible with the type signature, but be verbose for user-friendliness if file is None: raise ValueError("The given file cannot be None") @@ -92,5 +91,5 @@ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> N # pylint: disable=too-few-public-methods -class MessageReader(BaseIOHandler, Iterable, metaclass=ABCMeta): +class MessageReader(BaseIOHandler, Iterable[can.Message], metaclass=ABCMeta): """The base class for all readers.""" diff --git a/can/io/logger.py b/can/io/logger.py index ec73a62ec..cbed054ac 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -7,7 +7,7 @@ from abc import ABC, abstractmethod from datetime import datetime import gzip -from typing import Any, Optional, Callable, TextIO, Type, Tuple, Union, cast +from typing import Any, Optional, Callable, Type, Tuple, cast, Dict from types import TracebackType @@ -16,17 +16,17 @@ from ..message import Message from ..listener import Listener -from .generic import BaseIOHandler, FileIOMessageWriter +from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter from .asc import ASCWriter from .blf import BLFWriter from .canutils import CanutilsLogWriter from .csv import CSVWriter from .sqlite import SqliteWriter from .printer import Printer -from ..typechecking import StringPathLike +from ..typechecking import StringPathLike, FileLike, AcceptedIOType -class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method +class Logger(MessageWriter): # pylint: disable=abstract-method """ Logs CAN messages to a file. @@ -52,7 +52,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method """ fetched_plugins = False - message_writers = { + message_writers: Dict[str, Type[MessageWriter]] = { ".asc": ASCWriter, ".blf": BLFWriter, ".csv": CSVWriter, @@ -64,7 +64,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method @staticmethod def __new__( # type: ignore cls: Any, filename: Optional[StringPathLike], *args: Any, **kwargs: Any - ) -> Listener: + ) -> MessageWriter: """ :param filename: the filename/path of the file to write to, may be a path-like object or None to @@ -85,20 +85,19 @@ def __new__( # type: ignore suffix = pathlib.PurePath(filename).suffix.lower() + file_or_filename: AcceptedIOType = filename if suffix == ".gz": - suffix, filename = Logger.compress(filename) + suffix, file_or_filename = Logger.compress(filename) try: - return cast( - Listener, Logger.message_writers[suffix](filename, *args, **kwargs) - ) + return Logger.message_writers[suffix](file_or_filename, *args, **kwargs) except KeyError: raise ValueError( f'No write support for this unknown log format "{suffix}"' ) from None @staticmethod - def compress(filename: StringPathLike) -> Tuple[str, Union[str, Any]]: + def compress(filename: StringPathLike) -> Tuple[str, FileLike]: """ Return the suffix and io object of the decompressed file. File will automatically recompress upon close. diff --git a/can/io/player.py b/can/io/player.py index dc60cf115..132751f4d 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -10,18 +10,17 @@ from pkg_resources import iter_entry_points -if typing.TYPE_CHECKING: - import can - -from .generic import BaseIOHandler, MessageReader +from .generic import MessageReader from .asc import ASCReader from .blf import BLFReader from .canutils import CanutilsLogReader from .csv import CSVReader from .sqlite import SqliteReader +from ..typechecking import StringPathLike, FileLike, AcceptedIOType +from ..message import Message -class LogReader(BaseIOHandler): +class LogReader(MessageReader): """ Replay logged CAN messages from a file. @@ -51,7 +50,7 @@ class LogReader(BaseIOHandler): """ fetched_plugins = False - message_readers = { + message_readers: typing.Dict[str, typing.Type[MessageReader]] = { ".asc": ASCReader, ".blf": BLFReader, ".csv": CSVReader, @@ -62,7 +61,7 @@ class LogReader(BaseIOHandler): @staticmethod def __new__( # type: ignore cls: typing.Any, - filename: "can.typechecking.StringPathLike", + filename: StringPathLike, *args: typing.Any, **kwargs: typing.Any, ) -> MessageReader: @@ -81,14 +80,11 @@ def __new__( # type: ignore suffix = pathlib.PurePath(filename).suffix.lower() + file_or_filename: AcceptedIOType = filename if suffix == ".gz": - suffix, filename = LogReader.decompress(filename) - + suffix, file_or_filename = LogReader.decompress(filename) try: - return typing.cast( - MessageReader, - LogReader.message_readers[suffix](filename, *args, **kwargs), - ) + return LogReader.message_readers[suffix](file_or_filename, *args, **kwargs) except KeyError: raise ValueError( f'No read support for this unknown log format "{suffix}"' @@ -96,8 +92,8 @@ def __new__( # type: ignore @staticmethod def decompress( - filename: "can.typechecking.StringPathLike", - ) -> typing.Tuple[str, typing.Union[str, typing.Any]]: + filename: StringPathLike, + ) -> typing.Tuple[str, typing.Union[str, FileLike]]: """ Return the suffix and io object of the decompressed file. """ @@ -106,6 +102,9 @@ def decompress( return real_suffix, gzip.open(filename, mode) + def __iter__(self) -> typing.Generator[Message, None, None]: + pass + class MessageSync: # pylint: disable=too-few-public-methods """ @@ -114,7 +113,7 @@ class MessageSync: # pylint: disable=too-few-public-methods def __init__( self, - messages: typing.Iterable["can.Message"], + messages: typing.Iterable[Message], timestamps: bool = True, gap: float = 0.0001, skip: float = 60.0, @@ -132,7 +131,7 @@ def __init__( self.gap = gap self.skip = skip - def __iter__(self) -> typing.Generator["can.Message", None, None]: + def __iter__(self) -> typing.Generator[Message, None, None]: playback_start_time = time() recorded_start_time = None diff --git a/can/io/printer.py b/can/io/printer.py index 09c86f81f..cafab3815 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -4,17 +4,16 @@ import logging -from typing import Optional, cast, TextIO +from typing import Optional, TextIO, Union from ..message import Message -from ..listener import Listener -from .generic import BaseIOHandler -from ..typechecking import AcceptedIOType +from .generic import MessageWriter +from ..typechecking import StringPathLike log = logging.getLogger("can.io.printer") -class Printer(BaseIOHandler, Listener): +class Printer(MessageWriter): """ The Printer class is a subclass of :class:`~can.Listener` which simply prints any messages it receives to the terminal (stdout). A message is turned into a @@ -27,7 +26,7 @@ class Printer(BaseIOHandler, Listener): file: Optional[TextIO] def __init__( - self, file: Optional[AcceptedIOType] = None, append: bool = False + self, file: Optional[Union[StringPathLike, TextIO]] = None, append: bool = False ) -> None: """ :param file: An optional path-like object or a file-like object to "print" diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 8d184bce1..5f05764d5 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -8,15 +8,17 @@ import threading import logging import sqlite3 +from typing import Generator from can.listener import BufferedReader from can.message import Message -from .generic import BaseIOHandler +from .generic import MessageWriter, MessageReader +from ..typechecking import StringPathLike log = logging.getLogger("can.io.sqlite") -class SqliteReader(BaseIOHandler): +class SqliteReader(MessageReader): """ Reads recorded CAN messages from a simple SQL database. @@ -30,9 +32,9 @@ class SqliteReader(BaseIOHandler): .. note:: The database schema is given in the documentation of the loggers. """ - def __init__(self, file, table_name="messages"): + def __init__(self, file: StringPathLike, table_name: str = "messages") -> None: """ - :param file: a `str` or since Python 3.7 a path like object that points + :param file: a `str` path like object that points to the database file to use :param str table_name: the name of the table to look for the messages @@ -45,7 +47,7 @@ def __init__(self, file, table_name="messages"): self._cursor = self._conn.cursor() self.table_name = table_name - def __iter__(self): + def __iter__(self) -> Generator[Message, None, None]: for frame_data in self._cursor.execute(f"SELECT * FROM {self.table_name}"): yield SqliteReader._assemble_message(frame_data) @@ -81,7 +83,7 @@ def stop(self): self._conn.close() -class SqliteWriter(BaseIOHandler, BufferedReader): +class SqliteWriter(MessageWriter, BufferedReader): """Logs received CAN data to a simple SQL database. The sqlite database may already exist, otherwise it will @@ -126,9 +128,9 @@ class SqliteWriter(BaseIOHandler, BufferedReader): MAX_BUFFER_SIZE_BEFORE_WRITES = 500 """Maximum number of messages to buffer before writing to the database""" - def __init__(self, file, table_name="messages"): + def __init__(self, file: StringPathLike, table_name: str = "messages") -> None: """ - :param file: a `str` or since Python 3.7 a path like object that points + :param file: a `str` or path like object that points to the database file to use :param str table_name: the name of the table to store messages in @@ -229,4 +231,4 @@ def stop(self): BufferedReader.stop(self) self._stop_running_event.set() self._writer_thread.join() - BaseIOHandler.stop(self) + MessageReader.stop(self) diff --git a/can/listener.py b/can/listener.py index 8ed4f7b77..8b90fc79e 100644 --- a/can/listener.py +++ b/can/listener.py @@ -4,22 +4,14 @@ import sys import warnings +import asyncio +from abc import ABCMeta, abstractmethod +from queue import SimpleQueue, Empty from typing import Any, AsyncIterator, Awaitable, Optional from can.message import Message from can.bus import BusABC -from abc import ABCMeta, abstractmethod - -try: - # Python 3.7 - from queue import SimpleQueue, Empty -except ImportError: - # Python 3.0 - 3.6 - from queue import Queue as SimpleQueue, Empty # type: ignore - -import asyncio - class Listener(metaclass=ABCMeta): """The basic listener that can be called directly to handle some @@ -37,6 +29,9 @@ class Listener(metaclass=ABCMeta): listener.stop() """ + def __init__(self, *args: Any, **kwargs: Any) -> None: + pass + @abstractmethod def on_message_received(self, msg: Message) -> None: """This method is called to handle the given message. @@ -68,7 +63,8 @@ class RedirectReader(Listener): A RedirectReader sends all received messages to another Bus. """ - def __init__(self, bus: BusABC) -> None: + def __init__(self, bus: BusABC, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) self.bus = bus def on_message_received(self, msg: Message) -> None: @@ -89,7 +85,9 @@ class BufferedReader(Listener): :attr is_stopped: ``True`` if the reader has been stopped """ - def __init__(self) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + # set to "infinite" size self.buffer: SimpleQueue[Message] = SimpleQueue() self.is_stopped: bool = False @@ -139,7 +137,9 @@ class AsyncBufferedReader(Listener): print(msg) """ - def __init__(self, **kwargs: Any) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.buffer: "asyncio.Queue[Message]" if "loop" in kwargs: diff --git a/can/typechecking.py b/can/typechecking.py index 3e3cca833..ed76b6c85 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -1,6 +1,6 @@ """Types for mypy type-checking """ - +import gzip import typing if typing.TYPE_CHECKING: @@ -27,7 +27,7 @@ Channel = typing.Union[ChannelInt, ChannelStr] # Used by the IO module -FileLike = typing.Union[typing.TextIO, typing.BinaryIO] +FileLike = typing.Union[typing.TextIO, typing.BinaryIO, gzip.GzipFile] StringPathLike = typing.Union[str, "os.PathLike[str]"] AcceptedIOType = typing.Union[FileLike, StringPathLike] From f1808ed5e8e20ebbf489713fe5da4a44fea079f0 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 16:34:07 +0100 Subject: [PATCH 0862/1235] Address pylint issues (#1239) * Adress pylint issues * Format code with black * Fix line-too-long * Fix abstract-method * Fix line-too-long Co-authored-by: felixdivo --- can/broadcastmanager.py | 4 ++-- can/exceptions.py | 3 ++- can/interface.py | 11 ++++------- can/listener.py | 6 +++--- can/logconvert.py | 2 +- can/viewer.py | 19 ++++++++++--------- 6 files changed, 22 insertions(+), 23 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 90cf0ce60..c15186e2c 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -19,7 +19,7 @@ import threading import time -# try to import win32event for event-based cyclic send task(needs pywin32 package) +# try to import win32event for event-based cyclic send task (needs the pywin32 package) try: import win32event @@ -260,7 +260,7 @@ def stop(self) -> None: def start(self) -> None: self.stopped = False if self.thread is None or not self.thread.is_alive(): - name = "Cyclic send task for 0x%X" % (self.messages[0].arbitration_id) + name = f"Cyclic send task for 0x{self.messages[0].arbitration_id:X}" self.thread = threading.Thread(target=self._run, name=name) self.thread.daemon = True diff --git a/can/exceptions.py b/can/exceptions.py index aec0dfd1d..5a7aa0b7c 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -14,6 +14,7 @@ For example, validating typical arguments and parameters might result in a :class:`ValueError`. This should always be documented for the function at hand. """ + import sys from contextlib import contextmanager @@ -114,7 +115,7 @@ def error_check( """Catches any exceptions and turns them into the new type while preserving the stack trace.""" try: yield - except Exception as error: + except Exception as error: # pylint: disable=broad-except if error_message is None: raise exception_type(str(error)) from error else: diff --git a/can/interface.py b/can/interface.py index 5282d77bf..e217f2fb6 100644 --- a/can/interface.py +++ b/can/interface.py @@ -32,7 +32,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]: module_name, class_name = BACKENDS[interface] except KeyError: raise NotImplementedError( - "CAN interface '{}' not supported".format(interface) + f"CAN interface '{interface}' not supported" ) from None # Import the correct interface module @@ -40,9 +40,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]: module = importlib.import_module(module_name) except Exception as e: raise CanInterfaceNotImplementedError( - "Cannot import module {} for CAN interface '{}': {}".format( - module_name, interface, e - ) + f"Cannot import module {module_name} for CAN interface '{interface}': {e}" ) from None # Get the correct class @@ -50,9 +48,8 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]: bus_class = getattr(module, class_name) except Exception as e: raise CanInterfaceNotImplementedError( - "Cannot import class {} from module {} for CAN interface '{}': {}".format( - class_name, module_name, interface, e - ) + f"Cannot import class {class_name} from module {module_name} for CAN interface " + f"'{interface}': {e}" ) from None return cast(Type[BusABC], bus_class) diff --git a/can/listener.py b/can/listener.py index 8b90fc79e..12836a83c 100644 --- a/can/listener.py +++ b/can/listener.py @@ -58,7 +58,7 @@ def stop(self) -> None: """ -class RedirectReader(Listener): +class RedirectReader(Listener): # pylint: disable=abstract-method """ A RedirectReader sends all received messages to another Bus. """ @@ -71,7 +71,7 @@ def on_message_received(self, msg: Message) -> None: self.bus.send(msg) -class BufferedReader(Listener): +class BufferedReader(Listener): # pylint: disable=abstract-method """ A BufferedReader is a subclass of :class:`~can.Listener` which implements a **message buffer**: that is, when the :class:`can.BufferedReader` instance is @@ -126,7 +126,7 @@ def stop(self) -> None: self.is_stopped = True -class AsyncBufferedReader(Listener): +class AsyncBufferedReader(Listener): # pylint: disable=abstract-method """A message buffer for use with :mod:`asyncio`. See :ref:`asyncio` for how to use with :class:`can.Notifier`. diff --git a/can/logconvert.py b/can/logconvert.py index 6a2f52341..730e82304 100644 --- a/can/logconvert.py +++ b/can/logconvert.py @@ -12,7 +12,7 @@ class ArgumentParser(argparse.ArgumentParser): def error(self, message): self.print_help(sys.stderr) - self.exit(errno.EINVAL, "%s: error: %s\n" % (self.prog, message)) + self.exit(errno.EINVAL, f"{self.prog}: error: {message}\n") def main(): diff --git a/can/viewer.py b/can/viewer.py index b74e954a0..9cd5246fb 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -29,7 +29,6 @@ import time from typing import Dict, List, Tuple, Union -import can from can import __version__ from .logger import ( _create_bus, @@ -53,7 +52,7 @@ curses = None # type: ignore -class CanViewer: +class CanViewer: # pylint: disable=too-many-instance-attributes def __init__(self, stdscr, bus, data_structs, testing=False): self.stdscr = stdscr self.bus = bus @@ -89,7 +88,7 @@ def run(self): # Clear the terminal and draw the header self.draw_header() - while 1: + while True: # Do not read the CAN-Bus when in paused mode if not self.paused: # Read the CAN-Bus and draw it in the terminal window @@ -237,8 +236,11 @@ def draw_can_bus_message(self, msg, sorting=False): self.ids[key]["count"] += 1 # Format the CAN-Bus ID as a hex value - arbitration_id_string = "0x{0:0{1}X}".format( - msg.arbitration_id, 8 if msg.is_extended_id else 3 + arbitration_id_string = ( + "0x{0:0{1}X}".format( # pylint: disable=consider-using-f-string + msg.arbitration_id, + 8 if msg.is_extended_id else 3, + ) ) # Use red for error frames @@ -263,7 +265,7 @@ def draw_can_bus_message(self, msg, sorting=False): previous_byte_values = self.previous_values[key] except KeyError: # no row of previous values exists for the current message ID # initialise a row to store the values for comparison next time - self.previous_values[key] = dict() + self.previous_values[key] = {} previous_byte_values = self.previous_values[key] for i, b in enumerate(msg.data): col = 52 + i * 3 @@ -279,7 +281,7 @@ def draw_can_bus_message(self, msg, sorting=False): else: data_color = color except KeyError: - # previous entry for byte didnt exist - default to rest of line colour + # previous entry for byte didn't exist - default to rest of line colour data_color = color finally: # write the new value to the previous values dict for next time @@ -336,7 +338,7 @@ def draw_header(self): def redraw_screen(self): # Trigger a complete redraw self.draw_header() - for key, ids in self.ids.items(): + for ids in self.ids.values(): self.draw_can_bus_message(ids["msg"]) @@ -545,7 +547,6 @@ def main() -> None: if can_filters: additional_config.update({"can_filters": can_filters}) bus = _create_bus(parsed_args, **additional_config) - # print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") curses.wrapper(CanViewer, bus, data_structs) From 48f9c271a80993bc5649de6e19e6dabf547949e9 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 17:05:41 +0100 Subject: [PATCH 0863/1235] Fix deprecation of asyncio.get_event_loop() (#1235) * Fix deprecation in test case * Modernize example * Better test_asyncio_notifier() * Format code with black * Attempt to fix typing; improve docs * try to fix mypy failure with type annotation * Better bus handling * Doc fix * Fix type errors in notifier.py * Format code with black * Fix example typing * Fix too long line in doc * Switch name from Listenable to MessageRecipient Co-authored-by: felixdivo --- can/interface.py | 2 +- can/notifier.py | 38 +++++++++++-------- examples/asyncio_demo.py | 80 +++++++++++++++++++--------------------- examples/serial_com.py | 6 +-- test/notifier_test.py | 70 +++++++++++++++++------------------ 5 files changed, 98 insertions(+), 98 deletions(-) diff --git a/can/interface.py b/can/interface.py index e217f2fb6..f1a0087e3 100644 --- a/can/interface.py +++ b/can/interface.py @@ -103,7 +103,7 @@ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg # resolve the bus class to use for that interface cls = _get_class_for_interface(kwargs["interface"]) - # remove the 'interface' key so it doesn't get passed to the backend + # remove the "interface" key, so it doesn't get passed to the backend del kwargs["interface"] # make sure the bus can handle this config format diff --git a/can/notifier.py b/can/notifier.py index 2554fb4fc..f7c004c4e 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -2,29 +2,30 @@ This module contains the implementation of :class:`~can.Notifier`. """ -from typing import Any, cast, Iterable, List, Optional, Union, Awaitable +import asyncio +import logging +import threading +import time +from typing import Any, Callable, cast, Iterable, List, Optional, Union, Awaitable from can.bus import BusABC from can.listener import Listener from can.message import Message -import threading -import logging -import time -import asyncio - logger = logging.getLogger("can.Notifier") +MessageRecipient = Union[Listener, Callable[[Message], None]] + class Notifier: def __init__( self, bus: Union[BusABC, List[BusABC]], - listeners: Iterable[Listener], + listeners: Iterable[MessageRecipient], timeout: float = 1.0, loop: Optional[asyncio.AbstractEventLoop] = None, ) -> None: - """Manages the distribution of :class:`can.Message` instances to listeners. + """Manages the distribution of :class:`~can.Message` instances to listeners. Supports multiple buses and listeners. @@ -35,11 +36,13 @@ def __init__( :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. + :param listeners: + An iterable of :class:`~can.Listener` or callables that receive a :class:`~can.Message` + and return nothing. + :param timeout: An optional maximum number of seconds to wait for any :class:`~can.Message`. + :param loop: An :mod:`asyncio` event loop to schedule the ``listeners`` in. """ - self.listeners: List[Listener] = list(listeners) + self.listeners: List[MessageRecipient] = list(listeners) self.bus = bus self.timeout = timeout self._loop = loop @@ -101,8 +104,8 @@ def stop(self, timeout: float = 5) -> None: # reader is a file descriptor self._loop.remove_reader(reader) for listener in self.listeners: - if hasattr(listener, "stop"): - listener.stop() + # Mypy prefers this over a hasattr(...) check + getattr(listener, "stop", lambda: None)() def _rx_thread(self, bus: BusABC) -> None: msg = None @@ -150,9 +153,12 @@ def _on_error(self, exc: Exception) -> bool: was_handled = False for listener in self.listeners: - if hasattr(listener, "on_error"): + on_error = getattr( + listener, "on_error", None + ) # Mypy prefers this over hasattr(...) + if on_error is not None: try: - listener.on_error(exc) + on_error(exc) except NotImplementedError: pass else: diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index d501d1aaf..0f37d6573 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -5,57 +5,53 @@ """ import asyncio +from typing import List + import can +from can.notifier import MessageRecipient -def print_message(msg): +def print_message(msg: can.Message) -> None: """Regular callback function. Can also be a coroutine.""" print(msg) -async def main(): +async def main() -> None: """The main function that runs in the loop.""" - bus = can.Bus("vcan0", bustype="virtual", receive_own_messages=True) - reader = can.AsyncBufferedReader() - logger = can.Logger("logfile.asc") - - listeners = [ - print_message, # Callback function - reader, # AsyncBufferedReader() listener - logger, # Regular Listener object - ] - # Create Notifier with an explicit loop to use for scheduling of callbacks - loop = asyncio.get_event_loop() - notifier = can.Notifier(bus, listeners, loop=loop) - # Start sending first message - bus.send(can.Message(arbitration_id=0)) - - print("Bouncing 10 messages...") - for _ in range(10): - # Wait for next message from AsyncBufferedReader - msg = await reader.get_message() - # Delay response - await asyncio.sleep(0.5) - msg.arbitration_id += 1 - bus.send(msg) - # Wait for last message to arrive - await reader.get_message() - print("Done!") - - # Clean-up - notifier.stop() - bus.shutdown() + with can.Bus( # type: ignore + interface="virtual", channel="my_channel_0", receive_own_messages=True + ) as bus: + reader = can.AsyncBufferedReader() + logger = can.Logger("logfile.asc") + + listeners: List[MessageRecipient] = [ + print_message, # Callback function + reader, # AsyncBufferedReader() listener + logger, # Regular Listener object + ] + # Create Notifier with an explicit loop to use for scheduling of callbacks + loop = asyncio.get_running_loop() + notifier = can.Notifier(bus, listeners, loop=loop) + # Start sending first message + bus.send(can.Message(arbitration_id=0)) + + print("Bouncing 10 messages...") + for _ in range(10): + # Wait for next message from AsyncBufferedReader + msg = await reader.get_message() + # Delay response + await asyncio.sleep(0.5) + msg.arbitration_id += 1 + bus.send(msg) + + # Wait for last message to arrive + await reader.get_message() + print("Done!") + + # Clean-up + notifier.stop() if __name__ == "__main__": - try: - # Get the default event loop - LOOP = asyncio.get_event_loop() - # Run until main coroutine finishes - LOOP.run_until_complete(main()) - finally: - LOOP.close() - - # or on Python 3.7+ simply - # asyncio.run(main()) + asyncio.run(main()) diff --git a/examples/serial_com.py b/examples/serial_com.py index 1fbc997b2..c57207a77 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -47,9 +47,9 @@ def receive(bus, stop_event): def main(): - """Controles the sender and receiver.""" - with can.interface.Bus(bustype="serial", channel="/dev/ttyS10") as server: - with can.interface.Bus(bustype="serial", channel="/dev/ttyS11") as client: + """Controls the sender and receiver.""" + with can.interface.Bus(interface="serial", channel="/dev/ttyS10") as server: + with can.interface.Bus(interface="serial", channel="/dev/ttyS11") as client: tx_msg = can.Message( arbitration_id=0x01, diff --git a/test/notifier_test.py b/test/notifier_test.py index c9d8f4a27..ca2093f55 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -9,48 +9,46 @@ class NotifierTest(unittest.TestCase): def test_single_bus(self): - bus = can.Bus("test", bustype="virtual", receive_own_messages=True) - reader = can.BufferedReader() - notifier = can.Notifier(bus, [reader], 0.1) - msg = can.Message() - bus.send(msg) - self.assertIsNotNone(reader.get_message(1)) - notifier.stop() - bus.shutdown() + with can.Bus("test", interface="virtual", receive_own_messages=True) as bus: + reader = can.BufferedReader() + notifier = can.Notifier(bus, [reader], 0.1) + msg = can.Message() + bus.send(msg) + self.assertIsNotNone(reader.get_message(1)) + notifier.stop() def test_multiple_bus(self): - bus1 = can.Bus(0, bustype="virtual", receive_own_messages=True) - bus2 = can.Bus(1, bustype="virtual", receive_own_messages=True) - reader = can.BufferedReader() - notifier = can.Notifier([bus1, bus2], [reader], 0.1) - msg = can.Message() - bus1.send(msg) - time.sleep(0.1) - bus2.send(msg) - recv_msg = reader.get_message(1) - self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 0) - recv_msg = reader.get_message(1) - self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 1) - notifier.stop() - bus1.shutdown() - bus2.shutdown() + with can.Bus(0, interface="virtual", receive_own_messages=True) as bus1: + with can.Bus(1, interface="virtual", receive_own_messages=True) as bus2: + reader = can.BufferedReader() + notifier = can.Notifier([bus1, bus2], [reader], 0.1) + msg = can.Message() + bus1.send(msg) + time.sleep(0.1) + bus2.send(msg) + recv_msg = reader.get_message(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 0) + recv_msg = reader.get_message(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 1) + notifier.stop() class AsyncNotifierTest(unittest.TestCase): def test_asyncio_notifier(self): - loop = asyncio.get_event_loop() - bus = can.Bus("test", bustype="virtual", receive_own_messages=True) - reader = can.AsyncBufferedReader() - notifier = can.Notifier(bus, [reader], 0.1, loop=loop) - msg = can.Message() - bus.send(msg) - future = asyncio.wait_for(reader.get_message(), 1.0) - recv_msg = loop.run_until_complete(future) - self.assertIsNotNone(recv_msg) - notifier.stop() - bus.shutdown() + async def run_it(): + with can.Bus("test", interface="virtual", receive_own_messages=True) as bus: + reader = can.AsyncBufferedReader() + notifier = can.Notifier( + bus, [reader], 0.1, loop=asyncio.get_running_loop() + ) + bus.send(can.Message()) + recv_msg = await asyncio.wait_for(reader.get_message(), 0.5) + self.assertIsNotNone(recv_msg) + notifier.stop() + + asyncio.run(run_it()) if __name__ == "__main__": From c7ba84a012d9e56fa3d29a8cf2b87d3706f12988 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 17:06:21 +0100 Subject: [PATCH 0864/1235] Fix entry_points deprecation (#1233) * Fix entry_points deprectaion * Format code with black * Undo change that shall be in a separate PR Co-authored-by: felixdivo --- can/interfaces/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index a60f3ac2f..90a05d7bc 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -32,14 +32,16 @@ try: from importlib.metadata import entry_points - entry = entry_points() - if "can.interface" in entry: - BACKENDS.update( - { - interface.name: tuple(interface.value.split(":")) - for interface in entry["can.interface"] - } - ) + try: + entries = entry_points(group="can.interface") + except TypeError: + # Fallback for Python <3.10 + # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note" + entries = entry_points().get("can.interface", []) + + BACKENDS.update( + {interface.name: tuple(interface.value.split(":")) for interface in entries} + ) except ImportError: from pkg_resources import iter_entry_points From 24ea2f2a4dbb6e72b41dc88646d0fc22ac1e10da Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Fri, 28 Jan 2022 09:12:56 +0100 Subject: [PATCH 0865/1235] Update changelog (#1240) --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1428075d..1f57a4b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Improved interfaces * Add more interface information to channel config (#917) * Improve timestamp accuracy on Windows (#934, #936) * Fix error with VN8900 (#1184) + * Add static typing (#1229) * PCAN * Do not incorrectly reset CANMsg.MSGTYPE on remote frame (#659, #681) * Add support for error frames (#711) @@ -118,9 +119,10 @@ Other API changes and improvements * Logger, viewer and player tools can handle CAN FD (#632) * Many bugfixes and more testing coverage * IO - * Log rotation (#648, #874, #881, #1147) + * [Log rotation](https://python-can.readthedocs.io/en/develop/listeners.html#can.SizedRotatingLogger) (#648, #874, #881, #1147) + * Transparent (de)compression of [gzip](https://docs.python.org/3/library/gzip.html) files for all formats (#1221) * Add [plugin support to can.io Reader/Writer](https://python-can.readthedocs.io/en/develop/listeners.html#listener) (#783) - * ASCReader/Writer enhancements (#820) + * ASCReader/Writer enhancements like increased robustness (#820, #1223) * Adding absolute timestamps to ASC reader (#761) * Support other base number (radix) at ASCReader (#764) * Add [logconvert script](https://python-can.readthedocs.io/en/develop/scripts.html#can-logconvert) (#1072, #1194) @@ -135,7 +137,7 @@ Other API changes and improvements * Changes to serial device number decoding (#869) * Add a default fileno function to the BusABC (#877) * Disallow Messages to simultaneously be "FD" and "remote" (#1049) -* Speed up interface plugin imports by removing pkg_resources (#1110) +* Speed up interface plugin imports by avoiding pkg_resources (#1110) * Allowing for extra config arguments in can.logger (#1142, #1170) * Add changed byte highlighting to viewer.py (#1159) * Change DLC to DL in Message.\_\_str\_\_() (#1212) @@ -180,7 +182,7 @@ Behind the scenes & Quality assurance * Use the [mypy](https://github.com/python/mypy) static type checker (#598, #651) * Use [tox](https://tox.wiki/en/latest/) for testing (#582, #833, #870) * Use [Mergify](https://mergify.com/) (#821, #835, #937) - * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and partly [Travis CI](https://travis-ci.org/), ended up with [GitHub Actions](https://docs.github.com/en/actions) only (#827) + * Switch between various CI providers, abandoned [AppVeyor](https://www.appveyor.com/) (#1009) and partly [Travis CI](https://travis-ci.org/), ended up with mostly [GitHub Actions](https://docs.github.com/en/actions) (#827, #1224) * Use the [black](https://black.readthedocs.io/en/stable/) auto-formatter (#950) * [Good test coverage](https://app.codecov.io/gh/hardbyte/python-can/branch/develop) for all but the interfaces * Testing: Many of the new features directly added tests, and coverage of existing code was improved too (for example: #1031, #581, #585, #586, #942, #1196, #1198) From a2c012899301f0863dd91e3657960d3cd245be73 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Fri, 4 Feb 2022 20:10:39 +0100 Subject: [PATCH 0866/1235] Prepare 4.0.0-rc.0 (#1241) --- can/__init__.py | 2 +- doc/history.rst | 3 ++- doc/pycanlib.pml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index f21ffcc09..037c407f0 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.0.0-dev.2" +__version__ = "4.0.0-rc.0" log = logging.getLogger("can") diff --git a/doc/history.rst b/doc/history.rst index caed67baa..659f18dda 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -49,7 +49,7 @@ The CANalyst-II interface was contributed by Shaoyu Meng in 2018. Support for CAN within Python ----------------------------- -Python natively supports the CAN protocol from version 3.3 on, if running on Linux: +Python natively supports the CAN protocol from version 3.3 on, if running on Linux (with a sufficiently new kernel): ============== ============================================================== ==== Python version Feature Link @@ -58,4 +58,5 @@ Python version Feature 3.4 Broadcast Management (BCM) commands are natively supported `Docs `__ 3.5 CAN FD support `Docs `__ 3.7 Support for CAN ISO-TP `Docs `__ +3.9 Native support for joining CAN filters `Docs `__ ============== ============================================================== ==== diff --git a/doc/pycanlib.pml b/doc/pycanlib.pml index 0ddcf25e5..907fadabb 100644 --- a/doc/pycanlib.pml +++ b/doc/pycanlib.pml @@ -1,4 +1,4 @@ -/* This promela model was used to verify the concurrent design of the bus object. */ +/* This promela model was used to verify a past design of the bus object. */ bool lock = false; From 257d57d44c2b0b308ba3a7d455e50b0b15c81a5b Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Fri, 4 Feb 2022 21:19:02 +0100 Subject: [PATCH 0867/1235] Fix guaranteed crash when using usb2can (#1249) --- can/interfaces/usb2can/usb2canInterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 258853425..0d3323f9e 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -103,7 +103,7 @@ def __init__( self.can = Usb2CanAbstractionLayer(dll) # get the serial number of the device - device_id = kwargs.get("serial", d=channel) + device_id = kwargs.get("serial", channel) # search for a serial number if the device_id is None or empty if not device_id: From 207be4b9520ef036cb89dd6114f73b162f825e88 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 8 Feb 2022 10:19:10 +1300 Subject: [PATCH 0868/1235] Update history & contributors (#1251) * Adds a CONTRIBUTING.md file which links to our docs * Remove email addresses from contributors.txt * Add github handles for new contributors * Update history.rst --- CONTRIBUTING.md | 1 + CONTRIBUTORS.txt | 76 ++++++++++++++++++++++++++++++++++++++++-------- doc/history.rst | 28 ++++++++++++++++++ 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..c00e9bd32 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Please read the [Development - Contributing](https://python-can.readthedocs.io/en/stable/development.html#contributing) guidelines in the documentation site. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b446489ae..ae7792e42 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -1,11 +1,13 @@ +https://github.com/hardbyte/python-can/graphs/contributors + Ben Powell -Brian Thorne +Brian Thorne Geert Linders Mark Catley -Phillip Dixon +Phillip Dixon Rose Lu Karl van Workum -Albert Bloomfield +Albert Bloomfield Sam Bristow Ethan Zonca Robert Kaye @@ -15,18 +17,68 @@ Tynan McAuley Bruno Pennati Jack Jester-Weinstein Joshua Villyard -Giuseppe Corbelli +Giuseppe Corbelli Christian Sandberg -Eduard Bröcker +Eduard Bröcker Boris Wenzlaff Pierre-Luc Tessier Gagné -Felix Divo -Kristian Sloth Lauszus -Shaoyu Meng -Alexander Mueller +Felix Divo +Kristian Sloth Lauszus +Shaoyu Meng +Alexander Mueller Jan Goeteyn -"ykzheng" +ykzheng Lear Corporation -Nick Black +Nick Black Francisco Javier Burgos Macia -Felix Nieuwenhuizen +Felix Nieuwenhuizen +@marcel-kanter +@bessman +@koberbe +@tamenol +@deonvdw +@ptoews +@chrisoro +@sou1hacker +@auden-rovellequartz +@typecprint +@tysonite +@Joey5337 +@Aajn +@josko7452 +@leventerevesz +@ericevenchick +@ixygo +@Gussy +@altendky +@philsc +@rliebscher +@jxltom +@kdschlosser +@tojoh511 +@s0kr4t3s +@jaesc +@NiallDarwin +@sdorre +@gordon-epc +@willson556 +@jjguti +@wiboticalex +@illuusio +@cperkulator +@simontegelid +@DawidRosinski +@fabiocrestani +@ChrisSweetKT +@ausserlesh +@wbarnha +@projectgus +@samsmith94 +@alexey +@MattWoodhead +@nbusser +@domologic +@fjburgos +@pkess +@felixn +@Tbruno25 \ No newline at end of file diff --git a/doc/history.rst b/doc/history.rst index 659f18dda..9ae0581b0 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -46,6 +46,34 @@ The CAN viewer terminal script was contributed by Kristian Sloth Lauszus in 2018 The CANalyst-II interface was contributed by Shaoyu Meng in 2018. +@deonvdw added support for the Robotell interface in 2019. + +Felix Divo and Karl Ding added type hints for the core library and many +interfaces leading up to the 4.0 release. + +Eric Evenchick added support for the CANtact devices in 2020. + +Felix Divo added an interprocess virtual bus interface in 2020. + +@jxltom added the gs_usb interface in 2020 supporting Geschwister Schneider USB/CAN devices +and bytewerk.org candleLight USB CAN devices such as candlelight, canable, cantact, etc. + +@jaesc added the nixnet interface in 2021 supporting NI-XNET devices from National Instruments. + +Tuukka Pasanen @illuusio added the neousys interface in 2021. + +Francisco Javier Burgos Maciá @fjburgos added ixxat FD support. + +@domologic contributed a socketcand interface in 2021. + +Felix N @felixn contributed the ETAS interface in 2021. + +Felix Divo unified exception handling across every interface in the lead up to +the 4.0 release. + +Felix Divo prepared the python-can 4.0 release. + + Support for CAN within Python ----------------------------- From 1142299ab5c6d7aa5ecb3d0fba561dc72e9a9f54 Mon Sep 17 00:00:00 2001 From: Simon Kerscher Date: Tue, 8 Feb 2022 10:01:17 +0100 Subject: [PATCH 0869/1235] If parsed data has shortened, overwrite end of line with spaces (#1201) * If parsed data has shortened, overwrite end of line with spaces This bug could easily be replicated by running: `python can_viewer.py -c vcan0 -i socketcan -d "123:>BB"` In another terminal send: `cansend vcan0 123#FFFF` Followed by: `cansend vcan0 123#0001` * Fill until end of available line after parsed data as suggedted by @zariiii9003 --- can/viewer.py | 3 +++ test/test_viewer.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/can/viewer.py b/can/viewer.py index 9cd5246fb..a84c865f5 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -302,6 +302,9 @@ def draw_can_bus_message(self, msg, sorting=False): else: values_list.append(str(x)) values_string = " ".join(values_list) + self.ids[key]["values_string_length"] = len(values_string) + values_string += " " * (self.x - len(values_string)) + self.draw_line(self.ids[key]["row"], 77, values_string, color) except (ValueError, struct.error): pass diff --git a/test/test_viewer.py b/test/test_viewer.py index f2e3ef0e8..20c3d2faa 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -188,6 +188,16 @@ def test_send(self): msg = can.Message(arbitration_id=0x101, data=data, is_extended_id=False) self.can_viewer.bus.send(msg) + # Send non-CANopen message with long parsed data length + data = [255, 255] + msg = can.Message(arbitration_id=0x102, data=data, is_extended_id=False) + self.can_viewer.bus.send(msg) + + # Send the same command, but with shorter parsed data length + data = [0, 0] + msg = can.Message(arbitration_id=0x102, data=data, is_extended_id=False) + self.can_viewer.bus.send(msg) + # Message with extended id data = [1, 2, 3, 4, 5, 6, 7, 8] msg = can.Message(arbitration_id=0x123456, data=data, is_extended_id=True) @@ -210,6 +220,8 @@ def test_receive(self): # For converting the EMCY and HEARTBEAT messages 0x080 + 0x01: struct.Struct("ff"), } @@ -229,6 +241,11 @@ def test_receive(self): for col, v in self.stdscr_dummy.draw_buffer[_id["row"]].items(): if col >= 52 + _id["msg"].dlc * 3: self.assertEqual(v, " ") + elif _id["msg"].arbitration_id == 0x102: + # Make sure the parsed values have been cleared after the shorted message was send + for col, v in self.stdscr_dummy.draw_buffer[_id["row"]].items(): + if col >= 77 + _id["values_string_length"]: + self.assertEqual(v, " ") elif _id["msg"].arbitration_id == 0x123456: # Check if the counter is incremented if _id["dt"] == 0: From 20b3138ea297c524b476e70c4727226f179ac6eb Mon Sep 17 00:00:00 2001 From: jacobian91 Date: Fri, 11 Feb 2022 12:46:11 -0800 Subject: [PATCH 0870/1235] Pass flags instead of flags_t type for USB2CAN (#1252) * Pass flags instead of flags_t type for USB2CAN * Remove usb2can unused open arguments for super Co-authored-by: Jacob Erickson --- can/interfaces/usb2can/usb2canInterface.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 0d3323f9e..e51d485cd 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -9,7 +9,6 @@ from can import BusABC, Message, CanInitializationError, CanOperationError from .usb2canabstractionlayer import Usb2CanAbstractionLayer, CanalMsg, CanalError from .usb2canabstractionlayer import ( - flags_t, IS_ERROR_FRAME, IS_REMOTE_FRAME, IS_ID_TYPE, @@ -95,7 +94,7 @@ def __init__( channel=None, dll="usb2can.dll", flags=0x00000008, - *args, + *_, bitrate=500000, **kwargs, ): @@ -118,11 +117,9 @@ def __init__( self.channel_info = f"USB2CAN device {device_id}" connector = f"{device_id}; {baudrate}" - self.handle = self.can.open(connector, flags_t) + self.handle = self.can.open(connector, flags) - super().__init__( - channel=channel, dll=dll, flags_t=flags_t, bitrate=bitrate, *args, **kwargs - ) + super().__init__(channel=channel, **kwargs) def send(self, msg, timeout=None): tx = message_convert_tx(msg) From f1a012cf88c6845ae9afbde9348ddfa7e67da02a Mon Sep 17 00:00:00 2001 From: Nadhmi JAZI <38762095+jazi007@users.noreply.github.com> Date: Fri, 11 Feb 2022 21:46:35 +0100 Subject: [PATCH 0871/1235] [IO][ASC]: fix data length (#1246) Co-authored-by: Nadhmi JAZI --- can/io/asc.py | 23 ++++++++++++++++------- test/logformats_test.py | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index f8607d061..c45192f75 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -13,7 +13,7 @@ import logging from ..message import Message -from ..util import channel2int +from ..util import channel2int, len2dlc, dlc2len from .generic import FileIOMessageWriter, MessageReader from ..typechecking import StringPathLike @@ -194,11 +194,20 @@ def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Messag msg_kwargs["bitrate_switch"] = brs == "1" msg_kwargs["error_state_indicator"] = esi == "1" dlc = int(dlc_str, self._converted_base) - msg_kwargs["dlc"] = dlc data_length = int(data_length_str) - - # CAN remote Frame - msg_kwargs["is_remote_frame"] = data_length == 0 + if data_length == 0: + # CAN remote Frame + msg_kwargs["is_remote_frame"] = True + msg_kwargs["dlc"] = dlc + else: + if dlc2len(dlc) != data_length: + logger.warning( + "DLC vs Data Length mismatch %d[%d] != %d", + dlc, + dlc2len(dlc), + data_length, + ) + msg_kwargs["dlc"] = data_length self._process_data_string(data, data_length, msg_kwargs) @@ -381,8 +390,8 @@ def on_message_received(self, msg: Message) -> None: 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), + dlc=len2dlc(msg.dlc), + data_length=len(msg.data), data=" ".join(data), message_duration=0, message_length=0, diff --git a/test/logformats_test.py b/test/logformats_test.py index ffb7a75a3..40ecd8ba3 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -520,7 +520,7 @@ def test_can_fd_message_64(self): arbitration_id=0x4EE, is_extended_id=False, channel=3, - dlc=0xF, + dlc=64, data=[0xA1, 2, 3, 4] + 59 * [0] + [0x64], is_fd=True, error_state_indicator=True, @@ -529,7 +529,7 @@ def test_can_fd_message_64(self): timestamp=31.506898, arbitration_id=0x1C4D80A7, channel=3, - dlc=0xF, + dlc=64, data=[0xB1, 2, 3, 4] + 59 * [0] + [0x64], is_fd=True, bitrate_switch=True, From 7fcc813d59fe815ae27db9d1195f8136a5bc6c01 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 11 Feb 2022 21:47:07 +0100 Subject: [PATCH 0872/1235] bugfix ASCReader (#1257) --- can/io/asc.py | 128 +++- test/data/issue_1256.asc | 1461 ++++++++++++++++++++++++++++++++++++++ test/logformats_test.py | 3 + 3 files changed, 1556 insertions(+), 36 deletions(-) create mode 100644 test/data/issue_1256.asc diff --git a/can/io/asc.py b/can/io/asc.py index c45192f75..1a97f6b72 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -34,8 +34,6 @@ class ASCReader(MessageReader): file: TextIO - FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" - def __init__( self, file: Union[StringPathLike, TextIO], @@ -60,51 +58,95 @@ def __init__( self.base = base self._converted_base = self._check_base(base) self.relative_timestamp = relative_timestamp - self.date = None + self.date: Optional[str] = None + self.start_time = 0.0 # TODO - what is this used for? The ASC Writer only prints `absolute` - self.timestamps_format = None - self.internal_events_logged = None + self.timestamps_format: Optional[str] = None + self.internal_events_logged = False - def _extract_header(self): + def _extract_header(self) -> None: for line in self.file: line = line.strip() - lower_case = line.lower() - if lower_case.startswith("date"): - self.date = line[5:] - elif lower_case.startswith("base"): - try: - _, base, _, timestamp_format = line.split() - except ValueError as exception: - raise Exception( - f"Unsupported header string format: {line}" - ) from exception + + datetime_match = re.match( + r"date\s+\w+\s+(?P.+)", line, re.IGNORECASE + ) + base_match = re.match( + r"base\s+(?Phex|dec)(?:\s+timestamps\s+" + r"(?Pabsolute|relative))?", + line, + re.IGNORECASE, + ) + comment_match = re.match(r"//.*", line) + events_match = re.match( + r"(?Pno)?\s*internal\s+events\s+logged", line, re.IGNORECASE + ) + + if datetime_match: + self.date = datetime_match.group("datetime_string") + self.start_time = ( + 0.0 + if self.relative_timestamp + else self._datetime_to_timestamp(self.date) + ) + continue + + elif base_match: + base = base_match.group("base") + timestamp_format = base_match.group("timestamp_format") self.base = base self._converted_base = self._check_base(self.base) - self.timestamps_format = timestamp_format - elif lower_case.endswith("internal events logged"): - self.internal_events_logged = not lower_case.startswith("no") - elif lower_case.startswith("//"): - # ignore comments + self.timestamps_format = timestamp_format or "absolute" continue - # grab absolute timestamp - elif lower_case.startswith("begin triggerblock"): - if self.relative_timestamp: - self.start_time = 0.0 - else: - try: - _, _, start_time = lower_case.split(None, 2) - start_time = datetime.strptime( - start_time, self.FORMAT_START_OF_FILE_DATE - ).timestamp() - except (ValueError, OSError): - # `OSError` to handle non-POSIX capable timestamps - start_time = 0.0 - self.start_time = start_time - # Currently the last line in the header which is parsed + + elif comment_match: + continue + + elif events_match: + self.internal_events_logged = events_match.group("no_events") is None break + else: break + @staticmethod + def _datetime_to_timestamp(datetime_string: str) -> float: + # ugly locale independent solution + month_map = { + "Jan": 1, + "Feb": 2, + "Mar": 3, + "Apr": 4, + "May": 5, + "Jun": 6, + "Jul": 7, + "Aug": 8, + "Sep": 9, + "Oct": 10, + "Nov": 11, + "Dec": 12, + "Mär": 3, + "Mai": 5, + "Okt": 10, + "Dez": 12, + } + for name, number in month_map.items(): + datetime_string = datetime_string.replace(name, str(number).zfill(2)) + + datetime_formats = ( + "%m %d %I:%M:%S.%f %p %Y", + "%m %d %I:%M:%S %p %Y", + "%m %d %H:%M:%S.%f %Y", + "%m %d %H:%M:%S %Y", + ) + for format_str in datetime_formats: + try: + return datetime.strptime(datetime_string, format_str).timestamp() + except ValueError: + continue + + raise ValueError(f"Incompatible datetime string {datetime_string}") + def _extract_can_id(self, str_can_id: str, msg_kwargs: Dict[str, Any]) -> None: if str_can_id[-1:].lower() == "x": msg_kwargs["is_extended_id"] = True @@ -219,6 +261,20 @@ def __iter__(self) -> Generator[Message, None, None]: for line in self.file: line = line.strip() + trigger_match = re.match( + r"begin\s+triggerblock\s+\w+\s+(?P.+)", + line, + re.IGNORECASE, + ) + if trigger_match: + datetime_str = trigger_match.group("datetime_string") + self.start_time = ( + 0.0 + if self.relative_timestamp + else self._datetime_to_timestamp(datetime_str) + ) + continue + if not re.match( r"\d+\.\d+\s+(\d+\s+(\w+\s+(Tx|Rx)|ErrorFrame)|CANFD)", line, diff --git a/test/data/issue_1256.asc b/test/data/issue_1256.asc new file mode 100644 index 000000000..c3eb55199 --- /dev/null +++ b/test/data/issue_1256.asc @@ -0,0 +1,1461 @@ +date Tue May 27 04:09:35.000 pm 2014 +base hex timestamps absolute +internal events logged +// version 10.0.1 + 0.019968 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.029964 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.039943 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.039977 1 11 Rx d 8 4A 28 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.049949 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.059945 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.059976 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.060015 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 0.069970 1 11 Rx d 8 84 29 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.070001 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.079951 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.089947 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.099951 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.099982 1 11 Rx d 8 BD 2A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.109949 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.109983 1 10 Rx d 8 10 27 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.110014 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.110032 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 0.110053 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.129997 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.130036 1 11 Rx d 8 F5 2B 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.139949 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.149954 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.159951 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.159983 1 11 Rx d 8 2C 2D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.160095 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 0.160132 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.169955 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.180007 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.189956 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.189991 1 11 Rx d 8 62 2E 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.199956 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.209970 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 0.210004 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.210026 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 0.210084 1 10 Rx d 8 F3 28 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.219957 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.219987 1 11 Rx d 8 95 2F 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.229990 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.240004 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.249954 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.249988 1 11 Rx d 8 C7 30 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.259976 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.260138 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 0.260170 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.269974 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.279956 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.279989 1 11 Rx d 8 F6 31 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.289959 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.299963 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.309957 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.309989 1 11 Rx d 8 22 33 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.310012 1 10 Rx d 8 D5 2A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.310075 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.310097 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 0.319936 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.329956 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.339973 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.339993 1 11 Rx d 8 4B 34 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.349918 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.359922 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.359957 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 0.360032 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.360102 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.360114 1 11 Rx d 8 71 35 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.380012 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.389965 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.399981 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.400017 1 11 Rx d 8 93 36 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.409967 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.410001 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.410024 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 0.410086 1 10 Rx d 8 B5 2C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.419955 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.429968 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.430001 1 11 Rx d 8 B2 37 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.439986 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.440148 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.459928 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.459970 1 11 Rx d 8 CC 38 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.459991 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 0.460048 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.469963 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.479988 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.489926 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.489959 1 11 Rx d 8 E2 39 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.499927 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.509930 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.509963 1 10 Rx d 8 91 2E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.510027 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 0.510057 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.519946 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.519980 1 11 Rx d 8 F2 3A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.529932 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 0.540023 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.549926 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.549958 1 11 Rx d 8 FE 3B D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.559975 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.560137 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.560200 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 0.560231 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.579988 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.580022 1 11 Rx d 8 05 3D DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.589973 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.599972 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.609972 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.610004 1 11 Rx d 8 06 3E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.610115 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 0.610151 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.610162 1 10 Rx d 8 69 30 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.610188 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.629991 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.639991 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.640026 1 11 Rx d 8 01 3F F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.649975 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.659977 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.660010 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.660144 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 0.669992 1 11 Rx d 8 F6 3F F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.670076 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.680000 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.689982 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.690146 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.690159 1 11 Rx d 8 E5 40 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.709978 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.710013 1 10 Rx d 8 3B 32 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.710079 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.710100 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 0.720010 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.729980 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.730013 1 11 Rx d 8 CD 41 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.739981 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.749983 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.759997 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.760030 1 11 Rx d 8 AF 42 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.760052 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 0.760113 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.769999 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.779998 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.789982 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.790003 1 11 Rx d 8 8A 43 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.799976 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.809974 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.810125 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.810139 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 0.810168 1 10 Rx d 8 07 34 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.819974 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.820007 1 11 Rx d 8 5D 44 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.829974 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.839975 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.849974 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 0.850006 1 11 Rx d 8 29 45 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.860028 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.860065 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 0.860138 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.860201 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.880033 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.880170 1 11 Rx d 8 EE 45 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.889961 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.900008 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.909943 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.909976 1 11 Rx d 8 AA 46 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.910071 1 10 Rx d 8 CB 35 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 0.910101 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.910113 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 0.919950 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.929950 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.939955 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.940093 1 11 Rx d 8 5F 47 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.940105 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.959949 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.959982 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 0.960053 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 0.969947 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.969981 1 11 Rx d 8 0B 48 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 0.979953 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.990010 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 0.999991 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.000156 1 11 Rx d 8 AF 48 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.009977 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.010014 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.010037 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 1.010107 1 10 Rx d 8 86 37 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.019975 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.030073 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.030111 1 11 Rx d 8 4B 49 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.040047 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.050030 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.060044 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.060181 1 11 Rx d 8 DE 49 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.060194 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 1.060223 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.060283 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.080019 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.089998 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.090034 1 11 Rx d 8 68 4A 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.100056 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.110002 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.110041 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 1.110098 1 10 Rx d 8 37 39 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.110132 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.110145 1 11 Rx d 8 EA 4A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.110155 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.130015 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.140014 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.150012 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.150061 1 11 Rx d 8 62 4B D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.150160 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.150173 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.150238 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 1.169969 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.179968 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.180014 1 11 Rx d 8 D1 4B DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.189955 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.200037 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.209998 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 1.210021 1 11 Rx d 8 37 4C F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.210151 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 1.210196 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.210251 1 10 Rx d 8 DE 3A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.220057 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.230061 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.240062 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.240099 1 11 Rx d 8 93 4C F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.250061 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.260053 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.260090 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.260113 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 1.270063 1 11 Rx d 8 E6 4C F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.270179 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.270193 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.289924 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.300009 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.300037 1 11 Rx d 8 2F 4D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.309979 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.310113 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.310183 1 10 Rx d 8 78 3C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.310217 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 1.310249 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.329968 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.330021 1 11 Rx d 8 6F 4D 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.339962 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.349962 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.359966 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.359992 1 11 Rx d 8 A5 4D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.360009 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 1.360074 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.370015 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.380032 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.390025 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.390058 1 11 Rx d 8 D1 4D 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.400054 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.400094 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.400116 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 1.400176 1 10 Rx d 8 06 3E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.400209 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.420035 1 11 Rx d 8 F4 4D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.420068 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.430012 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.439963 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.449976 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.450009 1 11 Rx d 8 0C 4E D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.459963 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.459986 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.460057 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 1.469973 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.480036 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.480071 1 11 Rx d 8 1B 4E DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.489976 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.500035 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.509978 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.510010 1 11 Rx d 8 20 4E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.510100 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 1.510131 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 1.510142 1 10 Rx d 8 86 3F 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.519982 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.530039 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 1.540022 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.540051 1 11 Rx d 8 1B 4E F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.550046 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.560021 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.560057 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.560181 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 1.570020 1 11 Rx d 8 0C 4E F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.570138 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.580022 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.590043 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.600024 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.600055 1 11 Rx d 8 F4 4D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.610019 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.610179 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.610191 1 10 Rx d 8 F7 40 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.610217 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 1.620027 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.630024 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.630055 1 11 Rx d 8 D1 4D 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.640032 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.650055 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.650094 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.650119 1 11 Rx d 8 A5 4D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.650135 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 1.650190 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.670103 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.680104 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.690059 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.690097 1 11 Rx d 8 6F 4D 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.690212 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.710033 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.710070 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 1.710139 1 10 Rx d 8 59 42 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.710189 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.720025 1 11 Rx d 8 2F 4D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.720057 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.730027 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.730186 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.750028 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.750060 1 11 Rx d 8 E6 4C D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.760049 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.760080 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.760095 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 1.770032 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.780053 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.780087 1 11 Rx d 8 93 4C DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.790032 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.800013 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.810033 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.810068 1 11 Rx d 8 37 4C F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.810178 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 1.810211 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.810263 1 10 Rx d 8 AB 43 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.810292 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.830028 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.840037 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.840069 1 11 Rx d 8 D1 4B F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.850034 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 1.860036 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.860070 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.860091 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 1.870040 1 11 Rx d 8 62 4B F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.870173 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.880039 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.890037 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.900074 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.900111 1 11 Rx d 8 EA 4A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.900134 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.900151 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.900246 1 10 Rx d 8 EB 44 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 1.900276 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 1.920089 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.930090 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.930127 1 11 Rx d 8 68 4A 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.940093 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.940132 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.960058 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.960096 1 11 Rx d 8 DE 49 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 1.960118 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 1.960187 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18 + 1.970038 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.980044 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.990002 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 1.990149 1 11 Rx d 8 4B 49 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.000000 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.010062 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.010100 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 2.010143 1 10 Rx d 8 1A 46 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.010182 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.020003 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.020035 1 11 Rx d 8 AF 48 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.030002 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.040047 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.050003 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.050137 1 11 Rx d 8 0B 48 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.060035 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.060072 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.060166 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 2.070114 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.080034 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.080071 1 11 Rx d 8 5F 47 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.090050 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.100064 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.110044 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.110208 1 11 Rx d 8 AA 46 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.110260 1 10 Rx d 8 36 47 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.110289 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 2.110316 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.110327 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.130053 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.140028 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.140060 1 11 Rx d 8 EE 45 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.150070 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.150102 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.150125 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.150231 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 2.170064 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.170211 1 11 Rx d 8 29 45 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.180053 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.190065 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.200058 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.200095 1 11 Rx d 8 5D 44 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.210053 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 2.210089 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 2.210158 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.210227 1 10 Rx d 8 3F 48 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.220056 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.230054 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.230215 1 11 Rx d 8 8A 43 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.230267 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.250051 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.260053 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.260089 1 11 Rx d 8 AF 42 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.260111 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.260127 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 2.270057 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.280069 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.290077 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.290234 1 11 Rx d 8 CD 41 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.300073 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.310053 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.310096 1 10 Rx d 8 34 49 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.310167 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.310239 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 2.310268 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.310280 1 11 Rx d 8 E5 40 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.330060 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.340074 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.350083 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.350234 1 11 Rx d 8 F6 3F D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.350288 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.350300 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 2.350330 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.370081 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.380078 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.380119 1 11 Rx d 8 01 3F DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.390063 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.400062 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.400097 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.400120 1 11 Rx d 8 06 3E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.400136 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.400236 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 2.400266 1 10 Rx d 8 14 4A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.420096 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.430063 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.440083 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.440116 1 11 Rx d 8 05 3D F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.440230 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.460066 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.460101 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 2.460172 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.470079 1 11 Rx d 8 FE 3B F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.470111 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.480072 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.480232 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.500067 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.500100 1 11 Rx d 8 F2 3A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.510082 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.510117 1 10 Rx d 8 E0 4A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.510181 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 2.510237 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.520174 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.520211 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 2.520234 1 11 Rx d 8 E2 39 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.540030 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.550072 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.560027 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.560060 1 11 Rx d 8 CC 38 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.560141 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.560196 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 2.560227 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.580070 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.590075 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.590111 1 11 Rx d 8 B2 37 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.600069 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.610070 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.610104 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 2.610168 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.610189 1 10 Rx d 8 96 4B 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.620075 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.620108 1 11 Rx d 8 93 36 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.630077 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.640065 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.650071 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.650103 1 11 Rx d 8 71 35 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.650124 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.650141 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.650240 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 2.670078 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.680091 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.680126 1 11 Rx d 8 4B 34 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.690092 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.690126 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.710092 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.710128 1 11 Rx d 8 22 33 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.710154 1 10 Rx d 8 37 4C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.710214 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 2.710259 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.720071 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.730075 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.730236 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.730256 1 11 Rx d 8 F6 31 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.750078 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.760095 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.760128 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.760150 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 2.770101 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.770139 1 11 Rx d 8 C7 30 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.770253 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.790118 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.800075 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.800113 1 11 Rx d 8 95 2F CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.810084 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.810116 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 2.810185 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.810272 1 10 Rx d 8 C1 4C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.810301 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.830087 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.830122 1 11 Rx d 8 62 2E 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.840105 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.850098 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 2.860076 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.860114 1 11 Rx d 8 2C 2D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.860136 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.860153 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 2.870100 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.880131 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.890145 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.890182 1 11 Rx d 8 F5 2B 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.900088 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.900125 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.900150 1 10 Rx d 8 34 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 2.900213 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.900283 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 2.920086 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.920229 1 11 Rx d 8 BD 2A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.930105 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.940094 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.940128 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.940151 1 11 Rx d 8 84 29 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.960131 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.960153 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 2.960199 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 2.970084 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.980091 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100 + 2.980263 1 11 Rx d 8 4A 28 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 2.980317 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.000106 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.010158 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.010195 1 11 Rx d 8 10 27 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.010218 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.010234 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 3.010278 1 10 Rx d 8 91 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.020129 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.030126 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.040128 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.040267 1 11 Rx d 8 D6 25 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.050098 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.060086 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.060119 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 3.060193 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.060269 1 11 Rx d 8 9C 24 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.060280 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.080052 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.090013 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.100011 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.100139 1 11 Rx d 8 63 23 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.110014 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.110044 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 3.110104 1 10 Rx d 8 D7 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.110134 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.120011 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.130110 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.130139 1 11 Rx d 8 2B 22 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.140015 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.150013 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.160153 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.160290 1 11 Rx d 8 F4 20 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.160342 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.160395 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 3.170068 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.180094 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.190101 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.190134 1 11 Rx d 8 BE 1F 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.190157 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.210102 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 3.210135 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.210157 1 10 Rx d 8 06 4E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.210223 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 3.220098 1 11 Rx d 8 8B 1E 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.220219 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.230103 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.230137 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.250083 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.250118 1 11 Rx d 8 59 1D D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.260090 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.260122 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 3.260194 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.270103 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.280074 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.280241 1 11 Rx d 8 2A 1C DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.290127 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.300105 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.310145 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.310178 1 11 Rx d 8 FE 1A F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.310201 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.310217 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 3.310282 1 10 Rx d 8 1D 4E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.310326 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.330117 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.340106 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.340272 1 11 Rx d 8 D5 19 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.350133 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.360104 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.360136 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 3.360206 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.370111 1 11 Rx d 8 AF 18 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.370143 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.380073 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.390099 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.400128 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.400301 1 11 Rx d 8 8D 17 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.400355 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.400367 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 3.400394 1 10 Rx d 8 1D 4E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.400422 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.420127 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.430111 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.430143 1 11 Rx d 8 6E 16 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.440133 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.440164 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.460133 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.460291 1 11 Rx d 8 54 15 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.460343 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.460396 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 3.470130 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.480128 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.480160 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.480184 1 11 Rx d 8 3E 14 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.500107 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.510123 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.510160 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.510183 1 10 Rx d 8 06 4E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.510246 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 3.520133 1 11 Rx d 8 2E 13 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.520256 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.520336 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 3.540135 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.550123 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.550154 1 11 Rx d 8 22 12 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.560132 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.560163 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 3.560234 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.560312 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.580132 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.580289 1 11 Rx d 8 1B 11 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.590114 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.600140 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.610137 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.610168 1 11 Rx d 8 1A 10 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.610191 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.610209 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 3.610267 1 10 Rx d 8 D7 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.620042 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.630075 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.640082 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.640213 1 11 Rx d 8 1F 0F F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.650082 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.650118 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.650142 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 3.650202 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.670166 1 11 Rx d 8 2A 0E F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.670202 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.680120 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.690128 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.690160 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.690184 1 11 Rx d 8 3B 0D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.710127 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.710271 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 3.710304 1 10 Rx d 8 91 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.710332 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.720163 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.730129 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.730166 1 11 Rx d 8 53 0C 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.730283 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.750126 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.760173 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.760210 1 11 Rx d 8 71 0B 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.760233 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.760249 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 3.770116 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.770268 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.790150 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.790184 1 11 Rx d 8 96 0A 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.800152 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.810126 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.810151 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.810225 1 10 Rx d 8 34 4D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.810258 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 3.810288 1 11 Rx d 8 C3 09 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.810300 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.830121 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.840131 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.850150 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 3.850182 1 11 Rx d 8 F7 08 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.850304 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.850318 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 3.850348 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.870134 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.880155 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.880187 1 11 Rx d 8 32 08 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.890156 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.890314 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.900151 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.900184 1 11 Rx d 8 76 07 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.900206 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.900308 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 3.900337 1 10 Rx d 8 C1 4C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 3.920150 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.930141 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.940146 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.940177 1 11 Rx d 8 C1 06 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.940289 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.960155 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.960321 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 3.960353 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 3.970143 1 11 Rx d 8 15 06 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 3.970175 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.980155 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100 + 3.980187 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.000161 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.000193 1 11 Rx d 8 71 05 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.010222 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.010262 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 4.010319 1 10 Rx d 8 37 4C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.010350 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.020171 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.020337 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.020350 1 11 Rx d 8 D5 04 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.040172 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.050149 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.060159 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.060196 1 11 Rx d 8 42 04 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.060314 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.060367 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 4.060397 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.080148 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.090164 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.090198 1 11 Rx d 8 B8 03 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.100207 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.110147 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.110185 1 10 Rx d 8 96 4B 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.110267 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.110291 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 4.120175 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.120209 1 11 Rx d 8 36 03 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.130179 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.140158 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.140314 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.140327 1 11 Rx d 8 BE 02 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.150166 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.150201 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 4.150271 1 12 Rx d 4 01 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.170105 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.180168 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.180203 1 11 Rx d 8 4F 02 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.190075 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.190107 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.210114 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 4.210252 1 11 Rx d 8 E9 01 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.210265 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.210307 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 4.210336 1 10 Rx d 8 E0 4A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.220109 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.230170 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.230205 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.230222 1 11 Rx d 8 8D 01 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.250120 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.260111 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.260144 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 4.260213 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.270185 1 11 Rx d 8 3A 01 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.270317 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.270392 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.290125 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.300176 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.300208 1 11 Rx d 8 F1 00 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.310134 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.310165 1 10 Rx d 8 14 4A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.310231 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 4.310286 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.310347 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.330186 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.330343 1 11 Rx d 8 B1 00 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.340151 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.350181 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.360178 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.360210 1 11 Rx d 8 7B 00 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.360232 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.360249 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 4.370158 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.380181 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.390169 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.390287 1 11 Rx d 8 4F 00 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.390342 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.400204 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.400241 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 4.400310 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.400388 1 10 Rx d 8 34 49 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.420206 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.420243 1 11 Rx d 8 2C 00 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.430163 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.440152 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.440184 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.440208 1 11 Rx d 8 14 00 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.460180 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.460345 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.460388 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 4.470164 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.480182 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.480216 1 11 Rx d 8 05 00 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.480327 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.500107 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.510152 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.510187 1 11 Rx d 8 00 00 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.510205 1 10 Rx d 8 3F 48 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.510259 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 4.510290 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 4.520188 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.520347 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 4.540146 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.540175 1 11 Rx d 8 05 00 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.550167 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.560189 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.560224 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.560343 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 4.560373 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.560385 1 11 Rx d 8 14 00 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.580185 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.590144 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.600191 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.600219 1 11 Rx d 8 2C 00 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.600293 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.600307 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 4.600335 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.600346 1 10 Rx d 8 36 47 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.620191 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.630170 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.630202 1 11 Rx d 8 4F 00 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.640194 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.640338 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.650180 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.650215 1 11 Rx d 8 7B 00 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.650237 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.650349 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 4.670204 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.680196 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.690196 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.690216 1 11 Rx d 8 B1 00 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.690288 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.710150 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.710187 1 10 Rx d 8 1A 46 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.710208 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.710224 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 4.720179 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.720293 1 11 Rx d 8 F1 00 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.730133 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.730167 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.750198 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.750232 1 11 Rx d 8 3A 01 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.760140 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.760173 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 4.760240 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.770140 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.770174 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.770197 1 11 Rx d 8 8D 01 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.790183 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.800136 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.810198 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.810231 1 11 Rx d 8 E9 01 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.810306 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.810320 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 4.810345 1 10 Rx d 8 EB 44 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.810374 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.830181 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.840222 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.840254 1 11 Rx d 8 4F 02 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.850262 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 4.850399 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.850413 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 4.850442 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.870188 1 11 Rx d 8 BE 02 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.870319 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.880199 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.890186 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.900200 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.900232 1 11 Rx d 8 36 03 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.900255 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.900274 1 10 Rx d 8 AB 43 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 4.900333 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 4.900379 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.920200 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.930189 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.930226 1 11 Rx d 8 B8 03 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.930343 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.940195 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.960198 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.960231 1 11 Rx d 8 42 04 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 4.960253 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 4.960364 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 4.970188 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.980200 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.980360 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 4.980377 1 11 Rx d 8 D5 04 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.000197 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.010190 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.010223 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 5.010289 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.010310 1 10 Rx d 8 59 42 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.020239 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.020276 1 11 Rx d 8 71 05 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.020367 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.040200 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.050214 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.050249 1 11 Rx d 8 15 06 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.060174 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.060206 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.060338 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 5.060374 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.080201 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.080233 1 11 Rx d 8 C1 06 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.090209 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.100201 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.100357 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.100370 1 11 Rx d 8 76 07 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.100377 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 5.100404 1 10 Rx d 8 F7 40 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.100433 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.120199 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.130213 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.140201 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.140232 1 11 Rx d 8 32 08 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.140341 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.150214 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.150251 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.150351 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 5.170256 1 11 Rx d 8 F7 08 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.170292 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.180234 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.190222 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.190259 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.190284 1 11 Rx d 8 C3 09 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.210201 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 5.210237 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.210344 1 10 Rx d 8 86 3F 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.210373 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 5.220196 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.230215 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.230373 1 11 Rx d 8 96 0A 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.230426 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.250211 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.260116 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.260139 1 11 Rx d 8 71 0B 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.260154 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 5.260198 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.270165 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.270200 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.290162 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.290299 1 11 Rx d 8 53 0C 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.300164 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.310223 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.310257 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 5.310301 1 10 Rx d 8 06 3E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.310340 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.310404 1 11 Rx d 8 3B 0D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.310415 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.330168 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.340163 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.350165 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.350300 1 11 Rx d 8 2A 0E D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.350353 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.350365 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.350373 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 5.370212 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.380203 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.380237 1 11 Rx d 8 1F 0F DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.390213 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.400203 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.400242 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.400266 1 11 Rx d 8 1A 10 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.400283 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 5.400347 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.400410 1 10 Rx d 8 78 3C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.420202 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.430213 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.440203 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.440238 1 11 Rx d 8 1B 11 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.440352 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.460203 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.460238 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.460344 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 5.470230 1 11 Rx d 8 22 12 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.470257 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.480180 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.480355 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.500216 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.500252 1 11 Rx d 8 2E 13 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.510235 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.510269 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.510291 1 10 Rx d 8 DE 3A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.510349 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 5.510403 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.520203 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 5.520240 1 11 Rx d 8 3E 14 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.540232 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.550219 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.560240 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.560273 1 11 Rx d 8 54 15 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.560385 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 5.560417 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.560477 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.580241 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.590253 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.590290 1 11 Rx d 8 6E 16 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.600261 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.600398 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.600412 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 5.600439 1 10 Rx d 8 37 39 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.600467 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.620237 1 11 Rx d 8 8D 17 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.620368 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.630250 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.640286 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.640323 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.640346 1 11 Rx d 8 AF 18 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.650275 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.650312 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.650411 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 5.670301 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.680244 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.680281 1 11 Rx d 8 D5 19 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.680398 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.690241 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.710227 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.710266 1 11 Rx d 8 FE 1A F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.710289 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 5.710355 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.710408 1 10 Rx d 8 86 37 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.720247 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.730242 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.730400 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.730413 1 11 Rx d 8 2A 1C F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.750231 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.760248 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.760281 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.760303 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 5.770228 1 11 Rx d 8 59 1D F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.770350 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.770373 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.790239 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.800150 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.800188 1 11 Rx d 8 8B 1E CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.810189 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.810224 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.810337 1 10 Rx d 8 CB 35 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.810367 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 5.810396 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.830234 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.830270 1 11 Rx d 8 BE 1F 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.840299 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.850194 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 5.850341 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.850354 1 11 Rx d 8 F4 20 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.850362 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 5.850392 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.870185 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.880196 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.890274 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.890311 1 11 Rx d 8 2B 22 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.890425 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.900288 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.900326 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 5.900394 1 10 Rx d 8 07 34 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 5.900429 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.920258 1 11 Rx d 8 63 23 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.920295 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.930234 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.940252 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.940284 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.940307 1 11 Rx d 8 9C 24 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.960253 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.960285 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 5.960389 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 5.970257 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.980257 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100 + 5.980424 1 11 Rx d 8 D6 25 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 5.980476 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.000260 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.010259 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.010291 1 11 Rx d 8 10 27 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.010316 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 6.010375 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.010396 1 10 Rx d 8 3B 32 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.010437 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.020264 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.040254 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.040431 1 11 Rx d 8 4A 28 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.050267 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.060287 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.060329 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.060459 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 6.060490 1 11 Rx d 8 84 29 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.060501 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.080264 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.090264 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.100264 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.100426 1 11 Rx d 8 BD 2A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.100478 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.100490 1 10 Rx d 8 69 30 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.100516 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.100527 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 6.120259 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.130240 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.130278 1 11 Rx d 8 F5 2B 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.140262 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.140294 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.150266 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.150298 1 11 Rx d 8 2C 2D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.150320 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 6.150390 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.170276 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.180295 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.180332 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.180356 1 11 Rx d 8 62 2E 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.190293 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.210248 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 6.210286 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.210397 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 6.210437 1 10 Rx d 8 91 2E 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.220267 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.220301 1 11 Rx d 8 95 2F 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.230228 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.230392 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.250275 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.250312 1 11 Rx d 8 C7 30 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.260277 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.260314 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 6.260390 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.260402 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.270300 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.270338 1 11 Rx d 8 F6 31 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.290288 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.300282 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.310272 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.310305 1 11 Rx d 8 22 33 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.310417 1 10 Rx d 8 B5 2C 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.310449 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.310511 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 6.310539 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.330273 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.340288 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.340319 1 11 Rx d 8 4B 34 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.350201 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.350238 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.350258 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 6.350266 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.370218 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.370357 1 11 Rx d 8 71 35 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.380217 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.390218 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.400189 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.400212 1 11 Rx d 8 93 36 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.400228 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.400243 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.400303 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 6.400334 1 10 Rx d 8 D5 2A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.420281 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.430259 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.430419 1 11 Rx d 8 B2 37 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.430471 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.440281 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.460278 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.460316 1 11 Rx d 8 CC 38 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.460339 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 6.460410 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.470284 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.480284 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.480317 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.480340 1 11 Rx d 8 E2 39 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.500285 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.510262 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.510296 1 10 Rx d 8 F3 28 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.510359 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 6.510415 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.510427 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.510436 1 11 Rx d 8 F2 3A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.520278 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 6.540273 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.550261 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.550299 1 11 Rx d 8 FE 3B D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.560258 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.560405 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.560469 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 6.560500 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.580285 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.580323 1 11 Rx d 8 05 3D DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.590290 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.600283 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.600320 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.600343 1 11 Rx d 8 06 3E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.600359 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 6.600416 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.600427 1 10 Rx d 8 10 27 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.620260 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.630273 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.640290 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.640326 1 11 Rx d 8 01 3F F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.640439 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.650286 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.650319 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.650449 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 6.670354 1 11 Rx d 8 F6 3F F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.670389 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.680295 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.680459 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.690274 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.690306 1 11 Rx d 8 E5 40 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.710273 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.710311 1 10 Rx d 8 2D 25 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.710380 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.710451 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 6.720295 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.730276 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.730308 1 11 Rx d 8 CD 41 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.730420 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.750294 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.760296 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.760330 1 11 Rx d 8 AF 42 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.760352 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 6.760414 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.760434 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.770281 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.790280 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.790318 1 11 Rx d 8 8A 43 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.800301 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.810282 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.810447 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.810510 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 6.810539 1 10 Rx d 8 4B 23 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.810567 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.810579 1 11 Rx d 8 5D 44 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.830296 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.840298 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.850278 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 6.850309 1 11 Rx d 8 29 45 D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.850404 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.850417 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 6.850450 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.870283 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.870861 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.870893 1 11 Rx d 8 EE 45 DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.890260 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.890295 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.900196 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.900215 1 11 Rx d 8 AA 46 F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.900225 1 10 Rx d 8 6B 21 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 6.900260 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.900326 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 6.920201 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.930203 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.930351 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.930363 1 11 Rx d 8 5F 47 F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.940201 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.960203 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.960231 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 6.960293 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 6.970202 1 64 Rx d 4 5C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.970229 1 11 Rx d 8 0B 48 F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 6.980204 1 64 Rx d 4 64 03 00 00 Length = 0 BitCount = 0 ID = 100 + 6.980229 1 64 Rx d 4 6C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.000335 1 64 Rx d 4 74 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.000507 1 11 Rx d 8 AF 48 CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.010287 1 64 Rx d 4 7C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.010320 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.010343 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 7.010400 1 10 Rx d 8 8F 1F 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.010457 1 64 Rx d 4 64 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.020304 1 64 Rx d 4 6C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.020339 1 11 Rx d 8 4B 49 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.040311 1 64 Rx d 4 74 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.050320 1 64 Rx d 4 7C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.060309 1 64 Rx d 4 84 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.060477 1 11 Rx d 8 DE 49 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.060529 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 7.060560 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.060620 1 64 Rx d 4 8C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.080310 1 64 Rx d 4 94 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.090297 1 64 Rx d 4 9C 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.090329 1 11 Rx d 8 68 4A 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.100292 1 64 Rx d 4 A4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.100312 1 64 Rx d 4 AC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.100328 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 7.100387 1 10 Rx d 8 B7 1D 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.100419 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.120312 1 11 Rx d 8 EA 4A 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.120444 1 64 Rx d 4 B4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.130326 1 64 Rx d 4 BC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.140333 1 64 Rx d 4 C4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.140370 1 64 Rx d 4 CC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.140394 1 11 Rx d 8 62 4B D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.150338 1 64 Rx d 4 D4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.150376 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.150480 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 7.170318 1 64 Rx d 4 DC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.180302 1 64 Rx d 4 E4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.180442 1 11 Rx d 8 D1 4B DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.180498 1 64 Rx d 4 EC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.190295 1 64 Rx d 4 F4 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.210313 1 64 Rx d 4 FC 00 00 00 Length = 0 BitCount = 0 ID = 100 + 7.210347 1 11 Rx d 8 37 4C F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.210370 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 7.210430 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.210497 1 10 Rx d 8 E5 1B 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.220304 1 64 Rx d 4 04 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.230303 1 64 Rx d 4 0C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.230336 1 64 Rx d 4 14 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.230360 1 11 Rx d 8 93 4C F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.250319 1 64 Rx d 4 1C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.260301 1 64 Rx d 4 24 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.260335 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.260357 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 7.260418 1 11 Rx d 8 E6 4C F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.260497 1 64 Rx d 4 2C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.270301 1 64 Rx d 4 34 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.290306 1 64 Rx d 4 3C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.300306 1 64 Rx d 4 44 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.300341 1 11 Rx d 8 2F 4D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.310325 1 64 Rx d 4 4C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.310486 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.310548 1 10 Rx d 8 19 1A 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.310577 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 7.310605 1 64 Rx d 4 54 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.330337 1 64 Rx d 4 5C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.330375 1 11 Rx d 8 6F 4D 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.340304 1 64 Rx d 4 64 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.350330 1 64 Rx d 4 6C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.350362 1 64 Rx d 4 74 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.350386 1 11 Rx d 8 A5 4D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.350404 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 7.350467 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.370323 1 64 Rx d 4 7C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.380343 1 64 Rx d 4 84 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.390331 1 64 Rx d 4 8C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.390368 1 11 Rx d 8 D1 4D 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.390484 1 64 Rx d 4 94 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.400313 1 64 Rx d 4 9C 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.400345 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 7.400406 1 10 Rx d 8 55 18 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.400462 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.420308 1 11 Rx d 8 F4 4D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.420344 1 64 Rx d 4 A4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.430307 1 64 Rx d 4 AC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.430471 1 64 Rx d 4 B4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.440263 1 64 Rx d 4 BC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.440286 1 11 Rx d 8 0C 4E D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.460271 1 64 Rx d 4 C4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.460305 1 12 Rx d 4 01 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.460395 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 7.470270 1 64 Rx d 4 CC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.480274 1 64 Rx d 4 D4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.480307 1 11 Rx d 8 1B 4E DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.480398 1 64 Rx d 4 DC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.500279 1 64 Rx d 4 E4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.510321 1 64 Rx d 4 EC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.510356 1 11 Rx d 8 20 4E F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.510371 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 7.510413 1 12 Rx d 4 00 00 00 00 Length = 0 BitCount = 0 ID = 18 + 7.510426 1 10 Rx d 8 9A 16 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.510455 1 64 Rx d 4 F4 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.520273 1 64 Rx d 4 FC 01 00 00 Length = 0 BitCount = 0 ID = 100 + 7.540273 1 64 Rx d 4 04 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.540306 1 11 Rx d 8 1B 4E F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.550279 1 64 Rx d 4 0C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.560279 1 64 Rx d 4 14 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.560427 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 7.560491 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 7.560522 1 11 Rx d 8 0C 4E F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.560534 1 64 Rx d 4 1C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.580317 1 64 Rx d 4 24 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.590327 1 64 Rx d 4 2C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.600338 1 64 Rx d 4 34 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.600371 1 11 Rx d 8 F4 4D CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.600492 1 64 Rx d 4 3C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.600510 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 7.600518 1 10 Rx d 8 E9 14 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.600546 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 7.620313 1 64 Rx d 4 44 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.630335 1 64 Rx d 4 4C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.630369 1 11 Rx d 8 D1 4D 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.640340 1 64 Rx d 4 54 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.640372 1 64 Rx d 4 5C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.650332 1 64 Rx d 4 64 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.650364 1 11 Rx d 8 A5 4D 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.650386 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 7.650450 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 7.670400 1 64 Rx d 4 6C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.680343 1 64 Rx d 4 74 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.680511 1 64 Rx d 4 7C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.680525 1 11 Rx d 8 6F 4D 64 16 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.690328 1 64 Rx d 4 84 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.710318 1 64 Rx d 4 8C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.710351 1 65 Rx d 3 01 00 00 Length = 0 BitCount = 0 ID = 101 + 7.710419 1 10 Rx d 8 42 13 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.710479 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 7.720345 1 11 Rx d 8 2F 4D 99 05 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.720376 1 64 Rx d 4 94 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.720401 1 64 Rx d 4 9C 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.730346 1 64 Rx d 4 A4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.750372 1 64 Rx d 4 AC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.750536 1 11 Rx d 8 E6 4C D7 18 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.760370 1 64 Rx d 4 B4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.760408 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 7.760431 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 7.760479 1 64 Rx d 4 BC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.770375 1 64 Rx d 4 C4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.770413 1 11 Rx d 8 93 4C DD 2C 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.790333 1 64 Rx d 4 CC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.800344 1 64 Rx d 4 D4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.810334 1 64 Rx d 4 DC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.810496 1 11 Rx d 8 37 4C F1 14 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.810548 1 65 Rx d 3 19 00 00 Length = 0 BitCount = 0 ID = 101 + 7.810577 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 7.810629 1 10 Rx d 8 A8 11 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.810658 1 64 Rx d 4 E4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.830335 1 64 Rx d 4 EC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.840350 1 64 Rx d 4 F4 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.840382 1 11 Rx d 8 D1 4B F6 07 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.850350 1 64 Rx d 4 FC 02 00 00 Length = 0 BitCount = 0 ID = 100 + 7.850381 1 64 Rx d 4 04 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.850410 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 7.850429 1 66 Rx d 1 02 Length = 0 BitCount = 0 ID = 102 + 7.870348 1 11 Rx d 8 62 4B F9 0A 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.870470 1 64 Rx d 4 0C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.880353 1 64 Rx d 4 14 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.890353 1 64 Rx d 4 1C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.890391 1 64 Rx d 4 24 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.890416 1 11 Rx d 8 EA 4A CD 11 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.900349 1 64 Rx d 4 2C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.900381 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 + 7.900501 1 10 Rx d 8 1A 10 00 00 00 00 00 00 Length = 0 BitCount = 0 ID = 16 + 7.900533 1 65 Rx d 3 32 00 00 Length = 0 BitCount = 0 ID = 101 + 7.920354 1 64 Rx d 4 34 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.930328 1 64 Rx d 4 3C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.930488 1 11 Rx d 8 68 4A 4D 22 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.930546 1 64 Rx d 4 44 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.940352 1 64 Rx d 4 4C 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.960354 1 64 Rx d 4 54 03 00 00 Length = 0 BitCount = 0 ID = 100 + 7.960389 1 11 Rx d 8 DE 49 20 1D 00 00 00 00 Length = 0 BitCount = 0 ID = 17 + 7.960411 1 66 Rx d 1 04 Length = 0 BitCount = 0 ID = 102 + 7.960498 1 12 Rx d 4 00 01 00 00 Length = 0 BitCount = 0 ID = 18 diff --git a/test/logformats_test.py b/test/logformats_test.py index 40ecd8ba3..d7fb92f81 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -557,6 +557,9 @@ def test_can_and_canfd_error_frames(self): def test_ignore_comments(self): _msg_list = self._read_log_file("logfile.asc") + def test_no_triggerblock(self): + _msg_list = self._read_log_file("issue_1256.asc") + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From cbc5294ac7e4dbbb9b71b202723d80b3ce1d60bf Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 18 Feb 2022 23:41:27 +0100 Subject: [PATCH 0873/1235] Finalize changelog for 4.0.0 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f57a4b17..86612f9b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,8 @@ Improved interfaces * Improve timestamp accuracy on Windows (#934, #936) * usb2can * Fix "Error 8" on Windows and provide better error messages (#989) + * Fix crash on initialization (#1248, #1249) + * Pass flags instead of flags_t type upon initialization (#1252) * serial * Fix "TypeError: cannot unpack non-iterable NoneType" and more robust error handling (#1000, #1010) * canalystii @@ -122,7 +124,7 @@ Other API changes and improvements * [Log rotation](https://python-can.readthedocs.io/en/develop/listeners.html#can.SizedRotatingLogger) (#648, #874, #881, #1147) * Transparent (de)compression of [gzip](https://docs.python.org/3/library/gzip.html) files for all formats (#1221) * Add [plugin support to can.io Reader/Writer](https://python-can.readthedocs.io/en/develop/listeners.html#listener) (#783) - * ASCReader/Writer enhancements like increased robustness (#820, #1223) + * ASCReader/Writer enhancements like increased robustness (#820, #1223, #1256, #1257) * Adding absolute timestamps to ASC reader (#761) * Support other base number (radix) at ASCReader (#764) * Add [logconvert script](https://python-can.readthedocs.io/en/develop/scripts.html#can-logconvert) (#1072, #1194) @@ -167,6 +169,7 @@ Other Bugfixes * Calling stop_all_periodic_tasks() in BusABC.shutdown() and all interfaces call it on shutdown (#1174) * Timing configurations do not allow int (#1175) * Some smaller bugfixes are not listed here since the problems were never part of a proper release +* ASCReader & ASCWriter using DLC as data length (#1245, #1246) Behind the scenes & Quality assurance ------------------------------------- From d177a821bb10333aa546e601116f71dbafd11252 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 18 Feb 2022 23:42:26 +0100 Subject: [PATCH 0874/1235] Update version to 4.0.0 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 037c407f0..2a0b805ac 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.0.0-rc.0" +__version__ = "4.0.0" log = logging.getLogger("can") From 4d9b32c99c333ce9a0cac89afa9aeb324b3b4f7f Mon Sep 17 00:00:00 2001 From: Nadhmi JAZI <38762095+jazi007@users.noreply.github.com> Date: Tue, 1 Mar 2022 19:15:06 +0100 Subject: [PATCH 0875/1235] [IO][canutils]: add direction support (#1244) Co-authored-by: Nadhmi JAZI --- can/io/canutils.py | 17 ++++++++++++++--- test/test_logger.py | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/can/io/canutils.py b/can/io/canutils.py index 69793212c..fa96fcb9d 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -51,7 +51,12 @@ def __iter__(self) -> Generator[Message, None, None]: continue channel_string: str - timestamp_string, channel_string, frame = temp.split() + if temp[-2:].lower() in (" r", " t"): + timestamp_string, channel_string, frame, is_rx_string = temp.split() + is_rx = is_rx_string.strip().lower() == "r" + else: + timestamp_string, channel_string, frame = temp.split() + is_rx = True timestamp = float(timestamp_string[1:-1]) can_id_string, data = frame.split("#", maxsplit=1) @@ -101,6 +106,7 @@ def __iter__(self) -> Generator[Message, None, None]: is_extended_id=is_extended, is_remote_frame=is_remote_frame, is_fd=is_fd, + is_rx=is_rx, bitrate_switch=brs, error_state_indicator=esi, dlc=dlc, @@ -164,8 +170,13 @@ def on_message_received(self, msg): else: framestr += " %03X#" % (msg.arbitration_id) + if msg.is_error_frame: + eol = "\n" + else: + eol = " R\n" if msg.is_rx else " T\n" + if msg.is_remote_frame: - framestr += "R\n" + framestr += f"R{eol}" else: if msg.is_fd: fd_flags = 0 @@ -174,6 +185,6 @@ def on_message_received(self, msg): if msg.error_state_indicator: fd_flags |= CANFD_ESI framestr += "#%X" % fd_flags - framestr += "%s\n" % (msg.data.hex().upper()) + framestr += f"{msg.data.hex().upper()}{eol}" self.file.write(framestr) diff --git a/test/test_logger.py b/test/test_logger.py index 07cf17d37..b694f06bb 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -132,7 +132,7 @@ def test_compressed_logfile(self): with gzip.open(self.testfile.name, "rt") as testlog: last_line = testlog.readlines()[-1] - self.assertEqual(last_line, "(0.000000) vcan0 00C0FFEE#0019000103010401\n") + self.assertEqual(last_line, "(0.000000) vcan0 00C0FFEE#0019000103010401 R\n") def tearDown(self) -> None: self.testfile.close() From 56fb131131fcf2a07f4aabc70fba711a7f7843a0 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Wed, 2 Mar 2022 18:01:15 +0100 Subject: [PATCH 0876/1235] [IO][canutils]: use common CAN interface names in generated logfile CAN interfaces in canutils logfiles are usually named 'can0', 'can32' or 'vcan8'. This allows to split logfiles just by performing 'grep' or to rename CAN interfaces easily with 'sed'. The current implementation of the canutils log file writer just provides channel numbers for CAN interfaces. This patch adds the string 'can' to the channel number to make it look like a usual canutils logfiles that can be preprocessed as described above. The string 'can' is added when the provided CAN channel is only a number. Author: Brian Thorne Suggested-by: Oliver Hartkopp Tested-by: Oliver Hartkopp --- can/io/canutils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/io/canutils.py b/can/io/canutils.py index fa96fcb9d..0cca82eb8 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -160,6 +160,8 @@ def on_message_received(self, msg): timestamp = msg.timestamp channel = msg.channel if msg.channel is not None else self.channel + if isinstance(channel, int) or isinstance(channel, str) and channel.isdigit(): + channel = f"can{channel}" framestr = "(%f) %s" % (timestamp, channel) From 8ffdcbcbf3361ac937ab8cf9f5cd6709733c4e16 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 8 Mar 2022 11:16:54 +0100 Subject: [PATCH 0877/1235] Fix BLF timestamp conversion (#1273) * fix rounding error * fix test and add type annotations --- can/io/blf.py | 29 ++++++++++++++++++----------- test/logformats_test.py | 13 +++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index e7be8979f..efeba9488 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -17,7 +17,7 @@ import datetime import time import logging -from typing import List, BinaryIO, Generator, Union +from typing import List, BinaryIO, Generator, Union, Tuple, Optional, cast from ..message import Message from ..util import len2dlc, dlc2len, channel2int @@ -25,6 +25,9 @@ from .generic import FileIOMessageWriter, MessageReader +TSystemTime = Tuple[int, int, int, int, int, int, int, int] + + class BLFParseError(Exception): """BLF file could not be parsed correctly.""" @@ -97,11 +100,11 @@ class BLFParseError(Exception): TIME_ONE_NANS = 0x00000002 -def timestamp_to_systemtime(timestamp): +def timestamp_to_systemtime(timestamp: float) -> TSystemTime: if timestamp is None or timestamp < 631152000: # Probably not a Unix timestamp - return (0, 0, 0, 0, 0, 0, 0, 0) - t = datetime.datetime.fromtimestamp(timestamp) + return 0, 0, 0, 0, 0, 0, 0, 0 + t = datetime.datetime.fromtimestamp(round(timestamp, 3)) return ( t.year, t.month, @@ -110,11 +113,11 @@ def timestamp_to_systemtime(timestamp): t.hour, t.minute, t.second, - int(round(t.microsecond / 1000.0)), + t.microsecond // 1000, ) -def systemtime_to_timestamp(systemtime): +def systemtime_to_timestamp(systemtime: TSystemTime) -> float: try: t = datetime.datetime( systemtime[0], @@ -125,7 +128,7 @@ def systemtime_to_timestamp(systemtime): systemtime[6], systemtime[7] * 1000, ) - return time.mktime(t.timetuple()) + systemtime[7] / 1000.0 + return t.timestamp() except ValueError: return 0 @@ -154,8 +157,8 @@ def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None: self.file_size = header[10] self.uncompressed_size = header[11] self.object_count = header[12] - self.start_timestamp = systemtime_to_timestamp(header[14:22]) - self.stop_timestamp = systemtime_to_timestamp(header[22:30]) + self.start_timestamp = systemtime_to_timestamp(cast(TSystemTime, header[14:22])) + self.stop_timestamp = systemtime_to_timestamp(cast(TSystemTime, header[22:30])) # Read rest of header self.file.read(header[1] - FILE_HEADER_STRUCT.size) self._tail = b"" @@ -405,8 +408,12 @@ def __init__( raise BLFParseError("Unexpected file format") self.uncompressed_size = header[11] self.object_count = header[12] - self.start_timestamp = systemtime_to_timestamp(header[14:22]) - self.stop_timestamp = systemtime_to_timestamp(header[22:30]) + self.start_timestamp: Optional[float] = systemtime_to_timestamp( + cast(TSystemTime, header[14:22]) + ) + self.stop_timestamp: Optional[float] = systemtime_to_timestamp( + cast(TSystemTime, header[22:30]) + ) # Jump to the end of the file self.file.seek(0, 2) else: diff --git a/test/logformats_test.py b/test/logformats_test.py index d7fb92f81..eb8984ef6 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -20,6 +20,7 @@ from datetime import datetime import can +from can.io import blf from .data.example_data import ( TEST_MESSAGES_BASE, @@ -659,6 +660,18 @@ def test_can_error_frame_ext(self): self.assertMessagesEqual(actual, [expected] * 2) self.assertEqual(actual[0].channel, expected.channel) + def test_timestamp_to_systemtime(self): + self.assertAlmostEqual( + 1636485425.999, + blf.systemtime_to_timestamp(blf.timestamp_to_systemtime(1636485425.998908)), + places=3, + ) + self.assertAlmostEqual( + 1636485426.0, + blf.systemtime_to_timestamp(blf.timestamp_to_systemtime(1636485425.999908)), + places=3, + ) + class TestCanutilsFileFormat(ReaderWriterTest): """Tests can.CanutilsLogWriter and can.CanutilsLogReader""" From b92ee5ad25a55e78638306071d65f60de82c46f8 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 9 Mar 2022 08:54:34 +0100 Subject: [PATCH 0878/1235] Fix channel2int conversion (#1269) * make regex dot non-greedy * Update test/test_util.py Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/util.py | 2 +- test/test_util.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/can/util.py b/can/util.py index 9400259ce..a9d08c469 100644 --- a/can/util.py +++ b/can/util.py @@ -292,7 +292,7 @@ def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]: if isinstance(channel, int): return channel if isinstance(channel, str): - match = re.match(r".*(\d+)$", channel) + match = re.match(r".*?(\d+)$", channel) if match: return int(match.group(1)) return None diff --git a/test/test_util.py b/test/test_util.py index 5768da282..e151e3d63 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -3,7 +3,7 @@ import unittest import warnings -from can.util import _create_bus_config, _rename_kwargs +from can.util import _create_bus_config, _rename_kwargs, channel2int class RenameKwargsTest(unittest.TestCase): @@ -64,3 +64,15 @@ def test_timing_can_use_int(self): _create_bus_config({**self.base_config, **timing_conf}) except TypeError as e: self.fail(e) + + +class TestChannel2Int(unittest.TestCase): + def test_channel2int(self) -> None: + self.assertEqual(0, channel2int("can0")) + self.assertEqual(0, channel2int("vcan0")) + self.assertEqual(1, channel2int("vcan1")) + self.assertEqual(12, channel2int("vcan12")) + self.assertEqual(3, channel2int(3)) + self.assertEqual(42, channel2int("42")) + self.assertEqual(None, channel2int("can")) + self.assertEqual(None, channel2int("can0a")) From ae0bc1746c04b3c1ccd8b56aba99895d50c87d2d Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 9 Mar 2022 21:44:16 +0100 Subject: [PATCH 0879/1235] Update the black formatter to stable release It is finally stable and we should therefore use that version from now on. --- can/interfaces/socketcan/constants.py | 4 ++-- can/interfaces/socketcan/socketcan.py | 2 +- can/interfaces/systec/ucan.py | 2 +- can/interfaces/vector/canlib.py | 2 +- requirements-lint.txt | 2 +- test/data/example_data.py | 2 +- test/network_test.py | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index 37d4847c4..3144a2cfa 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -58,8 +58,8 @@ CANFD_MTU = 72 -STD_ACCEPTANCE_MASK_ALL_BITS = 2 ** 11 - 1 +STD_ACCEPTANCE_MASK_ALL_BITS = 2**11 - 1 MAX_11_BIT_ID = STD_ACCEPTANCE_MASK_ALL_BITS -EXT_ACCEPTANCE_MASK_ALL_BITS = 2 ** 29 - 1 +EXT_ACCEPTANCE_MASK_ALL_BITS = 2**29 - 1 MAX_29_BIT_ID = EXT_ACCEPTANCE_MASK_ALL_BITS diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 5355378ab..082bbcf19 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -829,7 +829,7 @@ def _send_periodic_internal( def _get_next_task_id(self) -> int: with self._task_id_guard: - self._task_id = (self._task_id + 1) % (2 ** 32 - 1) + self._task_id = (self._task_id + 1) % (2**32 - 1) return self._task_id def _get_bcm_socket(self, channel: str) -> socket.socket: diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index 6e150f7b1..a6de4e9f5 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -120,7 +120,7 @@ def check_result(result, func, arguments): try: # Select the proper dll architecture - lib = WinDLL("usbcan64.dll" if sys.maxsize > 2 ** 32 else "usbcan32.dll") + lib = WinDLL("usbcan64.dll" if sys.maxsize > 2**32 else "usbcan32.dll") # BOOL PUBLIC UcanSetDebugMode (DWORD dwDbgLevel_p, _TCHAR* pszFilePathName_p, DWORD dwFlags_p); UcanSetDebugMode = lib.UcanSetDebugMode diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 4a90ea1bc..9cecaa83d 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -82,7 +82,7 @@ def __init__( poll_interval: float = 0.01, receive_own_messages: bool = False, bitrate: Optional[int] = None, - rx_queue_size: int = 2 ** 14, + rx_queue_size: int = 2**14, app_name: Optional[str] = "CANalyzer", serial: Optional[int] = None, fd: bool = False, diff --git a/requirements-lint.txt b/requirements-lint.txt index e9ad9106c..68caecd03 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,5 +1,5 @@ pylint==2.12.2 -black==21.12b0 +black~=22.1.0 mypy==0.931 mypy-extensions==0.4.3 types-setuptools diff --git a/test/data/example_data.py b/test/data/example_data.py index d41544334..0fa70993a 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -179,7 +179,7 @@ def generate_message(arbitration_id): Generates a new message with the given ID, some random data and a non-extended ID. """ - data = bytearray([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) + data = bytearray([random.randrange(0, 2**8 - 1) for _ in range(8)]) return Message( arbitration_id=arbitration_id, data=data, diff --git a/test/network_test.py b/test/network_test.py index a4e40e901..5900cd10f 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -38,7 +38,7 @@ class ControllerAreaNetworkTestCase(unittest.TestCase): ids = list(range(num_messages)) data = list( - bytearray([random.randrange(0, 2 ** 8 - 1) for a in range(random.randrange(9))]) + bytearray([random.randrange(0, 2**8 - 1) for a in range(random.randrange(9))]) for b in range(num_messages) ) From af55b0a33d30096cb001c168dafb0c3b81ac9fcc Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 14 Mar 2022 08:34:21 -0400 Subject: [PATCH 0880/1235] %d format is for a number, not str (#1281) --- can/interfaces/neousys/neousys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index 05e1a4418..57f947aa4 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -131,7 +131,7 @@ class NeousysCanBitClk(Structure): NEOUSYS_CANLIB = CDLL("libwdt_dio.so") logger.info("Loaded Neousys WDT_DIO Can driver") except OSError as error: - logger.info("Cannot load Neousys CAN bus dll or shared object: %d", format(error)) + logger.info("Cannot load Neousys CAN bus dll or shared object: %s", error) class NeousysBus(BusABC): From ed6cd668b9d81df27bcc6a747047d9cba228a682 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 20 Apr 2022 09:19:01 +0200 Subject: [PATCH 0881/1235] fix #1292 (#1293) --- can/interfaces/iscan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index 0e51ddd12..e76d9d060 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -187,12 +187,12 @@ class IscanError(CanError): def __init__(self, function, error_code: int, arguments) -> None: try: - description = ": " + self.ERROR_CODES[self.error_code] + description = ": " + self.ERROR_CODES[error_code] except KeyError: description = "" super().__init__( - f"Function {self.function.__name__} failed{description}", + f"Function {function.__name__} failed{description}", error_code=error_code, ) From fe18a34be7e2c0f8aae073663e4e3dc73f74f47d Mon Sep 17 00:00:00 2001 From: chrisoro <4160557+chrisoro@users.noreply.github.com> Date: Wed, 20 Apr 2022 13:27:16 +0200 Subject: [PATCH 0882/1235] add missing vector devices (#1296) --- can/interfaces/vector/xldefine.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index be40a15a9..032f08318 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -283,9 +283,11 @@ class XL_HardwareType(IntEnum): XL_HWTYPE_VN1640 = 59 XL_HWTYPE_VN8970 = 61 XL_HWTYPE_VN1611 = 63 + XL_HWTYPE_VN5240 = 64 XL_HWTYPE_VN5610 = 65 XL_HWTYPE_VN5620 = 66 XL_HWTYPE_VN7570 = 67 + XL_HWTYPE_VN5650 = 68 XL_HWTYPE_IPCLIENT = 69 XL_HWTYPE_IPSERVER = 71 XL_HWTYPE_VX1121 = 73 From 638d81ac361aef9334b14748a02667d53bf9c987 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 29 Apr 2022 13:08:40 +0200 Subject: [PATCH 0883/1235] Update black to fix CI --- requirements-lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index 68caecd03..f62eeb189 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,5 +1,5 @@ pylint==2.12.2 -black~=22.1.0 +black~=22.3.0 mypy==0.931 mypy-extensions==0.4.3 types-setuptools From 19cf49a728e8b0b0818336c298a6a7bcc1f0e0c7 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Sat, 30 Apr 2022 01:22:49 +0200 Subject: [PATCH 0884/1235] Fix timestamp handling in udp_multicast on macOS (#1278) * Fix timestamp handling in udp_multicast on macOS * Fix import on Windows * Fix test conditions for BasicTestUdpMulticastBusIPv4/6 * Fix attribute init * Fix exceptions (don't use asserts, always raise CAN errors) --- can/interfaces/udp_multicast/bus.py | 95 ++++++++++++++++++++++------- doc/interfaces/udp_multicast.rst | 2 +- test/back2back_test.py | 6 +- 3 files changed, 76 insertions(+), 27 deletions(-) diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 8fc286627..6b7e57bd9 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -1,8 +1,14 @@ +import errno import logging import select import socket import struct +try: + from fcntl import ioctl +except ModuleNotFoundError: # Missing on Windows + pass + from typing import List, Optional, Tuple, Union log = logging.getLogger(__name__) @@ -21,6 +27,7 @@ # Additional constants for the interaction with Unix kernels SO_TIMESTAMPNS = 35 +SIOCGSTAMP = 0x8906 class UdpMulticastBus(BusABC): @@ -174,6 +181,9 @@ def __init__( self.hop_limit = hop_limit self.max_buffer = max_buffer + # `False` will always work, no matter the setup. This might be changed by _create_socket(). + self.timestamp_nanosecond = False + # Look up multicast group address in name server and find out IP version of the first suitable target # and then get the address family of it (socket.AF_INET or socket.AF_INET6) connection_candidates = socket.getaddrinfo( # type: ignore @@ -200,8 +210,15 @@ def __init__( # used in recv() self.received_timestamp_struct = "@ll" - ancillary_data_size = struct.calcsize(self.received_timestamp_struct) - self.received_ancillary_buffer_size = socket.CMSG_SPACE(ancillary_data_size) + self.received_timestamp_struct_size = struct.calcsize( + self.received_timestamp_struct + ) + if self.timestamp_nanosecond: + self.received_ancillary_buffer_size = socket.CMSG_SPACE( + self.received_timestamp_struct_size + ) + else: + self.received_ancillary_buffer_size = 0 # used by send() self._send_destination = (self.group, self.port) @@ -238,7 +255,15 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # set how to receive timestamps - sock.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) + try: + sock.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) + except OSError as error: + if error.errno == errno.ENOPROTOOPT: # It is unavailable on macOS + self.timestamp_nanosecond = False + else: + raise error + else: + self.timestamp_nanosecond = True # Bind it to the port (on any interface) sock.bind(("", self.port)) @@ -272,18 +297,22 @@ def send(self, data: bytes, timeout: Optional[float] = None) -> None: :param timeout: the timeout in seconds after which an Exception is raised is sending has failed :param data: the data to be sent - :raises OSError: if an error occurred while writing to the underlying socket - :raises socket.timeout: if the timeout ran out before sending was completed (this is a subclass of - *OSError*) + :raises can.CanOperationError: if an error occurred while writing to the underlying socket + :raises can.CanTimeoutError: if the timeout ran out before sending was completed """ if timeout != self._last_send_timeout: self._last_send_timeout = timeout # this applies to all blocking calls on the socket, but sending is the only one that is blocking self._socket.settimeout(timeout) - bytes_sent = self._socket.sendto(data, self._send_destination) - if bytes_sent < len(data): - raise socket.timeout() + try: + bytes_sent = self._socket.sendto(data, self._send_destination) + if bytes_sent < len(data): + raise TimeoutError() + except TimeoutError: + raise can.CanTimeoutError() from None + except OSError as error: + raise can.CanOperationError("failed to send via socket") from error def recv( self, timeout: Optional[float] = None @@ -320,21 +349,41 @@ def recv( self.max_buffer, self.received_ancillary_buffer_size ) - # fetch timestamp; this is configured in in _create_socket() - assert len(ancillary_data) == 1, "only requested a single extra field" - cmsg_level, cmsg_type, cmsg_data = ancillary_data[0] - assert ( - cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS - ), "received control message type that was not requested" - # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details - seconds, nanoseconds = struct.unpack( - self.received_timestamp_struct, cmsg_data - ) - if nanoseconds >= 1e9: - raise can.CanError( - f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9" + # fetch timestamp; this is configured in _create_socket() + if self.timestamp_nanosecond: + # Very similar to timestamp handling in can/interfaces/socketcan/socketcan.py -> capture_message() + if len(ancillary_data) != 1: + raise can.CanOperationError( + "Only requested a single extra field but got a different amount" + ) + cmsg_level, cmsg_type, cmsg_data = ancillary_data[0] + if cmsg_level != socket.SOL_SOCKET or cmsg_type != SO_TIMESTAMPNS: + raise can.CanOperationError( + "received control message type that was not requested" + ) + # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details + seconds, nanoseconds = struct.unpack( + self.received_timestamp_struct, cmsg_data + ) + if nanoseconds >= 1e9: + raise can.CanOperationError( + f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9" + ) + timestamp = seconds + nanoseconds * 1.0e-9 + else: + result_buffer = ioctl( + self._socket.fileno(), + SIOCGSTAMP, + bytes(self.received_timestamp_struct_size), + ) + seconds, microseconds = struct.unpack( + self.received_timestamp_struct, result_buffer ) - timestamp = seconds + nanoseconds * 1.0e-9 + if microseconds >= 1e6: + raise can.CanOperationError( + f"Timestamp microseconds field was out of range: {microseconds} not less than 1e6" + ) + timestamp = seconds + microseconds * 1e-6 return raw_message_data, sender_address, timestamp diff --git a/doc/interfaces/udp_multicast.rst b/doc/interfaces/udp_multicast.rst index be5882f20..f2775727c 100644 --- a/doc/interfaces/udp_multicast.rst +++ b/doc/interfaces/udp_multicast.rst @@ -25,7 +25,7 @@ for specifying multicast IP addresses. Supported Platforms ------------------- -It should work on most Unix systems (including Linux with kernel 2.6.22+) but currently not on Windows. +It should work on most Unix systems (including Linux with kernel 2.6.22+ and macOS) but currently not on Windows. Example ------- diff --git a/test/back2back_test.py b/test/back2back_test.py index 479274343..b5ae4e27c 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -285,7 +285,7 @@ class BasicTestSocketCan(Back2BackTestCase): # this doesn't even work on Travis CI for macOS; for example, see # https://travis-ci.org/github/hardbyte/python-can/jobs/745389871 @unittest.skipUnless( - IS_UNIX and not IS_OSX, + IS_UNIX and not (IS_CI and IS_OSX), "only supported on Unix systems (but not on macOS at Travis CI and GitHub Actions)", ) class BasicTestUdpMulticastBusIPv4(Back2BackTestCase): @@ -303,8 +303,8 @@ def test_unique_message_instances(self): # this doesn't even work for loopback multicast addresses on Travis CI; for example, see # https://travis-ci.org/github/hardbyte/python-can/builds/745065503 @unittest.skipUnless( - IS_UNIX and not (IS_TRAVIS or IS_OSX), - "only supported on Unix systems (but not on Travis CI; and not an macOS at GitHub Actions)", + IS_UNIX and not (IS_TRAVIS or (IS_CI and IS_OSX)), + "only supported on Unix systems (but not on Travis CI; and not on macOS at GitHub Actions)", ) class BasicTestUdpMulticastBusIPv6(Back2BackTestCase): From 1693c27ea751958260a9c162727af1790c28b006 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 30 Apr 2022 21:34:20 +0200 Subject: [PATCH 0885/1235] test python 3.11 (#1302) --- .github/workflows/build.yml | 14 +++++++------- test/test_viewer.py | 15 ++++++++------- tox.ini | 5 +++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 993247868..7e37b4ad5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,16 +15,16 @@ jobs: experimental: [false] python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"] # Do not test on Python 3.11 pre-releases since wrapt causes problems: https://github.com/GrahamDumpleton/wrapt/issues/196 - # include: + include: # Only test on a single configuration while there are just pre-releases - # - os: ubuntu-latest - # experimental: true - # python-version: "3.11.0-alpha.3" + - os: ubuntu-latest + experimental: true + python-version: "3.11.0-alpha - 3.11.0" fail-fast: false steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -44,7 +44,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: "3.10" - name: Install dependencies @@ -78,7 +78,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: "3.10" - name: Install dependencies diff --git a/test/test_viewer.py b/test/test_viewer.py index 20c3d2faa..5633a3bc1 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -37,7 +37,7 @@ import can from can.viewer import CanViewer, parse_args - +from test.config import IS_CI # Allow the curses module to be missing (e.g. on PyPy on Windows) try: @@ -251,9 +251,10 @@ def test_receive(self): if _id["dt"] == 0: self.assertEqual(_id["count"], 1) else: - self.assertTrue( - pytest.approx(_id["dt"], 0.1) - ) # dt should be ~0.1 s + if not IS_CI: # do not test timing in CI + assert _id["dt"] == pytest.approx( + 0.1, abs=5e-2 + ) # dt should be ~0.1 s self.assertEqual(_id["count"], 2) else: # Make sure dt is 0 @@ -347,7 +348,7 @@ def test_pack_unpack(self): raw_data = self.pack_data(CANOPEN_TPDO2 + 1, data_structs, 12.34, 4.5, 6) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO2 + 1, data_structs, raw_data) - self.assertTrue(pytest.approx(parsed_data, [12.34, 4.5, 6])) + assert parsed_data == pytest.approx([12.34, 4.5, 6]) self.assertTrue( isinstance(parsed_data[0], float) and isinstance(parsed_data[1], float) @@ -356,14 +357,14 @@ def test_pack_unpack(self): raw_data = self.pack_data(CANOPEN_TPDO3 + 1, data_structs, 123.45, 67.89) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO3 + 1, data_structs, raw_data) - self.assertTrue(pytest.approx(parsed_data, [123.45, 67.89])) + assert parsed_data == pytest.approx([123.45, 67.89]) self.assertTrue(all(isinstance(d, float) for d in parsed_data)) raw_data = self.pack_data( CANOPEN_TPDO4 + 1, data_structs, math.pi / 2.0, math.pi ) parsed_data = CanViewer.unpack_data(CANOPEN_TPDO4 + 1, data_structs, raw_data) - self.assertTrue(pytest.approx(parsed_data, [math.pi / 2.0, math.pi])) + assert parsed_data == pytest.approx([math.pi / 2.0, math.pi]) self.assertTrue(all(isinstance(d, float) for d in parsed_data)) raw_data = self.pack_data(CANOPEN_TPDO1 + 2, data_structs) diff --git a/tox.ini b/tox.ini index 6b407dfeb..248a6fd37 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] +isolated_build = true [testenv] deps = - pytest==6.2.*,>=6.2.5 + pytest==7.1.*,>=7.1.2 pytest-timeout==2.0.2 pytest-cov==3.0.0 - coverage==6.2 + coverage==6.3 codecov==2.1.12 hypothesis~=6.35.0 pyserial~=3.5 From 796b52586a5d891e4e2077b423de40cba07f8b44 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 2 May 2022 18:35:29 -0400 Subject: [PATCH 0886/1235] Default mode for FileIOMessageWriter should be wt (#1303) Default mode for FileIOMessageWriter should be `wt` instead of `rt`. --- can/io/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/generic.py b/can/io/generic.py index 6f18fbe65..b45e4dd1f 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -82,7 +82,7 @@ class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): file: can.typechecking.FileLike - def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None: + def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "wt") -> None: # Not possible with the type signature, but be verbose for user-friendliness if file is None: raise ValueError("The given file cannot be None") From 4cb2f2f6fbf6bd378a4e804b5c737a6128caea1d Mon Sep 17 00:00:00 2001 From: Gonzalo Ribera Date: Wed, 25 May 2022 06:05:36 -0300 Subject: [PATCH 0887/1235] Fix fileno error on Windows (robotell bus) (#1313) * Fix fileno error on Windows (robotell bus) * Fix format * Add test * format --- can/interfaces/robotell.py | 11 ++++++++--- test/test_robotell.py | 4 ++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 88cee26d1..709fad78d 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -2,6 +2,7 @@ Interface for Chinese Robotell compatible interfaces (win32/linux). """ +import io import time import logging @@ -367,10 +368,14 @@ def shutdown(self): self.serialPortOrig.close() def fileno(self): - if hasattr(self.serialPortOrig, "fileno"): + try: return self.serialPortOrig.fileno() - # Return an invalid file descriptor on Windows - return -1 + except io.UnsupportedOperation: + raise NotImplementedError( + "fileno is not implemented using current CAN bus on this platform" + ) + except Exception as exception: + raise CanOperationError("Cannot fetch fileno") from exception def get_serial_number(self, timeout): """Get serial number of the slcan interface. diff --git a/test/test_robotell.py b/test/test_robotell.py index 86e053f2d..8250b7ada 100644 --- a/test/test_robotell.py +++ b/test/test_robotell.py @@ -940,6 +940,10 @@ def test_set_hw_filter(self): ), ) + def test_when_no_fileno(self): + with self.assertRaises(NotImplementedError): + self.bus.fileno() + if __name__ == "__main__": unittest.main() From 97302b9db9ff621a97fbe7a280ec12a261bbff20 Mon Sep 17 00:00:00 2001 From: Jurgis Date: Wed, 1 Jun 2022 10:13:12 +0300 Subject: [PATCH 0888/1235] Improve gs_usb usability and fix loopback frames (#1270) * Improve gs_usb usability and fix loopback frames * Fix extended id parsing * Fix formatting --- can/interfaces/gs_usb.py | 39 ++++++++++++++++++++++++++++++++------- doc/interfaces/gs_usb.rst | 10 +++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 7731d797d..185d28acf 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -1,7 +1,7 @@ from typing import Optional, Tuple from gs_usb.gs_usb import GsUsb -from gs_usb.gs_usb_frame import GsUsbFrame +from gs_usb.gs_usb_frame import GsUsbFrame, GS_USB_NONE_ECHO_ID from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC import can import usb @@ -14,17 +14,42 @@ class GsUsbBus(can.BusABC): - def __init__(self, channel, bus, address, bitrate, can_filters=None, **kwargs): + def __init__( + self, + channel, + bitrate, + index=None, + bus=None, + address=None, + can_filters=None, + **kwargs, + ): """ :param channel: usb device name + :param index: device number if using automatic scan, starting from 0. + If specified, bus/address shall not be provided. :param bus: number of the bus that the device is connected to :param address: address of the device on the bus it is connected to :param can_filters: not supported :param bitrate: CAN network bandwidth (bits/s) """ - gs_usb = GsUsb.find(bus=bus, address=address) - if not gs_usb: - raise CanInitializationError(f"Cannot find device {channel}") + if (index is not None) and ((bus or address) is not None): + raise CanInitializationError( + f"index and bus/address cannot be used simultaneously" + ) + + if index is not None: + devs = GsUsb.scan() + if len(devs) <= index: + raise CanInitializationError( + f"Cannot find device {index}. Devices found: {len(devs)}" + ) + gs_usb = devs[index] + else: + gs_usb = GsUsb.find(bus=bus, address=address) + if not gs_usb: + raise CanInitializationError(f"Cannot find device {channel}") + self.gs_usb = gs_usb self.channel_info = channel @@ -100,13 +125,13 @@ def _recv_internal( msg = can.Message( timestamp=frame.timestamp, arbitration_id=frame.arbitration_id, - is_extended_id=frame.can_dlc, + is_extended_id=frame.is_extended_id, is_remote_frame=frame.is_remote_frame, is_error_frame=frame.is_error_frame, channel=self.channel_info, dlc=frame.can_dlc, data=bytearray(frame.data)[0 : frame.can_dlc], - is_rx=True, + is_rx=frame.echo_id == GS_USB_NONE_ECHO_ID, ) return msg, False diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index aae6c39f5..232786fb7 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -7,7 +7,15 @@ Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schne Install: ``pip install "python-can[gs_usb]"`` -Usage: pass ``bus`` and ``address`` to open the device. The parameters can be got by ``pyusb`` as shown below: +Usage: pass device ``index`` (starting from 0) if using automatic device detection: + +:: + + import can + + bus = can.Bus(bustype="gs_usb", channel=dev.product, index=0, bitrate=250000) + +Alternatively, pass ``bus`` and ``address`` to open a specific device. The parameters can be got by ``pyusb`` as shown below: :: From 47f673b47dcffa335ee10016052475f8af534b3d Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Wed, 1 Jun 2022 03:03:15 -0500 Subject: [PATCH 0889/1235] Remove redundant writer.stop call that throws error (#1317) * Remove redundant writer.stop call that throws error The issue is that the writer has already previously been told to stop. * Remove commented lines and update docstring The _get_new_writer function was previously calling `self._writer.close()`. The `self.writer` function is already being closed in the call stack. Due to `self.writer` and `self._writer` being linked, there was an error that the IO file was closed. The `self._writer.close()` was commented out in a prior commit as a proposal for the change. That comment is now removed. The docstring is updated. --- can/io/logger.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index cbed054ac..37ba1e736 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -208,7 +208,10 @@ def on_message_received(self, msg: Message) -> None: self.writer.on_message_received(msg) def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: - """Instantiate a new writer after stopping the old one. + """Instantiate a new writer. + + .. note:: + The :attr:`self.writer` should be closed prior to calling this function. :param filename: Path-like object that specifies the location and name of the log file. @@ -216,9 +219,6 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: :return: An instance of a writer class. """ - # Close the old writer first - if self._writer is not None: - self._writer.stop() logger = Logger(filename, *self.writer_args, **self.writer_kwargs) if isinstance(logger, FileIOMessageWriter): From 0d7f65f74b8b015fb650e9910a920323023f310d Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 1 Jun 2022 16:07:11 +0200 Subject: [PATCH 0890/1235] Clean up comment after !1302 (#1322) --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e37b4ad5..5365620cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,6 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] experimental: [false] python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"] - # Do not test on Python 3.11 pre-releases since wrapt causes problems: https://github.com/GrahamDumpleton/wrapt/issues/196 include: # Only test on a single configuration while there are just pre-releases - os: ubuntu-latest From 0c4dee01a00a9bdd82b71071627b8f2fd2fc929d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 7 Jun 2022 08:49:57 +0200 Subject: [PATCH 0891/1235] fix #1299 (#1301) --- can/io/asc.py | 4 ++-- test/data/issue_1299.asc | 11 +++++++++++ test/logformats_test.py | 3 +++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 test/data/issue_1299.asc diff --git a/can/io/asc.py b/can/io/asc.py index 1a97f6b72..3a320f007 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -202,9 +202,9 @@ def _process_classic_can_frame( _, dlc_str = rest_of_message.split(None, 1) data = "" - dlc = int(dlc_str, self._converted_base) + dlc = dlc2len(int(dlc_str, self._converted_base)) msg_kwargs["dlc"] = dlc - self._process_data_string(data, dlc, msg_kwargs) + self._process_data_string(data, min(8, dlc), msg_kwargs) return Message(**msg_kwargs) diff --git a/test/data/issue_1299.asc b/test/data/issue_1299.asc new file mode 100644 index 000000000..43c29302e --- /dev/null +++ b/test/data/issue_1299.asc @@ -0,0 +1,11 @@ +date Thu Apr 28 10:44:52.480 am 2022 +base hex timestamps absolute +internal events logged +// version 12.0.0 +Begin TriggerBlock Thu Apr 28 10:44:52.480 am 2022 + 0.000000 Start of measurement + 13.258199 1 180 Tx d 8 6A 00 00 00 00 00 00 00 Length = 244016 BitCount = 125 ID = 384 + 13.258433 1 221 Tx d 8 C2 4A 05 81 00 00 15 10 Length = 228016 BitCount = 117 ID = 545 + 13.258671 1 3FF Tx d D 55 AA 01 02 03 04 05 06 Length = 232016 BitCount = 119 ID = 1023 + 13.258907 1 F4 Tx d 8 8A 1A 0D F2 13 00 00 07 Length = 230016 BitCount = 118 ID = 244 +End TriggerBlock diff --git a/test/logformats_test.py b/test/logformats_test.py index eb8984ef6..49e9c563e 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -561,6 +561,9 @@ def test_ignore_comments(self): def test_no_triggerblock(self): _msg_list = self._read_log_file("issue_1256.asc") + def test_can_dlc_greater_than_8(self): + _msg_list = self._read_log_file("issue_1299.asc") + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From 14301543424a490d1607b2d0ed56c059cdff3d09 Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Fri, 10 Jun 2022 02:28:06 -0500 Subject: [PATCH 0892/1235] Enhance `can.logger` to consider the `append` option (#1327) * Add passage of **options to the logger initialization * Add -a boolean option to the parser * Add manual arg parse for boolean variables This solution is only required until Python>=3.9. * Fix the index-error check * Bolster help documentation for new -a option * Clean up variable names, notes, etc. * Whitespace formatting (PEP-8 style) * Make formatting recomendations based on `pylint logger.py` * Format code with black logger.py to stop failing the `black` check * Reduce complexity of append arg parse incorporation * Fix format with `black can/logger.py` * Change append argparse access from `results.a` to `results.append` * Add `dest` option to append argument --- can/logger.py | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/can/logger.py b/can/logger.py index 053001968..3594427e6 100644 --- a/can/logger.py +++ b/can/logger.py @@ -31,9 +31,10 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument( "-c", "--channel", - help='''Most backend interfaces require some sort of channel. - For example with the serial interface the channel might be a rfcomm device: "/dev/rfcomm0" - With the socketcan interfaces valid channel examples include: "can0", "vcan0"''', + help=r"Most backend interfaces require some sort of channel. For " + r"example with the serial interface the channel might be a rfcomm" + r' device: "/dev/rfcomm0". With the socketcan interface valid ' + r'channel examples include: "can0", "vcan0".', ) parser.add_argument( @@ -60,11 +61,10 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument( "extra_args", nargs=argparse.REMAINDER, - help="""\ - The remaining arguments will be used for the interface initialisation. - For example, `-i vector -c 1 --app-name=MyCanApp` is the equivalent to - opening the bus with `Bus('vector', channel=1, app_name='MyCanApp')` - """, + help=r"The remaining arguments will be used for the interface " + r"initialisation. For example, `-i vector -c 1 --app-name=" + r"MyCanApp` is the equivalent to opening the bus with `Bus(" + r"'vector', channel=1, app_name='MyCanApp')", ) @@ -82,8 +82,10 @@ def _append_filter_argument( *args, "--filter", help="R|Space separated CAN filters for the given CAN interface:" - "\n : (matches when & mask == can_id & mask)" - "\n ~ (matches when & mask != can_id & mask)" + "\n : (matches when & mask ==" + " can_id & mask)" + "\n ~ (matches when & mask !=" + " can_id & mask)" "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" "\n python -m can.viewer -f 100:7FC 200:7F0" "\nNote that the ID and mask are always interpreted as hex values", @@ -141,7 +143,8 @@ def _parse_additonal_config(unknown_args): def main() -> None: parser = argparse.ArgumentParser( - description="Log CAN traffic, printing messages to stdout or to a given file.", + description="Log CAN traffic, printing messages to stdout or to a " + "given file.", ) _create_base_argument_parser(parser) @@ -154,12 +157,21 @@ def main() -> None: default=None, ) + parser.add_argument( + "-a", + "--append", + dest="append", + help="Append to the log file if it already exists.", + action="store_true", + ) + parser.add_argument( "-s", "--file_size", dest="file_size", type=int, - help="Maximum file size in bytes. Rotate log file when size threshold is reached.", + help="Maximum file size in bytes. Rotate log file when size threshold " + "is reached.", default=None, ) @@ -201,12 +213,13 @@ def main() -> None: print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") print(f"Can Logger (Started on {datetime.now()})") + options = {"append": results.append} if results.file_size: logger = SizedRotatingLogger( - base_filename=results.log_file, max_bytes=results.file_size + base_filename=results.log_file, max_bytes=results.file_size, **options ) else: - logger = Logger(filename=results.log_file) # type: ignore + logger = Logger(filename=results.log_file, **options) # type: ignore try: while True: From cb3d4dcce7794ab3a22cf8f1e69aea33fa837c15 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 20 Jun 2022 14:12:08 +0200 Subject: [PATCH 0893/1235] fix broken badges --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 956033de1..0e5d79e3f 100644 --- a/README.rst +++ b/README.rst @@ -33,15 +33,15 @@ python-can :target: https://github.com/hardbyte/python-can/actions/workflows/build.yml :alt: Github Actions workflow status -.. |build_travis| image:: https://img.shields.io/travis/com/hardbyte/python-can/develop.svg?label=Travis%20CI - :target: https://travis-ci.com/hardbyte/python-can +.. |build_travis| image:: https://img.shields.io/travis/hardbyte/python-can/develop.svg?label=Travis%20CI + :target: https://app.travis-ci.com/github/hardbyte/python-can :alt: Travis CI Server for develop branch .. |coverage| image:: https://codecov.io/gh/hardbyte/python-can/branch/develop/graph/badge.svg :target: https://codecov.io/gh/hardbyte/python-can/branch/develop :alt: Test coverage reports on Codecov.io -.. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/hardbyte/python-can&style=flat +.. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/hardbyte/python-can&style=flat :target: https://mergify.io :alt: Mergify Status From ec064529b8acffe7f6d9a3494442b6b1c6152e97 Mon Sep 17 00:00:00 2001 From: MattWoodhead Date: Wed, 22 Jun 2022 09:38:37 +0100 Subject: [PATCH 0894/1235] Fix fileno error on Windows (Serial bus) (#1333) * Add conda-forge badge to readme * Update fileno method in serial_can.py Change implemented similar to that made in #1313. This means notifiers will now work with the Serial interface. * Format serial_test.py with black * Revert "Add conda-forge badge to readme" This reverts commit 59d0b3cbf876a16ae4781e607071665a2df7b7a4. --- can/interfaces/serial/serial_can.py | 11 ++++++++--- test/serial_test.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index c214d8559..ec4bb8671 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -7,6 +7,7 @@ See the interface documentation for the format being used. """ +import io import logging import struct from typing import Any, List, Tuple, Optional @@ -212,10 +213,14 @@ def _recv_internal( raise CanOperationError("could not read from serial") from error def fileno(self) -> int: - if hasattr(self._ser, "fileno"): + try: return self._ser.fileno() - # Return an invalid file descriptor on Windows - return -1 + except io.UnsupportedOperation: + raise NotImplementedError( + "fileno is not implemented using current CAN bus on this platform" + ) + except Exception as exception: + raise CanOperationError("Cannot fetch fileno") from exception @staticmethod def _detect_available_configs() -> List[AutoDetectedConfig]: diff --git a/test/serial_test.py b/test/serial_test.py index c9c035634..aa6c71994 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -136,6 +136,24 @@ def test_rx_tx_min_timestamp_error(self): msg = can.Message(timestamp=-1) self.assertRaises(ValueError, self.bus.send, msg) + def test_when_no_fileno(self): + """ + Tests for the fileno method catching the missing pyserial implementeation on the Windows platform + """ + try: + fileno = self.bus.fileno() + except NotImplementedError: + pass # allow it to be left non-implemented for Windows platform + else: + fileno.__gt__ = ( + lambda self, compare: True + ) # Current platform implements fileno, so get the mock to respond to a greater than comparison + self.assertIsNotNone(fileno) + self.assertFalse( + fileno == -1 + ) # forcing the value to -1 is the old way of managing fileno on Windows but it is not compatible with notifiers + self.assertTrue(fileno > 0) + class SimpleSerialTest(unittest.TestCase, SimpleSerialTestBase): def __init__(self, *args, **kwargs): From d3305225330c2585cf1671532344afe5500352ee Mon Sep 17 00:00:00 2001 From: tamenol <37591107+tamenol@users.noreply.github.com> Date: Wed, 22 Jun 2022 11:28:12 +0200 Subject: [PATCH 0895/1235] fix typing in add_listener and remove_listener (#1335) * fix typing in add_listener and remove_listener Use the union to typecheck these functions * add awaitable to MessageRecipient definition; remove unneeded cast * remove unused imports --- can/notifier.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index f7c004c4e..2adae431e 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -6,7 +6,7 @@ import logging import threading import time -from typing import Any, Callable, cast, Iterable, List, Optional, Union, Awaitable +from typing import Callable, Iterable, List, Optional, Union, Awaitable from can.bus import BusABC from can.listener import Listener @@ -14,7 +14,7 @@ logger = logging.getLogger("can.Notifier") -MessageRecipient = Union[Listener, Callable[[Message], None]] +MessageRecipient = Union[Listener, Callable[[Message], Union[Awaitable[None], None]]] class Notifier: @@ -140,7 +140,7 @@ def _on_message_available(self, bus: BusABC) -> None: def _on_message_received(self, msg: Message) -> None: for callback in self.listeners: - res = cast(Union[None, Optional[Awaitable[Any]]], callback(msg)) + res = callback(msg) if res is not None and self._loop is not None and asyncio.iscoroutine(res): # Schedule coroutine self._loop.create_task(res) @@ -166,7 +166,7 @@ def _on_error(self, exc: Exception) -> bool: return was_handled - def add_listener(self, listener: Listener) -> None: + def add_listener(self, listener: MessageRecipient) -> None: """Add new Listener to the notification list. If it is already present, it will be called two times each time a message arrives. @@ -175,7 +175,7 @@ def add_listener(self, listener: Listener) -> None: """ self.listeners.append(listener) - def remove_listener(self, listener: Listener) -> None: + def remove_listener(self, listener: MessageRecipient) -> None: """Remove a listener from the notification list. This method throws an exception if the given listener is not part of the stored listeners. From 5bca2d71a7a41692cb1899cd7daa2ecc3710e859 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Tue, 28 Jun 2022 14:52:47 -0400 Subject: [PATCH 0896/1235] Allow ICSApiError to be pickled and un-pickled (#1341) --- can/interfaces/ics_neovi/neovi_bus.py | 9 +++++++++ test/test_neovi.py | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 test/test_neovi.py diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 95a5bc4c5..5366f8155 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -95,6 +95,15 @@ def __init__( self.severity = severity self.restart_needed = restart_needed == 1 + def __reduce__(self): + return type(self), ( + self.error_code, + self.description_short, + self.description_long, + self.severity, + self.restart_needed, + ) + @property def error_number(self) -> int: """Deprecated. Renamed to :attr:`can.CanError.error_code`.""" diff --git a/test/test_neovi.py b/test/test_neovi.py new file mode 100644 index 000000000..181f92377 --- /dev/null +++ b/test/test_neovi.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +""" +import pickle +import unittest +from can.interfaces.ics_neovi import ICSApiError + + +class ICSApiErrorTest(unittest.TestCase): + def test_error_pickling(self): + iae = ICSApiError( + 0xF00, + "description_short", + "description_long", + severity=ICSApiError.ICS_SPY_ERR_CRITICAL, + restart_needed=1, + ) + pickled_iae = pickle.dumps(iae) + un_pickled_iae = pickle.loads(pickled_iae) + assert iae.__dict__ == un_pickled_iae.__dict__ + + +if __name__ == "__main__": + unittest.main() From 7b1893d4c7d5654426dcfec4ebcd28ecb5951a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?uml=C3=A4ute?= Date: Sun, 10 Jul 2022 16:05:18 +0200 Subject: [PATCH 0897/1235] Sort interface names, to make documentation reproducible (#1342) --- can/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/logger.py b/can/logger.py index 3594427e6..5aff5e4e1 100644 --- a/can/logger.py +++ b/can/logger.py @@ -43,7 +43,7 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: dest="interface", help="""Specify the backend CAN interface to use. If left blank, fall back to reading from configuration files.""", - choices=can.VALID_INTERFACES, + choices=sorted(can.VALID_INTERFACES), ) parser.add_argument( From f59fe4f8a1f27f202d1a4419b3153dccdfeca68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?uml=C3=A4ute?= Date: Mon, 11 Jul 2022 09:55:15 +0200 Subject: [PATCH 0898/1235] Exclude repository-configuration from git-archive (#1343) --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a661f6235 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +.git* export-ignore +.*.yml export-ignore From 3ae087963295cd08146cd6b377bcc1f401276018 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 13 Jul 2022 12:34:03 +0200 Subject: [PATCH 0899/1235] Add py.typed file and distribute it upon installation (#1344) --- can/py.typed | 0 setup.py | 1 + 2 files changed, 1 insertion(+) create mode 100644 can/py.typed diff --git a/can/py.typed b/can/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/setup.py b/setup.py index 9dd6153c7..c9defecab 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.md"], "doc": ["*.*"], "examples": ["*.py"], + "can": ["py.typed"], }, # Installation # see https://www.python.org/dev/peps/pep-0345/#version-specifiers From b9d9d01c1c87bc2a4d08b709a0763cdc850f8c35 Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Sun, 24 Jul 2022 19:16:58 +0200 Subject: [PATCH 0900/1235] Add device_id parameter to PcanBus constructor (#1346) * Add device_id parameter to PcanBus constructor The new device_id parameter can be used to select a PCAN channel based on the freely programmable device ID of a PCAN USB device. This change allows for a more deterministic channel selection since the device ID does not change between restarts. * Change wording of PCAN _find_channel_by_dev_id docstring Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Apply black code formatting Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/pcan/pcan.py | 41 ++++++++++++++++++++++++++++++++++++- test/test_pcan.py | 19 +++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index b8a0ebee5..2fea74e77 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -103,6 +103,7 @@ class PcanBus(BusABC): def __init__( self, channel="PCAN_USBBUS1", + device_id=None, state=BusState.ACTIVE, bitrate=500000, *args, @@ -119,6 +120,14 @@ def __init__( Alternatively the value can be an int with the numerical value. Default is 'PCAN_USBBUS1' + :param int device_id: + Select the PCAN interface based on its ID. The device ID is a 8/32bit + value that can be configured for each PCAN device. If you set the + device_id parameter, it takes precedence over the channel parameter. + The constructor searches all connected interfaces and initializes the + first one that matches the parameter value. If no device is found, + an exception is raised. + :param can.bus.BusState state: BusState of the channel. Default is ACTIVE @@ -198,6 +207,15 @@ def __init__( Ignored if not using CAN-FD. """ + self.m_objPCANBasic = PCANBasic() + + if device_id is not None: + channel = self._find_channel_by_dev_id(device_id) + + if channel is None: + err_msg = "Cannot find a channel with ID {:08x}".format(device_id) + raise ValueError(err_msg) + self.channel_info = str(channel) self.fd = kwargs.get("fd", False) pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K) @@ -209,7 +227,6 @@ def __init__( if not isinstance(channel, int): channel = PCAN_CHANNEL_NAMES[channel] - self.m_objPCANBasic = PCANBasic() self.m_PcanHandle = channel self.check_api_version() @@ -269,6 +286,28 @@ def __init__( super().__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs) + def _find_channel_by_dev_id(self, device_id): + """ + Iterate over all possible channels to find a channel that matches the device + ID. This method is somewhat brute force, but the Basic API only offers a + suitable API call since V4.4.0. + + :param device_id: The device_id for which to search for + :return: The name of a PCAN channel that matches the device ID, or None if + no channel can be found. + """ + for ch_name, ch_handle in PCAN_CHANNEL_NAMES.items(): + err, cur_dev_id = self.m_objPCANBasic.GetValue( + ch_handle, PCAN_DEVICE_NUMBER + ) + if err != PCAN_ERROR_OK: + continue + + if cur_dev_id == device_id: + return ch_name + + return None + def _get_formatted_error(self, error): """ Gets the text using the GetErrorText API function. diff --git a/test/test_pcan.py b/test/test_pcan.py index 6fc21184a..eba42d7e1 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -344,6 +344,25 @@ def test_status_string(self, name, status, expected_result) -> None: self.assertEqual(self.bus.status_string(), expected_result) self.mock_pcan.GetStatus.assert_called() + @parameterized.expand([(0x0, "error"), (0x42, "PCAN_USBBUS8")]) + def test_constructor_with_device_id(self, dev_id, expected_result): + def get_value_side_effect(handle, param): + if param == PCAN_API_VERSION: + return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii") + + if handle in (PCAN_USBBUS8, PCAN_USBBUS14): + return 0, 0x42 + else: + return PCAN_ERROR_ILLHW, 0x0 + + self.mock_pcan.GetValue = Mock(side_effect=get_value_side_effect) + + if expected_result == "error": + self.assertRaises(ValueError, can.Bus, bustype="pcan", device_id=dev_id) + else: + self.bus = can.Bus(bustype="pcan", device_id=dev_id) + self.assertEqual(expected_result, self.bus.channel_info) + if __name__ == "__main__": unittest.main() From 788dd048264ca4f82a2c8f994b6dad19655365de Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 6 Aug 2022 23:26:40 +0200 Subject: [PATCH 0901/1235] Detect types in _parse_additonal_config (#1328) * detect types in _parse_additonal_config * check argument format * fix typo * recognize extra_args * fix typo again --- can/logger.py | 37 ++++++++++++++++++++++++++++-------- can/player.py | 7 ++++--- can/viewer.py | 4 ++-- test/test_logger.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/can/logger.py b/can/logger.py index 5aff5e4e1..209c1381c 100644 --- a/can/logger.py +++ b/can/logger.py @@ -13,12 +13,12 @@ Dynamic Controls 2010 """ - +import re import sys import argparse from datetime import datetime import errno -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Union, Sequence, Tuple import can from . import Bus, BusState, Logger, SizedRotatingLogger @@ -134,11 +134,32 @@ def _parse_filters(parsed_args: Any) -> CanFilters: return can_filters -def _parse_additonal_config(unknown_args): - return dict( - (arg.split("=", 1)[0].lstrip("--").replace("-", "_"), arg.split("=", 1)[1]) - for arg in unknown_args - ) +def _parse_additional_config( + unknown_args: Sequence[str], +) -> Dict[str, Union[str, int, float, bool]]: + for arg in unknown_args: + if not re.match(r"^--[a-zA-Z\-]*?=\S*?$", arg): + raise ValueError(f"Parsing argument {arg} failed") + + def _split_arg(_arg: str) -> Tuple[str, str]: + left, right = _arg.split("=", 1) + return left.lstrip("--").replace("-", "_"), right + + args: Dict[str, Union[str, int, float, bool]] = {} + for key, string_val in map(_split_arg, unknown_args): + if re.match(r"^[-+]?\d+$", string_val): + # value is integer + args[key] = int(string_val) + elif re.match(r"^[-+]?\d*\.\d+$", string_val): + # value is float + args[key] = float(string_val) + elif re.match(r"^(?:True|False)$", string_val): + # value is bool + args[key] = string_val == "True" + else: + # value is string + args[key] = string_val + return args def main() -> None: @@ -202,7 +223,7 @@ def main() -> None: raise SystemExit(errno.EINVAL) results, unknown_args = parser.parse_known_args() - additional_config = _parse_additonal_config(unknown_args) + additional_config = _parse_additional_config(unknown_args) bus = _create_bus(results, can_filters=_parse_filters(results), **additional_config) if results.active: diff --git a/can/player.py b/can/player.py index 632cc331b..72faa892a 100644 --- a/can/player.py +++ b/can/player.py @@ -13,7 +13,7 @@ from can import LogReader, Message, MessageSync -from .logger import _create_base_argument_parser, _create_bus +from .logger import _create_base_argument_parser, _create_bus, _parse_additional_config def main() -> None: @@ -78,13 +78,14 @@ def main() -> None: parser.print_help(sys.stderr) raise SystemExit(errno.EINVAL) - results = parser.parse_args() + results, unknown_args = parser.parse_known_args() + additional_config = _parse_additional_config(unknown_args) verbosity = results.verbosity error_frames = results.error_frames - with _create_bus(results) as bus: + with _create_bus(results, **additional_config) as bus: with LogReader(results.infile) as reader: in_sync = MessageSync( diff --git a/can/viewer.py b/can/viewer.py index a84c865f5..7be04949d 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -35,7 +35,7 @@ _parse_filters, _append_filter_argument, _create_base_argument_parser, - _parse_additonal_config, + _parse_additional_config, ) @@ -540,7 +540,7 @@ def parse_args(args): else: data_structs[key] = struct.Struct(fmt) - additional_config = _parse_additonal_config(unknown_args) + additional_config = _parse_additional_config(unknown_args) return parsed_args, can_filters, data_structs, additional_config diff --git a/test/test_logger.py b/test/test_logger.py index b694f06bb..bb0015a89 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -10,6 +10,9 @@ import gzip import os import sys + +import pytest + import can import can.logger @@ -105,6 +108,49 @@ def test_log_virtual_sizedlogger(self): self.assertSuccessfullCleanup() self.mock_logger_sized.assert_called_once() + def test_parse_additional_config(self): + unknown_args = [ + "--app-name=CANalyzer", + "--serial=5555", + "--receive-own-messages=True", + "--false-boolean=False", + "--offset=1.5", + ] + parsed_args = can.logger._parse_additional_config(unknown_args) + + assert "app_name" in parsed_args + assert parsed_args["app_name"] == "CANalyzer" + + assert "serial" in parsed_args + assert parsed_args["serial"] == 5555 + + assert "receive_own_messages" in parsed_args + assert ( + isinstance(parsed_args["receive_own_messages"], bool) + and parsed_args["receive_own_messages"] is True + ) + + assert "false_boolean" in parsed_args + assert ( + isinstance(parsed_args["false_boolean"], bool) + and parsed_args["false_boolean"] is False + ) + + assert "offset" in parsed_args + assert parsed_args["offset"] == 1.5 + + with pytest.raises(ValueError): + can.logger._parse_additional_config(["--wrong-format"]) + + with pytest.raises(ValueError): + can.logger._parse_additional_config(["-wrongformat=value"]) + + with pytest.raises(ValueError): + can.logger._parse_additional_config(["--wrongformat=value1 value2"]) + + with pytest.raises(ValueError): + can.logger._parse_additional_config(["wrongformat="]) + class TestLoggerCompressedFile(unittest.TestCase): def setUp(self) -> None: From 616b00e82b4e9f148ddf3799d9edcad525a8deb6 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 6 Aug 2022 14:32:05 -0700 Subject: [PATCH 0902/1235] Finds USB2CAN Serial Number by USB Name (#1129) * Finds usb2can serial numbers by name * Cleaned up variable names --- can/interfaces/usb2can/serial_selector.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index fcc951262..c6b9053d6 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -40,7 +40,7 @@ def WMIDateStringToDate(dtmDate) -> str: return strDateTime -def find_serial_devices(serial_matcher: str = "ED") -> List[str]: +def find_serial_devices(serial_matcher: str = "") -> List[str]: """ Finds a list of USB devices where the serial number (partially) matches the given string. @@ -49,6 +49,9 @@ def find_serial_devices(serial_matcher: str = "ED") -> List[str]: """ objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") objSWbemServices = objWMIService.ConnectServer(".", "root\\cimv2") - items = objSWbemServices.ExecQuery("SELECT * FROM Win32_USBControllerDevice") - ids = (item.Dependent.strip('"')[-8:] for item in items) - return [e for e in ids if e.startswith(serial_matcher)] + query = "SELECT * FROM CIM_LogicalDevice where Name LIKE '%USB2CAN%'" + devices = objSWbemServices.ExecQuery(query) + serial_numbers = [device.DeviceID.split("\\")[-1] for device in devices] + if serial_matcher: + return [sn for sn in serial_numbers if serial_matcher in sn] + return serial_numbers From 73663b6bdd98f7d7aed1cbfb75c9a919d22217a4 Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Mon, 8 Aug 2022 01:52:30 -0500 Subject: [PATCH 0903/1235] Raise appropriate error message when append is not possible (#1361) * Raise appropriate error messages when SqliteWriter is requested to roll - Handle "append" by adding **options to SqlWriter - Check logger instance for SqliteWriter in rotating logger setup and raise appropriate error. * Change **options to **kwargs in can/io/sqlite.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Better error handling for Sqlite rollover and append option * Add type to **kwargs in asc * Raise type error rather than exception so tests pass * Changes to kwargs lookup and rolling log unavailability * Updates based on black and mypy feedback * Reformat exception by black * Handle multiple suffixes in exception * Replace ValueError with TypeError * Update append test to handle ValueError Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/io/asc.py | 6 ++++++ can/io/logger.py | 5 +++-- can/io/sqlite.py | 11 +++++++++-- test/logformats_test.py | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 3a320f007..2826a22a6 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -352,6 +352,7 @@ def __init__( self, file: Union[StringPathLike, TextIO], channel: int = 1, + **kwargs: Any, ) -> None: """ :param file: a path-like object or as file-like object to write to @@ -360,6 +361,11 @@ def __init__( :param channel: a default channel to use when the message does not have a channel set """ + if kwargs.get("append", False): + raise ValueError( + f"{self.__class__.__name__} is currently not equipped to " + f"append messages to an existing file." + ) super().__init__(file, mode="w") self.channel = channel diff --git a/can/io/logger.py b/can/io/logger.py index 37ba1e736..5e46f6dc2 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -14,6 +14,7 @@ from typing_extensions import Literal from pkg_resources import iter_entry_points +import can.io from ..message import Message from ..listener import Listener from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter @@ -227,8 +228,8 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: return cast(FileIOMessageWriter, logger) else: raise Exception( - "The Logger corresponding to the arguments is not a FileIOMessageWriter or " - "can.Printer" + f"The log format \"{''.join(pathlib.Path(filename).suffixes[-2:])}" + f'" is not supported by {self.__class__.__name__}' ) def stop(self) -> None: diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 5f05764d5..98e870a84 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -8,7 +8,7 @@ import threading import logging import sqlite3 -from typing import Generator +from typing import Generator, Any from can.listener import BufferedReader from can.message import Message @@ -128,7 +128,9 @@ class SqliteWriter(MessageWriter, BufferedReader): MAX_BUFFER_SIZE_BEFORE_WRITES = 500 """Maximum number of messages to buffer before writing to the database""" - def __init__(self, file: StringPathLike, table_name: str = "messages") -> None: + def __init__( + self, file: StringPathLike, table_name: str = "messages", **kwargs: Any + ) -> None: """ :param file: a `str` or path like object that points to the database file to use @@ -137,6 +139,11 @@ def __init__(self, file: StringPathLike, table_name: str = "messages") -> None: .. warning:: In contrary to all other readers/writers the Sqlite handlers do not accept file-like objects as the `file` parameter. """ + if kwargs.get("append", False): + raise ValueError( + f"The append argument should not be used in " + f"conjunction with the {self.__class__.__name__}." + ) super().__init__(file=None) self.table_name = table_name self._db_filename = file diff --git a/test/logformats_test.py b/test/logformats_test.py index 49e9c563e..6a0eafac1 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -311,7 +311,7 @@ def test_append_mode(self): # use append mode for second half try: writer = self.writer_constructor(self.test_file_name, append=True) - except TypeError as e: + except ValueError as e: # maybe "append" is not a formal parameter (this is the case for SqliteWriter) try: writer = self.writer_constructor(self.test_file_name) From c1736ccfbc49e27178cded7e4c21bf5ac4e11e1e Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 10 Aug 2022 08:34:25 +0200 Subject: [PATCH 0904/1235] Add file_size() function to FileIOMessageWriter (#1367) * Add file_size() function to FileIOMessageWriter * Modify help statement for -s to indicate blf behaves differently * Open max_container_size in blf writer to API via kwargs Co-authored-by: j-c-cook --- can/io/blf.py | 11 ++++++++++- can/io/generic.py | 4 ++++ can/io/logger.py | 2 +- can/logger.py | 5 +++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index efeba9488..911177d20 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -17,7 +17,7 @@ import datetime import time import logging -from typing import List, BinaryIO, Generator, Union, Tuple, Optional, cast +from typing import List, BinaryIO, Generator, Union, Tuple, Optional, cast, Any from ..message import Message from ..util import len2dlc, dlc2len, channel2int @@ -370,6 +370,8 @@ def __init__( append: bool = False, channel: int = 1, compression_level: int = -1, + *args: Any, + **kwargs: Any ) -> None: """ :param file: a path-like object or as file-like object to write to @@ -400,6 +402,9 @@ def __init__( self.compression_level = compression_level self._buffer: List[bytes] = [] self._buffer_size = 0 + # If max container size is located in kwargs, then update the instance + if kwargs.get("max_container_size", False): + self.max_container_size = kwargs["max_container_size"] if append: # Parse file header data = self.file.read(FILE_HEADER_STRUCT.size) @@ -566,6 +571,10 @@ def _flush(self): self.uncompressed_size += LOG_CONTAINER_STRUCT.size self.uncompressed_size += len(uncompressed_data) + def file_size(self) -> int: + """Return an estimate of the current file size in bytes.""" + return self.file.tell() + self._buffer_size + def stop(self): """Stops logging and closes the file.""" self._flush() diff --git a/can/io/generic.py b/can/io/generic.py index b45e4dd1f..acdf367d6 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -89,6 +89,10 @@ def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "wt") -> N super().__init__(file, mode) + def file_size(self) -> int: + """Return an estimate of the current file size in bytes.""" + return self.file.tell() + # pylint: disable=too-few-public-methods class MessageReader(BaseIOHandler, Iterable[can.Message], metaclass=ABCMeta): diff --git a/can/io/logger.py b/can/io/logger.py index 5e46f6dc2..071eeb300 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -326,7 +326,7 @@ def should_rollover(self, msg: Message) -> bool: if self.max_bytes <= 0: return False - if self.writer.file.tell() >= self.max_bytes: + if self.writer.file_size() >= self.max_bytes: return True return False diff --git a/can/logger.py b/can/logger.py index 209c1381c..dbf78e408 100644 --- a/can/logger.py +++ b/can/logger.py @@ -191,8 +191,9 @@ def main() -> None: "--file_size", dest="file_size", type=int, - help="Maximum file size in bytes. Rotate log file when size threshold " - "is reached.", + help="Maximum file size in bytes (or for the case of blf, maximum " + "buffer size before compression and flush to file). Rotate log " + "file when size threshold is reached.", default=None, ) From 88fc8e5bcf56ab2f9f2041242733ad90bb7101b4 Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Wed, 10 Aug 2022 11:02:34 +0200 Subject: [PATCH 0905/1235] Fix race condition in back2back_test for UDP multicast bus (#1349) * Add debug prints to back2back_test to analyze race condition Might fail if pytest does not print stdout * Update IPv6 back2back test to use interface-local multicast address Under unknown conditions the IPv6 multicast frames sent during the test are reflected back to the sender twice. This causes the test to fail. With this commit, the multicast frames sent during the test are constrained to the local interface to potentially avoid a double reception. * Revert "Add debug prints to back2back_test to analyze race condition" This reverts commit a830c0900dee465d3d7ffa9697f49791dde722da. --- test/back2back_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index b5ae4e27c..ab4d57dc1 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -307,11 +307,12 @@ def test_unique_message_instances(self): "only supported on Unix systems (but not on Travis CI; and not on macOS at GitHub Actions)", ) class BasicTestUdpMulticastBusIPv6(Back2BackTestCase): + HOST_LOCAL_MCAST_GROUP_IPv6 = "ff11:7079:7468:6f6e:6465:6d6f:6d63:6173" INTERFACE_1 = "udp_multicast" - CHANNEL_1 = UdpMulticastBus.DEFAULT_GROUP_IPv6 + CHANNEL_1 = HOST_LOCAL_MCAST_GROUP_IPv6 INTERFACE_2 = "udp_multicast" - CHANNEL_2 = UdpMulticastBus.DEFAULT_GROUP_IPv6 + CHANNEL_2 = HOST_LOCAL_MCAST_GROUP_IPv6 def test_unique_message_instances(self): with self.assertRaises(NotImplementedError): From 9e795de62166c37c19770fb4aaf1a5ac966dff9c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 11 Aug 2022 23:26:57 +0200 Subject: [PATCH 0906/1235] refactor for mypy friendly version checks (#1371) --- can/interfaces/__init__.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 90a05d7bc..755e8675c 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -2,8 +2,11 @@ Interfaces contain low level implementations that interact with CAN hardware. """ +import sys +from typing import Dict, Tuple + # interface_name => (module, classname) -BACKENDS = { +BACKENDS: Dict[str, Tuple[str, ...]] = { "kvaser": ("can.interfaces.kvaser", "KvaserBus"), "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), "serial": ("can.interfaces.serial.serial_can", "SerialBus"), @@ -29,27 +32,21 @@ "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), } -try: +if sys.version_info >= (3, 8): from importlib.metadata import entry_points - try: - entries = entry_points(group="can.interface") - except TypeError: - # Fallback for Python <3.10 - # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note" - entries = entry_points().get("can.interface", []) - + entries = entry_points().get("can.interface", ()) BACKENDS.update( {interface.name: tuple(interface.value.split(":")) for interface in entries} ) -except ImportError: +else: from pkg_resources import iter_entry_points - entry = iter_entry_points("can.interface") + entries = iter_entry_points("can.interface") BACKENDS.update( { interface.name: (interface.module_name, interface.attrs[0]) - for interface in entry + for interface in entries } ) From 3a7c80eb24d253b092b038f45be9d36942c2ab2d Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Thu, 11 Aug 2022 17:00:38 -0500 Subject: [PATCH 0907/1235] Write 3 ms digits to ascii to resolve CANoe bug (#1362) * Write 3 ms to ascii for CANoe and fix append error The ascii format currently is not setup to append. I recenly added in **options to the logger script. **options are only required for the rolling logger. * Black and mypy fixes * Revert "Write 3 ms to ascii for CANoe and fix append error" This reverts commit 57af021eba6218186a30af684e0b96fbbcac209d. --- can/io/asc.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/can/io/asc.py b/can/io/asc.py index 2826a22a6..ba2214c71 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -372,6 +372,10 @@ def __init__( # write start of file header now = datetime.now().strftime(self.FORMAT_START_OF_FILE_DATE) + # Note: CANoe requires that the microsecond field only have 3 digits + idx = now.index(".") # Find the index in the string of the decimal + # Keep decimal and first three ms digits (4), remove remaining digits + now = now.replace(now[idx + 4 : now[idx:].index(" ") + idx], "") self.file.write(f"date {now}\n") self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") From c4f0789afb756abaa57efe83d222b28b9343d8ab Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:03:35 +0200 Subject: [PATCH 0908/1235] Pass CLI extra_args to Logger initialisation (#1366) * pass CLI extra_args to Logger * pass extra_args to player --- can/io/asc.py | 3 +++ can/io/blf.py | 9 +++++++-- can/io/canutils.py | 13 ++++++++++--- can/io/csv.py | 15 ++++++++++++--- can/io/generic.py | 15 +++++++++++++-- can/io/logger.py | 2 +- can/io/printer.py | 8 ++++++-- can/io/sqlite.py | 14 ++++++++++++-- can/logger.py | 25 +++++++++++++++++-------- can/player.py | 4 ++-- can/viewer.py | 4 +++- 11 files changed, 86 insertions(+), 26 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index ba2214c71..7cefa5b76 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -39,6 +39,8 @@ def __init__( file: Union[StringPathLike, TextIO], base: str = "hex", relative_timestamp: bool = True, + *args: Any, + **kwargs: Any, ) -> None: """ :param file: a path-like object or as file-like object to read from @@ -352,6 +354,7 @@ def __init__( self, file: Union[StringPathLike, TextIO], channel: int = 1, + *args: Any, **kwargs: Any, ) -> None: """ diff --git a/can/io/blf.py b/can/io/blf.py index 911177d20..93fa54ca2 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -143,7 +143,12 @@ class BLFReader(MessageReader): file: BinaryIO - def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None: + def __init__( + self, + file: Union[StringPathLike, BinaryIO], + *args: Any, + **kwargs: Any, + ) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in binary @@ -371,7 +376,7 @@ def __init__( channel: int = 1, compression_level: int = -1, *args: Any, - **kwargs: Any + **kwargs: Any, ) -> None: """ :param file: a path-like object or as file-like object to write to diff --git a/can/io/canutils.py b/can/io/canutils.py index 0cca82eb8..f63df3f6b 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -5,11 +5,11 @@ """ import logging -from typing import Generator, TextIO, Union +from typing import Generator, TextIO, Union, Any from can.message import Message from .generic import FileIOMessageWriter, MessageReader -from ..typechecking import AcceptedIOType, StringPathLike +from ..typechecking import StringPathLike log = logging.getLogger("can.io.canutils") @@ -34,7 +34,12 @@ class CanutilsLogReader(MessageReader): file: TextIO - def __init__(self, file: Union[StringPathLike, TextIO]) -> None: + def __init__( + self, + file: Union[StringPathLike, TextIO], + *args: Any, + **kwargs: Any, + ) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text @@ -132,6 +137,8 @@ def __init__( file: Union[StringPathLike, TextIO], channel: str = "vcan0", append: bool = False, + *args: Any, + **kwargs: Any, ): """ :param file: a path-like object or as file-like object to write to diff --git a/can/io/csv.py b/can/io/csv.py index 0161b4f55..2e2f46699 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -10,7 +10,7 @@ """ from base64 import b64encode, b64decode -from typing import TextIO, Generator, Union +from typing import TextIO, Generator, Union, Any from can.message import Message from .generic import FileIOMessageWriter, MessageReader @@ -28,7 +28,12 @@ class CSVReader(MessageReader): file: TextIO - def __init__(self, file: Union[StringPathLike, TextIO]) -> None: + def __init__( + self, + file: Union[StringPathLike, TextIO], + *args: Any, + **kwargs: Any, + ) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text @@ -87,7 +92,11 @@ class CSVWriter(FileIOMessageWriter): file: TextIO def __init__( - self, file: Union[StringPathLike, TextIO], append: bool = False + self, + file: Union[StringPathLike, TextIO], + append: bool = False, + *args: Any, + **kwargs: Any, ) -> None: """ :param file: a path-like object or a file-like object to write to. diff --git a/can/io/generic.py b/can/io/generic.py index acdf367d6..d5c7a2057 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -7,6 +7,7 @@ Iterable, Type, ContextManager, + Any, ) from typing_extensions import Literal from types import TracebackType @@ -28,7 +29,11 @@ class BaseIOHandler(ContextManager, metaclass=ABCMeta): file: Optional[can.typechecking.FileLike] def __init__( - self, file: Optional[can.typechecking.AcceptedIOType], mode: str = "rt" + self, + file: Optional[can.typechecking.AcceptedIOType], + mode: str = "rt", + *args: Any, + **kwargs: Any ) -> None: """ :param file: a path-like object to open a file, a file-like object @@ -82,7 +87,13 @@ class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): file: can.typechecking.FileLike - def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "wt") -> None: + def __init__( + self, + file: can.typechecking.AcceptedIOType, + mode: str = "wt", + *args: Any, + **kwargs: Any + ) -> None: # Not possible with the type signature, but be verbose for user-friendliness if file is None: raise ValueError("The given file cannot be None") diff --git a/can/io/logger.py b/can/io/logger.py index 071eeb300..b50825d3f 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -303,8 +303,8 @@ class SizedRotatingLogger(BaseRotatingLogger): def __init__( self, base_filename: StringPathLike, - *args: Any, max_bytes: int = 0, + *args: Any, **kwargs: Any, ) -> None: """ diff --git a/can/io/printer.py b/can/io/printer.py index cafab3815..61871e8ad 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -4,7 +4,7 @@ import logging -from typing import Optional, TextIO, Union +from typing import Optional, TextIO, Union, Any from ..message import Message from .generic import MessageWriter @@ -26,7 +26,11 @@ class Printer(MessageWriter): file: Optional[TextIO] def __init__( - self, file: Optional[Union[StringPathLike, TextIO]] = None, append: bool = False + self, + file: Optional[Union[StringPathLike, TextIO]] = None, + append: bool = False, + *args: Any, + **kwargs: Any ) -> None: """ :param file: An optional path-like object or a file-like object to "print" diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 98e870a84..b9cbf9f93 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -32,7 +32,13 @@ class SqliteReader(MessageReader): .. note:: The database schema is given in the documentation of the loggers. """ - def __init__(self, file: StringPathLike, table_name: str = "messages") -> None: + def __init__( + self, + file: StringPathLike, + table_name: str = "messages", + *args: Any, + **kwargs: Any, + ) -> None: """ :param file: a `str` path like object that points to the database file to use @@ -129,7 +135,11 @@ class SqliteWriter(MessageWriter, BufferedReader): """Maximum number of messages to buffer before writing to the database""" def __init__( - self, file: StringPathLike, table_name: str = "messages", **kwargs: Any + self, + file: StringPathLike, + table_name: str = "messages", + *args: Any, + **kwargs: Any, ) -> None: """ :param file: a `str` or path like object that points diff --git a/can/logger.py b/can/logger.py index dbf78e408..0b73dd785 100644 --- a/can/logger.py +++ b/can/logger.py @@ -21,6 +21,8 @@ from typing import Any, Dict, List, Union, Sequence, Tuple import can +from can.io import BaseRotatingLogger +from can.io.generic import MessageWriter from . import Bus, BusState, Logger, SizedRotatingLogger from .typechecking import CanFilter, CanFilters @@ -61,10 +63,10 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument( "extra_args", nargs=argparse.REMAINDER, - help=r"The remaining arguments will be used for the interface " - r"initialisation. For example, `-i vector -c 1 --app-name=" - r"MyCanApp` is the equivalent to opening the bus with `Bus(" - r"'vector', channel=1, app_name='MyCanApp')", + help="The remaining arguments will be used for the interface and " + "logger/player initialisation. " + "For example, `-i vector -c 1 --app-name=MyCanApp` is the equivalent " + "to opening the bus with `Bus('vector', channel=1, app_name='MyCanApp')", ) @@ -224,7 +226,7 @@ def main() -> None: raise SystemExit(errno.EINVAL) results, unknown_args = parser.parse_known_args() - additional_config = _parse_additional_config(unknown_args) + additional_config = _parse_additional_config([*results.extra_args, *unknown_args]) bus = _create_bus(results, can_filters=_parse_filters(results), **additional_config) if results.active: @@ -235,13 +237,20 @@ def main() -> None: print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") print(f"Can Logger (Started on {datetime.now()})") - options = {"append": results.append} + logger: Union[MessageWriter, BaseRotatingLogger] if results.file_size: logger = SizedRotatingLogger( - base_filename=results.log_file, max_bytes=results.file_size, **options + base_filename=results.log_file, + max_bytes=results.file_size, + append=results.append, + **additional_config, ) else: - logger = Logger(filename=results.log_file, **options) # type: ignore + logger = Logger( + filename=results.log_file, + append=results.append, + **additional_config, + ) try: while True: diff --git a/can/player.py b/can/player.py index 72faa892a..c029981be 100644 --- a/can/player.py +++ b/can/player.py @@ -79,14 +79,14 @@ def main() -> None: raise SystemExit(errno.EINVAL) results, unknown_args = parser.parse_known_args() - additional_config = _parse_additional_config(unknown_args) + additional_config = _parse_additional_config([*results.extra_args, *unknown_args]) verbosity = results.verbosity error_frames = results.error_frames with _create_bus(results, **additional_config) as bus: - with LogReader(results.infile) as reader: + with LogReader(results.infile, **additional_config) as reader: in_sync = MessageSync( cast(Iterable[Message], reader), diff --git a/can/viewer.py b/can/viewer.py index 7be04949d..6773a0acd 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -540,7 +540,9 @@ def parse_args(args): else: data_structs[key] = struct.Struct(fmt) - additional_config = _parse_additional_config(unknown_args) + additional_config = _parse_additional_config( + [*parsed_args.extra_args, *unknown_args] + ) return parsed_args, can_filters, data_structs, additional_config From ed124c92f38b454ca2f485ca4c9557eb47b44951 Mon Sep 17 00:00:00 2001 From: Atabey <55498083+1atabey1@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:26:26 +0200 Subject: [PATCH 0909/1235] Add Parameter for Enabling PCAN Auto Bus-Off Reset (#1345) * parametrise pcan auto reset * add unit tests, fix build error * formatting Co-authored-by: Atabey --- can/interfaces/pcan/pcan.py | 13 +++++++++++++ test/test_pcan.py | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 2fea74e77..e5b877762 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -56,6 +56,7 @@ PCAN_CHANNEL_FEATURES, FEATURE_FD_CAPABLE, PCAN_DICT_STATUS, + PCAN_BUSOFF_AUTORESET, ) @@ -206,6 +207,10 @@ def __init__( In the range (1..16). Ignored if not using CAN-FD. + :param bool auto_reset: + Enable automatic recovery in bus off scenario. + Resetting the driver takes ~500ms during which + it will not be responsive. """ self.m_objPCANBasic = PCANBasic() @@ -276,6 +281,14 @@ def __init__( "Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.10" ) + if kwargs.get("auto_reset", False): + result = self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_BUSOFF_AUTORESET, PCAN_PARAMETER_ON + ) + + if result != PCAN_ERROR_OK: + raise PcanCanInitializationError(self._get_formatted_error(result)) + if HAS_EVENTS: self._recv_event = CreateEvent(None, 0, 0, None) result = self.m_objPCANBasic.SetValue( diff --git a/test/test_pcan.py b/test/test_pcan.py index eba42d7e1..0a680fea0 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -363,6 +363,16 @@ def get_value_side_effect(handle, param): self.bus = can.Bus(bustype="pcan", device_id=dev_id) self.assertEqual(expected_result, self.bus.channel_info) + def test_bus_creation_auto_reset(self): + self.bus = can.Bus(bustype="pcan", auto_reset=True) + self.assertIsInstance(self.bus, PcanBus) + self.MockPCANBasic.assert_called_once() + + def test_auto_reset_init_fault(self): + self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_INITIALIZE) + with self.assertRaises(CanInitializationError): + self.bus = can.Bus(bustype="pcan", auto_reset=True) + if __name__ == "__main__": unittest.main() From f6d8fe0432d46ea48027511bd38ae4c141a9a5e4 Mon Sep 17 00:00:00 2001 From: "Fede.Breg" <44495483+Thepowa753@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:47:54 +0200 Subject: [PATCH 0910/1235] fix: conversion for port number (#1309) * fix: conversion for port number * fix: correct order for valid port range * refactor: tabs and spaces Sorry * fix: calling port within if in dict * fix: forcing int to bypass wrong error * feat: add util port tests * fix: tests assertRaises with correct syntax * refactor: using black * make mypy happy Co-authored-by: Federico Bregant Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/util.py | 14 ++++++++++++++ test/test_util.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/can/util.py b/can/util.py index a9d08c469..d1ff643de 100644 --- a/can/util.py +++ b/can/util.py @@ -211,6 +211,20 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: raise CanInterfaceNotImplementedError( f'Unknown interface type "{config["interface"]}"' ) + if "port" in config: + # convert port to integer if necessary + if isinstance(config["port"], int): + port = config["port"] + elif isinstance(config["port"], str): + if config["port"].isnumeric(): + config["port"] = port = int(config["port"]) + else: + raise ValueError("Port config must be a number!") + else: + raise TypeError("Port config must be string or integer!") + + if not 0 < port < 65535: + raise ValueError("Port config must be inside 0-65535 range!") if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) diff --git a/test/test_util.py b/test/test_util.py index e151e3d63..7048d6151 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -53,6 +53,9 @@ def test_with_new_and_alias_present(self): class TestBusConfig(unittest.TestCase): base_config = dict(interface="socketcan", bitrate=500_000) + port_alpha_config = dict(interface="socketcan", bitrate=500_000, port="fail123") + port_to_high_config = dict(interface="socketcan", bitrate=500_000, port="999999") + port_wrong_type_config = dict(interface="socketcan", bitrate=500_000, port=(1234,)) def test_timing_can_use_int(self): """ @@ -64,6 +67,17 @@ def test_timing_can_use_int(self): _create_bus_config({**self.base_config, **timing_conf}) except TypeError as e: self.fail(e) + self.assertRaises( + ValueError, _create_bus_config, {**self.port_alpha_config, **timing_conf} + ) + self.assertRaises( + ValueError, _create_bus_config, {**self.port_to_high_config, **timing_conf} + ) + self.assertRaises( + TypeError, + _create_bus_config, + {**self.port_wrong_type_config, **timing_conf}, + ) class TestChannel2Int(unittest.TestCase): From ae41b3e145a6b2ca6d0c6834cf5f0a54096472bc Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:59:04 +0200 Subject: [PATCH 0911/1235] replace socket.error with OSError (#1373) --- can/interfaces/socketcan/socketcan.py | 16 ++++++++-------- can/interfaces/socketcand/socketcand.py | 2 +- can/interfaces/udp_multicast/bus.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 082bbcf19..c7c038520 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -530,7 +530,7 @@ def capture_message( channel = addr[0] if isinstance(addr, tuple) else addr else: channel = None - except socket.error as error: + except OSError as error: raise can.CanOperationError(f"Error receiving: {error.strerror}", error.errno) can_id, can_dlc, flags, data = dissect_can_frame(cf) @@ -656,7 +656,7 @@ def __init__( self.socket.setsockopt( SOL_CAN_RAW, CAN_RAW_LOOPBACK, 1 if local_loopback else 0 ) - except socket.error as error: + except OSError as error: log.error("Could not set local loopback flag(%s)", error) # set the receive_own_messages parameter @@ -664,21 +664,21 @@ def __init__( self.socket.setsockopt( SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 1 if receive_own_messages else 0 ) - except socket.error as error: + except OSError as error: log.error("Could not receive own messages (%s)", error) # enable CAN-FD frames if desired if fd: try: self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1) - except socket.error as error: + except OSError as error: log.error("Could not enable CAN-FD frames (%s)", error) if not ignore_rx_error_frames: # enable error frames try: self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF) - except socket.error as error: + except OSError as error: log.error("Could not enable error frames (%s)", error) # enable nanosecond resolution timestamping @@ -714,7 +714,7 @@ def _recv_internal( # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout) - except socket.error as error: + except OSError as error: # something bad happened (e.g. the interface went down) raise can.CanOperationError( f"Failed to receive: {error.strerror}", error.errno @@ -776,7 +776,7 @@ def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: sent = self.socket.sendto(data, (channel,)) else: sent = self.socket.send(data) - except socket.error as error: + except OSError as error: raise can.CanOperationError( f"Failed to transmit: {error.strerror}", error.errno ) @@ -840,7 +840,7 @@ def _get_bcm_socket(self, channel: str) -> socket.socket: def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: try: self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters)) - except socket.error as error: + except OSError as error: # fall back to "software filtering" (= not in kernel) self._is_filtered = False log.error( diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 6b74b59e0..327df9e73 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -93,7 +93,7 @@ def _recv_internal(self, timeout): ready_receive_sockets, _, _ = select.select( [self.__socket], [], [], timeout ) - except socket.error as exc: + except OSError as exc: # something bad happened (e.g. the interface went down) log.error(f"Failed to receive: {exc}") raise can.CanError(f"Failed to receive: {exc}") diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 6b7e57bd9..7f74c685f 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -332,7 +332,7 @@ def recv( # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) ready_receive_sockets, _, _ = select.select([self._socket], [], [], timeout) - except socket.error as exc: + except OSError as exc: # something bad (not a timeout) happened (e.g. the interface went down) raise can.CanOperationError( f"Failed to wait for IP/UDP socket: {exc}" From 2da28c1a1c87776618a60218b0b97800cf2deb34 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 15 Aug 2022 08:33:15 +0200 Subject: [PATCH 0912/1235] improve MessageSync timings (#1374) --- can/io/player.py | 25 ++++++++++++++---------- test/test_message_sync.py | 40 ++++++++++++++++++--------------------- test/test_player.py | 9 ++------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/can/io/player.py b/can/io/player.py index 132751f4d..8eb4ba24f 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -5,7 +5,7 @@ """ import gzip import pathlib -from time import time, sleep +import time import typing from pkg_resources import iter_entry_points @@ -132,8 +132,9 @@ def __init__( self.skip = skip def __iter__(self) -> typing.Generator[Message, None, None]: - playback_start_time = time() + t_wakeup = playback_start_time = time.perf_counter() recorded_start_time = None + t_skipped = 0.0 for message in self.raw_messages: @@ -142,15 +143,19 @@ def __iter__(self) -> typing.Generator[Message, None, None]: if recorded_start_time is None: recorded_start_time = message.timestamp - now = time() - current_offset = now - playback_start_time - recorded_offset_from_start = message.timestamp - recorded_start_time - remaining_gap = max(0.0, recorded_offset_from_start - current_offset) - - sleep_period = max(self.gap, min(self.skip, remaining_gap)) + t_wakeup = playback_start_time + ( + message.timestamp - t_skipped - recorded_start_time + ) else: - sleep_period = self.gap + t_wakeup += self.gap + + sleep_period = t_wakeup - time.perf_counter() + + if self.skip and sleep_period > self.skip: + t_skipped += sleep_period - self.skip + sleep_period = self.skip - sleep(sleep_period) + if sleep_period > 1e-4: + time.sleep(sleep_period) yield message diff --git a/test/test_message_sync.py b/test/test_message_sync.py index 1e2d61b24..8750dd416 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -5,7 +5,7 @@ """ from copy import copy -from time import time +import time import gc import unittest @@ -49,43 +49,40 @@ def teardown_method(self, _): # we need to reenable the garbage collector again gc.enable() - @pytest.mark.timeout(inc(0.2)) def test_general(self): messages = [ Message(timestamp=50.0), Message(timestamp=50.0), Message(timestamp=50.0 + 0.05), - Message(timestamp=50.0 + 0.05 + 0.08), + Message(timestamp=50.0 + 0.13), Message(timestamp=50.0), # back in time ] - sync = MessageSync(messages, gap=0.0) + sync = MessageSync(messages, gap=0.0, skip=0.0) - start = time() + t_start = time.perf_counter() collected = [] timings = [] for message in sync: + t_now = time.perf_counter() collected.append(message) - now = time() - timings.append(now - start) - start = now + timings.append(t_now - t_start) self.assertMessagesEqual(messages, collected) self.assertEqual(len(timings), len(messages), "programming error in test code") - self.assertTrue(0.0 <= timings[0] < inc(0.005), str(timings[0])) - self.assertTrue(0.0 <= timings[1] < inc(0.005), str(timings[1])) - self.assertTrue(0.045 <= timings[2] < inc(0.055), str(timings[2])) - self.assertTrue(0.075 <= timings[3] < inc(0.085), str(timings[3])) - self.assertTrue(0.0 <= timings[4] < inc(0.005), str(timings[4])) + self.assertTrue(0.0 <= timings[0] < 0.0 + inc(0.02), str(timings[0])) + self.assertTrue(0.0 <= timings[1] < 0.0 + inc(0.02), str(timings[1])) + self.assertTrue(0.045 <= timings[2] < 0.05 + inc(0.02), str(timings[2])) + self.assertTrue(0.125 <= timings[3] < 0.13 + inc(0.02), str(timings[3])) + self.assertTrue(0.125 <= timings[4] < 0.13 + inc(0.02), str(timings[4])) - @pytest.mark.timeout(inc(0.1) * len(TEST_FEWER_MESSAGES)) # very conservative def test_skip(self): messages = copy(TEST_FEWER_MESSAGES) sync = MessageSync(messages, skip=0.005, gap=0.0) - before = time() + before = time.perf_counter() collected = list(sync) - after = time() + after = time.perf_counter() took = after - before # the handling of the messages itself also takes some time: @@ -96,9 +93,8 @@ def test_skip(self): @skip_on_unreliable_platforms -@pytest.mark.timeout(inc(0.3)) @pytest.mark.parametrize( - "timestamp_1,timestamp_2", [(0.0, 0.0), (0.0, 0.01), (0.01, 0.0)] + "timestamp_1,timestamp_2", [(0.0, 0.0), (0.0, 0.01), (0.01, 1.5)] ) def test_gap(timestamp_1, timestamp_2): """This method is alone so it can be parameterized.""" @@ -106,16 +102,16 @@ def test_gap(timestamp_1, timestamp_2): Message(arbitration_id=0x1, timestamp=timestamp_1), Message(arbitration_id=0x2, timestamp=timestamp_2), ] - sync = MessageSync(messages, gap=0.1) + sync = MessageSync(messages, timestamps=False, gap=0.1) gc.disable() - before = time() + before = time.perf_counter() collected = list(sync) - after = time() + after = time.perf_counter() gc.enable() took = after - before - assert 0.1 <= took < inc(0.3) + assert 0.195 <= took < 0.2 + inc(0.02) assert messages == collected diff --git a/test/test_player.py b/test/test_player.py index 0001ae678..c15bf82e2 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -27,7 +27,7 @@ def setUp(self) -> None: self.mock_virtual_bus.__enter__ = Mock(return_value=self.mock_virtual_bus) # Patch time sleep object - patcher_sleep = mock.patch("can.io.player.sleep", spec=True) + patcher_sleep = mock.patch("can.io.player.time.sleep", spec=True) self.MockSleep = patcher_sleep.start() self.addCleanup(patcher_sleep.stop) @@ -60,7 +60,6 @@ def test_play_virtual(self): dlc=8, data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], ) - self.assertEqual(self.MockSleep.call_count, 2) if sys.version_info >= (3, 8): # The args argument was introduced with python 3.8 self.assertTrue( @@ -78,7 +77,6 @@ def test_play_virtual_verbose(self): self.assertIn("09 08 07 06 05 04 03 02", mock_stdout.getvalue()) self.assertIn("05 0c 00 00 00 00 00 00", mock_stdout.getvalue()) self.assertEqual(self.mock_virtual_bus.send.call_count, 2) - self.assertEqual(self.MockSleep.call_count, 2) self.assertSuccessfulCleanup() def test_play_virtual_exit(self): @@ -86,8 +84,7 @@ def test_play_virtual_exit(self): sys.argv = self.baseargs + [self.logfile] can.player.main() - self.assertEqual(self.mock_virtual_bus.send.call_count, 1) - self.assertEqual(self.MockSleep.call_count, 2) + assert self.mock_virtual_bus.send.call_count <= 2 self.assertSuccessfulCleanup() def test_play_skip_error_frame(self): @@ -97,7 +94,6 @@ def test_play_skip_error_frame(self): sys.argv = self.baseargs + ["-v", logfile] can.player.main() self.assertEqual(self.mock_virtual_bus.send.call_count, 9) - self.assertEqual(self.MockSleep.call_count, 12) self.assertSuccessfulCleanup() def test_play_error_frame(self): @@ -107,7 +103,6 @@ def test_play_error_frame(self): sys.argv = self.baseargs + ["-v", "--error-frames", logfile] can.player.main() self.assertEqual(self.mock_virtual_bus.send.call_count, 12) - self.assertEqual(self.MockSleep.call_count, 12) self.assertSuccessfulCleanup() From 9d9cb16f474f837bd46eabb1350ecf86e8d2c42b Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Sun, 28 Aug 2022 08:37:00 -0500 Subject: [PATCH 0913/1235] Fix _default_name for compressed files (#1383) * Fix _default_name for compressed files * Handle a filepath that contains "." prior to stem * Reuse local path variable --- can/io/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index b50825d3f..478651953 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -345,11 +345,11 @@ def _default_name(self) -> StringPathLike: """Generate the default rotation filename.""" path = pathlib.Path(self.base_filename) new_name = ( - path.stem + path.stem.split(".")[0] + "_" + datetime.now().strftime("%Y-%m-%dT%H%M%S") + "_" + f"#{self.rollover_count:03}" - + path.suffix + + "".join(path.suffixes[-2:]) ) return str(path.parent / new_name) From ad8b9482ba9fe76f1a4b746f20a9e7a216f6d9f9 Mon Sep 17 00:00:00 2001 From: Maksim Salau Date: Sat, 3 Sep 2022 19:59:06 +0200 Subject: [PATCH 0914/1235] socketcan: Make find_available_interfaces() find slcanX interfaces (#1369) * socketcan: Make find_available_interfaces() find slcanX interfaces * doc: socketcan: Fix external link to can-utils on github * socketcan: Extend name interface pattern to match vxcan\d+ --- can/interfaces/socketcan/utils.py | 2 +- doc/interfaces/socketcan.rst | 22 ++++++++++++++++++++++ test/open_vcan.sh | 4 ++++ test/test_socketcan_helpers.py | 6 ++++-- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index b718bb69e..55e7eb392 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -38,7 +38,7 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes return struct.pack(can_filter_fmt, *filter_data) -_PATTERN_CAN_INTERFACE = re.compile(r"v?can\d+") +_PATTERN_CAN_INTERFACE = re.compile(r"(sl|v|vx)?can\d+") def find_available_interfaces() -> Iterable[str]: diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index d3a583d75..1e82d8827 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -57,6 +57,28 @@ existing ``can0`` interface with a bitrate of 1MB: sudo ip link set can0 up type can bitrate 1000000 +CAN over Serial / SLCAN +~~~~~~~~~~~~~~~~~~~~~~~ + +SLCAN adapters can be used directly via :doc:`/interfaces/slcan`, or +via :doc:`/interfaces/socketcan` with some help from the ``slcand`` utility +which can be found in the `can-utils `_ package. + +To create a socketcan interface for an SLCAN adapter run the following: + +.. code-block:: bash + + slcand -f -o -c -s5 /dev/ttyAMA0 + ip link set up slcan0 + +Names of the interfaces created by ``slcand`` match the ``slcan\d+`` regex. +If a custom name is required, it can be specified as the last argument. E.g.: + +.. code-block:: bash + + slcand -f -o -c -s5 /dev/ttyAMA0 can0 + ip link set up can0 + .. _socketcan-pcan: PCAN diff --git a/test/open_vcan.sh b/test/open_vcan.sh index bd02ad752..b6f4676b7 100755 --- a/test/open_vcan.sh +++ b/test/open_vcan.sh @@ -5,3 +5,7 @@ modprobe vcan ip link add dev vcan0 type vcan ip link set up vcan0 mtu 72 +ip link add dev vxcan0 type vcan +ip link set up vxcan0 mtu 72 +ip link add dev slcan0 type vcan +ip link set up slcan0 mtu 72 diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index f3fbe6d26..ad53836f2 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -31,10 +31,12 @@ def test_find_available_interfaces(self): result = list(find_available_interfaces()) self.assertGreaterEqual(len(result), 0) for entry in result: - self.assertRegex(entry, r"v?can\d+") + self.assertRegex(entry, r"(sl|v|vx)?can\d+") if TEST_INTERFACE_SOCKETCAN: - self.assertGreaterEqual(len(result), 1) + self.assertGreaterEqual(len(result), 3) self.assertIn("vcan0", result) + self.assertIn("vxcan0", result) + self.assertIn("slcan0", result) if __name__ == "__main__": From 40f6cce796ef5969764e18e746d0dab8d1adab63 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 3 Sep 2022 20:48:06 +0200 Subject: [PATCH 0915/1235] extend XL api wrapper (#1387) --- can/interfaces/vector/xlclass.py | 13 ++++++++++++- can/interfaces/vector/xldefine.py | 8 +++++++- can/interfaces/vector/xldriver.py | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py index c0ef9fa48..6441ad4e5 100644 --- a/can/interfaces/vector/xlclass.py +++ b/can/interfaces/vector/xlclass.py @@ -45,6 +45,13 @@ class s_xl_chip_state(ctypes.Structure): ] +class s_xl_sync_pulse(ctypes.Structure): + _fields_ = [ + ("pulseCode", ctypes.c_ubyte), + ("time", XLuint64), + ] + + class s_xl_can_ev_chip_state(ctypes.Structure): _fields_ = [ ("busStatus", ctypes.c_ubyte), @@ -65,7 +72,11 @@ class s_xl_can_ev_sync_pulse(ctypes.Structure): # BASIC bus message structure class s_xl_tag_data(ctypes.Union): - _fields_ = [("msg", s_xl_can_msg), ("chipState", s_xl_chip_state)] + _fields_ = [ + ("msg", s_xl_can_msg), + ("chipState", s_xl_chip_state), + ("syncPulse", s_xl_sync_pulse), + ] # CAN FD messages diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index 032f08318..5a1084f48 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -64,7 +64,7 @@ class XL_BusTypes(IntFlag): XL_BUS_TYPE_A429 = 8192 # =0x00002000 -class XL_CANFD_BusParams_CanOpMode(IntEnum): +class XL_CANFD_BusParams_CanOpMode(IntFlag): XL_BUS_PARAMS_CANOPMODE_CAN20 = 1 XL_BUS_PARAMS_CANOPMODE_CANFD = 2 XL_BUS_PARAMS_CANOPMODE_CANFD_NO_ISO = 8 @@ -318,3 +318,9 @@ class XL_HardwareType(IntEnum): XL_HWTYPE_VX1161A = 114 XL_HWTYPE_VX1161B = 115 XL_MAX_HWTYPE = 120 + + +class XL_SyncPulseSource(IntEnum): + XL_SYNC_PULSE_EXTERNAL = 0 + XL_SYNC_PULSE_OUR = 1 + XL_SYNC_PULSE_OUR_SHARED = 2 diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 3243fa4a0..8df39e9dc 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -272,3 +272,18 @@ def check_status_initialization(result, function, arguments): xlCanGetEventString = _xlapi_dll.xlCanGetEventString xlCanGetEventString.argtypes = [ctypes.POINTER(xlclass.XLcanRxEvent)] xlCanGetEventString.restype = xlclass.XLstringType + +xlGetReceiveQueueLevel = _xlapi_dll.xlGetReceiveQueueLevel +xlGetReceiveQueueLevel.argtypes = [xlclass.XLportHandle, ctypes.POINTER(ctypes.c_int)] +xlGetReceiveQueueLevel.restype = xlclass.XLstatus +xlGetReceiveQueueLevel.errcheck = check_status_operation + +xlGenerateSyncPulse = _xlapi_dll.xlGenerateSyncPulse +xlGenerateSyncPulse.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] +xlGenerateSyncPulse.restype = xlclass.XLstatus +xlGenerateSyncPulse.errcheck = check_status_operation + +xlFlushReceiveQueue = _xlapi_dll.xlFlushReceiveQueue +xlFlushReceiveQueue.argtypes = [xlclass.XLportHandle] +xlFlushReceiveQueue.restype = xlclass.XLstatus +xlFlushReceiveQueue.errcheck = check_status_operation From 1e11f21189a297cc08c3e24df52dff9f762a1b60 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 11 Sep 2022 20:14:52 +0200 Subject: [PATCH 0916/1235] VectorBus init refactoring (#1389) * refactor VectorBus.__init__() * move bitrate methods below __init__(), fix typo * refactor channel index search into method '_find_global_channel_idx', improve error messages --- can/interfaces/vector/canlib.py | 316 ++++++++++++++++++++------------ 1 file changed, 200 insertions(+), 116 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 9cecaa83d..333380a6f 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -21,6 +21,7 @@ Any, Dict, Callable, + cast, ) WaitForSingleObject: Optional[Callable[[int, int], int]] @@ -43,7 +44,7 @@ deprecated_args_alias, time_perfcounter_correlation, ) -from can.typechecking import AutoDetectedConfig, CanFilters, Channel +from can.typechecking import AutoDetectedConfig, CanFilters # Define Module Logger # ==================== @@ -152,6 +153,7 @@ def __init__( if xldriver is None: raise CanInterfaceNotImplementedError("The Vector API has not been loaded") self.xldriver = xldriver # keep reference so mypy knows it is not None + self.xldriver.xlOpenDriver() self.poll_interval = poll_interval @@ -165,7 +167,7 @@ def __init__( self.channels = [int(ch) for ch in channel] else: raise TypeError( - f"Invalid type for channels parameter: {type(channel).__name__}" + f"Invalid type for parameter 'channel': {type(channel).__name__}" ) self._app_name = app_name.encode() if app_name is not None else b"" @@ -174,136 +176,71 @@ def __init__( ", ".join(f"CAN {ch + 1}" for ch in self.channels), ) - if serial is not None: - app_name = None - channel_index = [] - channel_configs = get_channel_configs() - for channel_config in channel_configs: - if channel_config.serialNumber == serial: - if channel_config.hwChannel in self.channels: - channel_index.append(channel_config.channelIndex) - if channel_index: - if len(channel_index) != len(self.channels): - LOG.info( - "At least one defined channel wasn't found on the specified hardware." - ) - self.channels = channel_index - else: - # Is there any better way to raise the error? - raise CanInitializationError( - "None of the configured channels could be found on the specified hardware." - ) + channel_configs = get_channel_configs() - self.xldriver.xlOpenDriver() - self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE) self.mask = 0 self.fd = fd - # Get channels masks - self.channel_masks: Dict[Optional[Channel], int] = {} - self.index_to_channel = {} + self.channel_masks: Dict[int, int] = {} + self.index_to_channel: Dict[int, int] = {} for channel in self.channels: - if app_name: - # Get global channel index from application channel - hw_type, hw_index, hw_channel = self.get_application_config( - app_name, channel - ) - LOG.debug("Channel index %d found", channel) - idx = self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) - if idx < 0: - # Undocumented behavior! See issue #353. - # If hardware is unavailable, this function returns -1. - # Raise an exception as if the driver - # would have signalled XL_ERR_HW_NOT_PRESENT. - raise VectorInitializationError( - xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, - xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name, - "xlGetChannelIndex", - ) - else: - # Channel already given as global channel - idx = channel - mask = 1 << idx - self.channel_masks[channel] = mask - self.index_to_channel[idx] = channel - self.mask |= mask + channel_index = self._find_global_channel_idx( + channel=channel, + serial=serial, + app_name=app_name, + channel_configs=channel_configs, + ) + LOG.debug("Channel index %d found", channel) + + channel_mask = 1 << channel_index + self.channel_masks[channel] = channel_mask + self.index_to_channel[channel_index] = channel + self.mask |= channel_mask permission_mask = xlclass.XLaccess() # Set mask to request channel init permission if needed if bitrate or fd: permission_mask.value = self.mask - if fd: - self.xldriver.xlOpenPort( - self.port_handle, - self._app_name, - self.mask, - permission_mask, - rx_queue_size, - xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - ) - else: - self.xldriver.xlOpenPort( - self.port_handle, - self._app_name, - self.mask, - permission_mask, - rx_queue_size, - xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - ) + + interface_version = ( + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 + if fd + else xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION + ) + + self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE) + self.xldriver.xlOpenPort( + self.port_handle, + self._app_name, + self.mask, + permission_mask, + rx_queue_size, + interface_version, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + ) + LOG.debug( "Open Port: PortHandle: %d, PermissionMask: 0x%X", self.port_handle.value, permission_mask.value, ) - if permission_mask.value == self.mask: - if fd: - self.canFdConf = xlclass.XLcanFdConf() - if bitrate: - self.canFdConf.arbitrationBitRate = int(bitrate) - else: - self.canFdConf.arbitrationBitRate = 500000 - self.canFdConf.sjwAbr = int(sjw_abr) - self.canFdConf.tseg1Abr = int(tseg1_abr) - self.canFdConf.tseg2Abr = int(tseg2_abr) - if data_bitrate: - self.canFdConf.dataBitRate = int(data_bitrate) - else: - self.canFdConf.dataBitRate = self.canFdConf.arbitrationBitRate - self.canFdConf.sjwDbr = int(sjw_dbr) - self.canFdConf.tseg1Dbr = int(tseg1_dbr) - self.canFdConf.tseg2Dbr = int(tseg2_dbr) - - self.xldriver.xlCanFdSetConfiguration( - self.port_handle, self.mask, self.canFdConf - ) - LOG.info( - "SetFdConfig.: ABaudr.=%u, DBaudr.=%u", - self.canFdConf.arbitrationBitRate, - self.canFdConf.dataBitRate, - ) - LOG.info( - "SetFdConfig.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", - self.canFdConf.sjwAbr, - self.canFdConf.tseg1Abr, - self.canFdConf.tseg2Abr, - ) - LOG.info( - "SetFdConfig.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u", - self.canFdConf.sjwDbr, - self.canFdConf.tseg1Dbr, - self.canFdConf.tseg2Dbr, - ) - else: - if bitrate: - self.xldriver.xlCanSetChannelBitrate( - self.port_handle, permission_mask, bitrate + for channel in self.channels: + if permission_mask.value & self.channel_masks[channel]: + if fd: + self._set_bitrate_canfd( + channel=channel, + bitrate=bitrate, + data_bitrate=data_bitrate, + sjw_abr=sjw_abr, + tseg1_abr=tseg1_abr, + tseg2_abr=tseg2_abr, + sjw_dbr=sjw_dbr, + tseg1_dbr=tseg1_dbr, + tseg2_dbr=tseg2_dbr, ) - LOG.info("SetChannelBitrate: baudr.=%u", bitrate) - else: - LOG.info("No init access!") + elif bitrate: + self._set_bitrate_can(channel=channel, bitrate=bitrate) # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 @@ -348,6 +285,153 @@ def __init__( self._is_filtered = False super().__init__(channel=channel, can_filters=can_filters, **kwargs) + def _find_global_channel_idx( + self, + channel: int, + serial: Optional[int], + app_name: Optional[str], + channel_configs: List["VectorChannelConfig"], + ) -> int: + if serial is not None: + hw_type: Optional[xldefine.XL_HardwareType] = None + for channel_config in channel_configs: + if channel_config.serialNumber != serial: + continue + + hw_type = xldefine.XL_HardwareType(channel_config.hwType) + if channel_config.hwChannel == channel: + return channel_config.channelIndex + + if hw_type is None: + err_msg = f"No interface with serial {serial} found." + else: + err_msg = f"Channel {channel} not found on interface {hw_type.name} ({serial})." + raise CanInitializationError( + err_msg, error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT + ) + + if app_name: + hw_type, hw_index, hw_channel = self.get_application_config( + app_name, channel + ) + idx = cast( + int, self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) + ) + if idx < 0: + # Undocumented behavior! See issue #353. + # If hardware is unavailable, this function returns -1. + # Raise an exception as if the driver + # would have signalled XL_ERR_HW_NOT_PRESENT. + raise VectorInitializationError( + xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, + xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name, + "xlGetChannelIndex", + ) + return idx + + # check if channel is a valid global channel index + for channel_config in channel_configs: + if channel == channel_config.channelIndex: + return channel + + raise CanInitializationError( + f"Channel {channel} not found. The 'channel' parameter must be " + f"a valid global channel index if neither 'app_name' nor 'serial' were given.", + error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, + ) + + def _set_bitrate_can( + self, + channel: int, + bitrate: int, + sjw: Optional[int] = None, + tseg1: Optional[int] = None, + tseg2: Optional[int] = None, + sam: int = 1, + ) -> None: + kwargs = [sjw, tseg1, tseg2] + if any(kwargs) and not all(kwargs): + raise ValueError( + f"Either all of sjw, tseg1, tseg2 must be set or none of them." + ) + + # set parameters if channel has init access + if any(kwargs): + chip_params = xlclass.XLchipParams() + chip_params.bitRate = bitrate + chip_params.sjw = sjw + chip_params.tseg1 = tseg1 + chip_params.tseg2 = tseg2 + chip_params.sam = sam + self.xldriver.xlCanSetChannelParams( + self.port_handle, + self.channel_masks[channel], + chip_params, + ) + LOG.info( + "xlCanSetChannelParams: baudr.=%u, sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", + chip_params.bitRate, + chip_params.sjw, + chip_params.tseg1, + chip_params.tseg2, + ) + else: + self.xldriver.xlCanSetChannelBitrate( + self.port_handle, + self.channel_masks[channel], + bitrate, + ) + LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate) + + def _set_bitrate_canfd( + self, + channel: int, + bitrate: Optional[int] = None, + data_bitrate: Optional[int] = None, + sjw_abr: int = 2, + tseg1_abr: int = 6, + tseg2_abr: int = 3, + sjw_dbr: int = 2, + tseg1_dbr: int = 6, + tseg2_dbr: int = 3, + ) -> None: + # set parameters if channel has init access + canfd_conf = xlclass.XLcanFdConf() + if bitrate: + canfd_conf.arbitrationBitRate = int(bitrate) + else: + canfd_conf.arbitrationBitRate = 500_000 + canfd_conf.sjwAbr = int(sjw_abr) + canfd_conf.tseg1Abr = int(tseg1_abr) + canfd_conf.tseg2Abr = int(tseg2_abr) + if data_bitrate: + canfd_conf.dataBitRate = int(data_bitrate) + else: + canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate) + canfd_conf.sjwDbr = int(sjw_dbr) + canfd_conf.tseg1Dbr = int(tseg1_dbr) + canfd_conf.tseg2Dbr = int(tseg2_dbr) + self.xldriver.xlCanFdSetConfiguration( + self.port_handle, self.channel_masks[channel], canfd_conf + ) + LOG.info( + "xlCanFdSetConfiguration.: ABaudr.=%u, DBaudr.=%u", + canfd_conf.arbitrationBitRate, + canfd_conf.dataBitRate, + ) + LOG.info( + "xlCanFdSetConfiguration.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", + canfd_conf.sjwAbr, + canfd_conf.tseg1Abr, + canfd_conf.tseg2Abr, + ) + LOG.info( + "xlCanFdSetConfiguration.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u", + canfd_conf.sjwDbr, + canfd_conf.tseg1Dbr, + canfd_conf.tseg2Dbr, + ) + def _apply_filters(self, filters: Optional[CanFilters]) -> None: if filters: # Only up to one filter per ID type allowed @@ -544,7 +628,7 @@ def _send_sequence(self, msgs: Sequence[Message]) -> int: def _get_tx_channel_mask(self, msgs: Sequence[Message]) -> int: if len(msgs) == 1: - return self.channel_masks.get(msgs[0].channel, self.mask) + return self.channel_masks.get(msgs[0].channel, self.mask) # type: ignore[arg-type] else: return self.mask From 366e2391a517c3b828e90925508a9ea3c1bfd6f2 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 12 Sep 2022 15:02:58 +0200 Subject: [PATCH 0917/1235] Test on vector virtual bus if XL API is available (#1390) * refactoring for easier testing * use the vector virtual bus if XL driver is available * check xldriver to satisfy mypy * fix assertion --- can/interfaces/vector/canlib.py | 21 +- test/test_vector.py | 1118 ++++++++++++++++++++++--------- 2 files changed, 802 insertions(+), 337 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 333380a6f..ae60c5754 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -881,16 +881,25 @@ class VectorChannelConfig(NamedTuple): transceiverName: str -def get_channel_configs() -> List[VectorChannelConfig]: +def _get_xl_driver_config() -> xlclass.XLdriverConfig: if xldriver is None: - return [] + raise VectorError( + error_code=xldefine.XL_Status.XL_ERR_DLL_NOT_FOUND, + error_string="xldriver is unavailable", + function="_get_xl_driver_config", + ) driver_config = xlclass.XLdriverConfig() + xldriver.xlOpenDriver() + xldriver.xlGetDriverConfig(driver_config) + xldriver.xlCloseDriver() + return driver_config + + +def get_channel_configs() -> List[VectorChannelConfig]: try: - xldriver.xlOpenDriver() - xldriver.xlGetDriverConfig(driver_config) - xldriver.xlCloseDriver() + driver_config = _get_xl_driver_config() except VectorError: - pass + return [] channel_list: List[VectorChannelConfig] = [] for i in range(driver_config.channelCount): diff --git a/test/test_vector.py b/test/test_vector.py index 338783136..c4ae21f4e 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -5,9 +5,9 @@ """ import ctypes -import os +import functools import pickle -import unittest +import time from unittest.mock import Mock import pytest @@ -24,332 +24,792 @@ ) from test.config import IS_WINDOWS +XLDRIVER_FOUND = canlib.xldriver is not None -class TestVectorBus(unittest.TestCase): - def setUp(self) -> None: - # basic mock for XLDriver - can.interfaces.vector.canlib.xldriver = Mock() - - # bus creation functions - can.interfaces.vector.canlib.xldriver.xlOpenDriver = Mock() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig = Mock( - side_effect=xlGetApplConfig - ) - can.interfaces.vector.canlib.xldriver.xlGetChannelIndex = Mock( - side_effect=xlGetChannelIndex - ) - can.interfaces.vector.canlib.xldriver.xlOpenPort = Mock(side_effect=xlOpenPort) - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration = Mock( - return_value=0 - ) - can.interfaces.vector.canlib.xldriver.xlCanSetChannelMode = Mock(return_value=0) - can.interfaces.vector.canlib.xldriver.xlActivateChannel = Mock(return_value=0) - can.interfaces.vector.canlib.xldriver.xlGetSyncTime = Mock( - side_effect=xlGetSyncTime - ) - can.interfaces.vector.canlib.xldriver.xlCanSetChannelAcceptance = Mock( - return_value=0 - ) - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate = Mock( - return_value=0 - ) - can.interfaces.vector.canlib.xldriver.xlSetNotification = Mock( - side_effect=xlSetNotification - ) - - # bus deactivation functions - can.interfaces.vector.canlib.xldriver.xlDeactivateChannel = Mock(return_value=0) - can.interfaces.vector.canlib.xldriver.xlClosePort = Mock(return_value=0) - can.interfaces.vector.canlib.xldriver.xlCloseDriver = Mock() - - # sender functions - can.interfaces.vector.canlib.xldriver.xlCanTransmit = Mock(return_value=0) - can.interfaces.vector.canlib.xldriver.xlCanTransmitEx = Mock(return_value=0) - - # various functions - can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue = Mock() - can.interfaces.vector.canlib.WaitForSingleObject = Mock() - - self.bus = None - - def tearDown(self) -> None: - if self.bus: - self.bus.shutdown() - self.bus = None - - def test_bus_creation(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", _testing=True) - self.assertIsInstance(self.bus, canlib.VectorBus) - can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() - - can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() - xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] - self.assertEqual( - xlOpenPort_args[5], xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value - ) - self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) - - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() - - def test_bus_creation_bitrate(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", bitrate=200000, _testing=True) - self.assertIsInstance(self.bus, canlib.VectorBus) - can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() - - can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() - xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] - self.assertEqual( - xlOpenPort_args[5], xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value - ) - self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) - - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_called() - xlCanSetChannelBitrate_args = ( - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[0] - ) - self.assertEqual(xlCanSetChannelBitrate_args[2], 200000) - - def test_bus_creation_fd(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) - self.assertIsInstance(self.bus, canlib.VectorBus) - can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() - - can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() - xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] - self.assertEqual( - xlOpenPort_args[5], - xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, - ) - self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) - - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() - - def test_bus_creation_fd_bitrate_timings(self) -> None: - self.bus = can.Bus( - channel=0, - bustype="vector", - fd=True, - bitrate=500000, - data_bitrate=2000000, - sjw_abr=10, - tseg1_abr=11, - tseg2_abr=12, - sjw_dbr=13, - tseg1_dbr=14, - tseg2_dbr=15, - _testing=True, - ) - self.assertIsInstance(self.bus, canlib.VectorBus) - can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() - - can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() - xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] - self.assertEqual( - xlOpenPort_args[5], - xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, - ) - self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) - - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() - - xlCanFdSetConfiguration_args = ( - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[0] - ) - canFdConf = xlCanFdSetConfiguration_args[2] - self.assertEqual(canFdConf.arbitrationBitRate, 500000) - self.assertEqual(canFdConf.dataBitRate, 2000000) - self.assertEqual(canFdConf.sjwAbr, 10) - self.assertEqual(canFdConf.tseg1Abr, 11) - self.assertEqual(canFdConf.tseg2Abr, 12) - self.assertEqual(canFdConf.sjwDbr, 13) - self.assertEqual(canFdConf.tseg1Dbr, 14) - self.assertEqual(canFdConf.tseg2Dbr, 15) - - def test_receive(self) -> None: - can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive) - self.bus = can.Bus(channel=0, bustype="vector", _testing=True) - self.bus.recv(timeout=0.05) - can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() - - def test_receive_fd(self) -> None: - can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( - side_effect=xlCanReceive - ) - self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) - self.bus.recv(timeout=0.05) - can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() - - def test_receive_non_msg_event(self) -> None: - can.interfaces.vector.canlib.xldriver.xlReceive = Mock( - side_effect=xlReceive_chipstate - ) - self.bus = can.Bus(channel=0, bustype="vector", _testing=True) - self.bus.handle_can_event = Mock() - self.bus.recv(timeout=0.05) - can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() - self.bus.handle_can_event.assert_called() - - def test_receive_fd_non_msg_event(self) -> None: - can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( - side_effect=xlCanReceive_chipstate - ) - self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) - self.bus.handle_canfd_event = Mock() - self.bus.recv(timeout=0.05) - can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() - self.bus.handle_canfd_event.assert_called() - - def test_send(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", _testing=True) - msg = can.Message( - arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True - ) - self.bus.send(msg) - can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_not_called() - - def test_send_fd(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) - msg = can.Message( - arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True - ) - self.bus.send(msg) - can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_called() - - def test_flush_tx_buffer(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", _testing=True) - self.bus.flush_tx_buffer() - can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called() - - def test_shutdown(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", _testing=True) - self.bus.shutdown() - can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() - can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called() - can.interfaces.vector.canlib.xldriver.xlCloseDriver.assert_called() - - def test_reset(self) -> None: - self.bus = can.Bus(channel=0, bustype="vector", _testing=True) - self.bus.reset() - can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() - can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called() - - def test_popup_hw_cfg(self) -> None: - canlib.xldriver.xlPopupHwConfig = Mock() - canlib.VectorBus.popup_vector_hw_configuration(10) - assert canlib.xldriver.xlPopupHwConfig.called - args, kwargs = canlib.xldriver.xlPopupHwConfig.call_args - assert isinstance(args[0], ctypes.c_char_p) - assert isinstance(args[1], ctypes.c_uint) - - def test_get_application_config(self) -> None: - canlib.xldriver.xlGetApplConfig = Mock() - canlib.VectorBus.get_application_config(app_name="CANalyzer", app_channel=0) - assert canlib.xldriver.xlGetApplConfig.called - - def test_set_application_config(self) -> None: - canlib.xldriver.xlSetApplConfig = Mock() - canlib.VectorBus.set_application_config( - app_name="CANalyzer", - app_channel=0, - hw_type=xldefine.XL_HardwareType.XL_HWTYPE_VN1610, - hw_index=0, - hw_channel=0, - ) - assert canlib.xldriver.xlSetApplConfig.called - - def test_set_timer_rate(self) -> None: - canlib.xldriver.xlSetTimerRate = Mock() - bus: canlib.VectorBus = can.Bus( - channel=0, bustype="vector", fd=True, _testing=True - ) - bus.set_timer_rate(timer_rate_ms=1) - assert canlib.xldriver.xlSetTimerRate.called - - def test_called_without_testing_argument(self) -> None: - """This tests if an exception is thrown when we are not running on Windows.""" - if os.name != "nt": - with self.assertRaises(can.CanInterfaceNotImplementedError): - # do not set the _testing argument, since it would suppress the exception - can.Bus(channel=0, bustype="vector") - - def test_vector_error_pickle(self) -> None: - for error_type in [ - VectorError, - VectorInitializationError, - VectorOperationError, - ]: - with self.subTest(f"error_type = {error_type.__name__}"): - - error_code = 118 - error_string = "XL_ERROR" - function = "function_name" - - exc = error_type(error_code, error_string, function) - - # pickle and unpickle - p = pickle.dumps(exc) - exc_unpickled: VectorError = pickle.loads(p) - - self.assertEqual(str(exc), str(exc_unpickled)) - self.assertEqual(error_code, exc_unpickled.error_code) - - with pytest.raises(error_type): - raise exc_unpickled - - def test_vector_subtype_error_from_generic(self) -> None: - for error_type in [VectorInitializationError, VectorOperationError]: - with self.subTest(f"error_type = {error_type.__name__}"): - - error_code = 118 - error_string = "XL_ERROR" - function = "function_name" - - generic = VectorError(error_code, error_string, function) - - # pickle and unpickle - specific: VectorError = error_type.from_generic(generic) - - self.assertEqual(str(generic), str(specific)) - self.assertEqual(error_code, specific.error_code) - - with pytest.raises(error_type): - raise specific - - @unittest.skipUnless(IS_WINDOWS, "Windows specific test") - def test_winapi_availability(self) -> None: - self.assertIsNotNone(canlib.WaitForSingleObject) - self.assertIsNotNone(canlib.INFINITE) - - -class TestVectorChannelConfig: - def test_attributes(self): - assert hasattr(VectorChannelConfig, "name") - assert hasattr(VectorChannelConfig, "hwType") - assert hasattr(VectorChannelConfig, "hwIndex") - assert hasattr(VectorChannelConfig, "hwChannel") - assert hasattr(VectorChannelConfig, "channelIndex") - assert hasattr(VectorChannelConfig, "channelMask") - assert hasattr(VectorChannelConfig, "channelCapabilities") - assert hasattr(VectorChannelConfig, "channelBusCapabilities") - assert hasattr(VectorChannelConfig, "isOnBus") - assert hasattr(VectorChannelConfig, "connectedBusType") - assert hasattr(VectorChannelConfig, "serialNumber") - assert hasattr(VectorChannelConfig, "articleNumber") - assert hasattr(VectorChannelConfig, "transceiverName") + +@pytest.fixture() +def mock_xldriver() -> None: + # basic mock for XLDriver + xldriver_mock = Mock() + + # bus creation functions + xldriver_mock.xlOpenDriver = Mock() + xldriver_mock.xlGetApplConfig = Mock(side_effect=xlGetApplConfig) + xldriver_mock.xlGetChannelIndex = Mock(side_effect=xlGetChannelIndex) + xldriver_mock.xlOpenPort = Mock(side_effect=xlOpenPort) + xldriver_mock.xlCanFdSetConfiguration = Mock(return_value=0) + xldriver_mock.xlCanSetChannelMode = Mock(return_value=0) + xldriver_mock.xlActivateChannel = Mock(return_value=0) + xldriver_mock.xlGetSyncTime = Mock(side_effect=xlGetSyncTime) + xldriver_mock.xlCanSetChannelAcceptance = Mock(return_value=0) + xldriver_mock.xlCanSetChannelBitrate = Mock(return_value=0) + xldriver_mock.xlSetNotification = Mock(side_effect=xlSetNotification) + + # bus deactivation functions + xldriver_mock.xlDeactivateChannel = Mock(return_value=0) + xldriver_mock.xlClosePort = Mock(return_value=0) + xldriver_mock.xlCloseDriver = Mock() + + # sender functions + xldriver_mock.xlCanTransmit = Mock(return_value=0) + xldriver_mock.xlCanTransmitEx = Mock(return_value=0) + + # various functions + xldriver_mock.xlCanFlushTransmitQueue = Mock() + + # backup unmodified values + real_xldriver = canlib.xldriver + real_waitforsingleobject = canlib.WaitForSingleObject + + # set mock + canlib.xldriver = xldriver_mock + canlib.HAS_EVENTS = False + + yield + + # cleanup + canlib.xldriver = real_xldriver + canlib.WaitForSingleObject = real_waitforsingleobject + + +def test_bus_creation_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, bustype="vector", _testing=True) + assert isinstance(bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value + assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_bus_creation() -> None: + bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + assert isinstance(bus, canlib.VectorBus) + bus.shutdown() + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert bus.channel_masks[0] == xl_channel_config.channelMask + assert ( + xl_channel_config.busParams.data.can.canOpMode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 + ) + + bus = canlib.VectorBus(channel=0, serial=_find_virtual_can_serial()) + assert isinstance(bus, canlib.VectorBus) + bus.shutdown() + + +def test_bus_creation_bitrate_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, bustype="vector", bitrate=200_000, _testing=True) + assert isinstance(bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value + assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_called() + xlCanSetChannelBitrate_args = ( + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[0] + ) + assert xlCanSetChannelBitrate_args[2] == 200_000 + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_bus_creation_bitrate() -> None: + bus = can.Bus( + channel=0, serial=_find_virtual_can_serial(), bustype="vector", bitrate=200_000 + ) + assert isinstance(bus, canlib.VectorBus) + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert xl_channel_config.busParams.data.can.bitRate == 200_000 + + bus.shutdown() + + +def test_bus_creation_fd_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + assert isinstance(bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + assert ( + xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value + ) + assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_bus_creation_fd() -> None: + bus = can.Bus( + channel=0, serial=_find_virtual_can_serial(), bustype="vector", fd=True + ) + assert isinstance(bus, canlib.VectorBus) + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert ( + xl_channel_config.interfaceVersion + == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 + ) + assert ( + xl_channel_config.busParams.data.canFD.canOpMode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD + ) + bus.shutdown() + + +def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None: + bus = can.Bus( + channel=0, + bustype="vector", + fd=True, + bitrate=500_000, + data_bitrate=2_000_000, + sjw_abr=10, + tseg1_abr=11, + tseg2_abr=12, + sjw_dbr=13, + tseg1_dbr=14, + tseg2_dbr=15, + _testing=True, + ) + assert isinstance(bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + assert ( + xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value + ) + + assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + xlCanFdSetConfiguration_args = ( + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[0] + ) + canFdConf = xlCanFdSetConfiguration_args[2] + assert canFdConf.arbitrationBitRate == 500000 + assert canFdConf.dataBitRate == 2000000 + assert canFdConf.sjwAbr == 10 + assert canFdConf.tseg1Abr == 11 + assert canFdConf.tseg2Abr == 12 + assert canFdConf.sjwDbr == 13 + assert canFdConf.tseg1Dbr == 14 + assert canFdConf.tseg2Dbr == 15 + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_bus_creation_fd_bitrate_timings() -> None: + bus = can.Bus( + channel=0, + serial=_find_virtual_can_serial(), + bustype="vector", + fd=True, + bitrate=500_000, + data_bitrate=2_000_000, + sjw_abr=10, + tseg1_abr=11, + tseg2_abr=12, + sjw_dbr=13, + tseg1_dbr=14, + tseg2_dbr=15, + ) + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert ( + xl_channel_config.interfaceVersion + == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 + ) + assert ( + xl_channel_config.busParams.data.canFD.canOpMode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD + ) + assert xl_channel_config.busParams.data.canFD.arbitrationBitRate == 500_000 + assert xl_channel_config.busParams.data.canFD.sjwAbr == 10 + assert xl_channel_config.busParams.data.canFD.tseg1Abr == 11 + assert xl_channel_config.busParams.data.canFD.tseg2Abr == 12 + assert xl_channel_config.busParams.data.canFD.sjwDbr == 13 + assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 14 + assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 15 + assert xl_channel_config.busParams.data.canFD.dataBitRate == 2_000_000 + + bus.shutdown() + + +def test_send_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, bustype="vector", _testing=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + bus.send(msg) + can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_not_called() + + +def test_send_fd_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + bus.send(msg) + can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_called() + + +def test_receive_mocked(mock_xldriver) -> None: + can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive) + bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() + + +def test_receive_fd_mocked(mock_xldriver) -> None: + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(side_effect=xlCanReceive) + bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_send_and_receive() -> None: + bus1 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + bus2 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + + msg_std = can.Message( + channel=0, arbitration_id=0xFF, data=list(range(8)), is_extended_id=False + ) + msg_ext = can.Message( + channel=0, arbitration_id=0xFFFFFF, data=list(range(8)), is_extended_id=True + ) + + bus1.send(msg_std) + msg_std_recv = bus2.recv(None) + assert msg_std.equals(msg_std_recv, timestamp_delta=None) + + bus1.send(msg_ext) + msg_ext_recv = bus2.recv(None) + assert msg_ext.equals(msg_ext_recv, timestamp_delta=None) + + bus1.shutdown() + bus2.shutdown() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_send_and_receive_fd() -> None: + bus1 = can.Bus( + channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" + ) + bus2 = can.Bus( + channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" + ) + + msg_std = can.Message( + channel=0, + arbitration_id=0xFF, + data=list(range(64)), + is_extended_id=False, + is_fd=True, + ) + msg_ext = can.Message( + channel=0, + arbitration_id=0xFFFFFF, + data=list(range(64)), + is_extended_id=True, + is_fd=True, + ) + + bus1.send(msg_std) + msg_std_recv = bus2.recv(None) + assert msg_std.equals(msg_std_recv, timestamp_delta=None) + + bus1.send(msg_ext) + msg_ext_recv = bus2.recv(None) + assert msg_ext.equals(msg_ext_recv, timestamp_delta=None) + + bus1.shutdown() + bus2.shutdown() + + +def test_receive_non_msg_event_mocked(mock_xldriver) -> None: + can.interfaces.vector.canlib.xldriver.xlReceive = Mock( + side_effect=xlReceive_chipstate + ) + bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus.handle_can_event = Mock() + bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() + bus.handle_can_event.assert_called() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_receive_non_msg_event() -> None: + bus = canlib.VectorBus( + channel=0, serial=_find_virtual_can_serial(), bustype="vector" + ) + bus.handle_can_event = Mock() + bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0]) + bus.recv(timeout=0.5) + bus.handle_can_event.assert_called() + bus.shutdown() + + +def test_receive_fd_non_msg_event_mocked(mock_xldriver) -> None: + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( + side_effect=xlCanReceive_chipstate + ) + bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + bus.handle_canfd_event = Mock() + bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() + bus.handle_canfd_event.assert_called() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_receive_fd_non_msg_event() -> None: + bus = canlib.VectorBus( + channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" + ) + bus.handle_canfd_event = Mock() + bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0]) + bus.recv(timeout=0.5) + bus.handle_canfd_event.assert_called() + bus.shutdown() + + +def test_flush_tx_buffer_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus.flush_tx_buffer() + can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_flush_tx_buffer() -> None: + bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + bus.flush_tx_buffer() + bus.shutdown() + + +def test_shutdown_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus.shutdown() + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() + can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called() + can.interfaces.vector.canlib.xldriver.xlCloseDriver.assert_called() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_shutdown() -> None: + bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert xl_channel_config.isOnBus != 0 + bus.shutdown() + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert xl_channel_config.isOnBus == 0 + + +def test_reset_mocked(mock_xldriver) -> None: + bus = canlib.VectorBus(channel=0, bustype="vector", _testing=True) + bus.reset() + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() + can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called() + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_reset_mocked() -> None: + bus = canlib.VectorBus( + channel=0, serial=_find_virtual_can_serial(), bustype="vector" + ) + bus.reset() + bus.shutdown() + + +def test_popup_hw_cfg_mocked(mock_xldriver) -> None: + canlib.xldriver.xlPopupHwConfig = Mock() + canlib.VectorBus.popup_vector_hw_configuration(10) + assert canlib.xldriver.xlPopupHwConfig.called + args, kwargs = canlib.xldriver.xlPopupHwConfig.call_args + assert isinstance(args[0], ctypes.c_char_p) + assert isinstance(args[1], ctypes.c_uint) + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_popup_hw_cfg() -> None: + with pytest.raises(VectorOperationError): + canlib.VectorBus.popup_vector_hw_configuration(1) + + +def test_get_application_config_mocked(mock_xldriver) -> None: + canlib.xldriver.xlGetApplConfig = Mock() + canlib.VectorBus.get_application_config(app_name="CANalyzer", app_channel=0) + assert canlib.xldriver.xlGetApplConfig.called + + +def test_set_application_config_mocked(mock_xldriver) -> None: + canlib.xldriver.xlSetApplConfig = Mock() + canlib.VectorBus.set_application_config( + app_name="CANalyzer", + app_channel=0, + hw_type=xldefine.XL_HardwareType.XL_HWTYPE_VN1610, + hw_index=0, + hw_channel=0, + ) + assert canlib.xldriver.xlSetApplConfig.called + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_set_and_get_application_config() -> None: + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=1 + ) + canlib.VectorBus.set_application_config( + app_name="python-can::test_vector", + app_channel=5, + hw_channel=xl_channel_config.hwChannel, + hw_index=xl_channel_config.hwIndex, + hw_type=xldefine.XL_HardwareType(xl_channel_config.hwType), + ) + hw_type, hw_index, hw_channel = canlib.VectorBus.get_application_config( + app_name="python-can::test_vector", + app_channel=5, + ) + assert hw_type == xldefine.XL_HardwareType(xl_channel_config.hwType) + assert hw_index == xl_channel_config.hwIndex + assert hw_channel == xl_channel_config.hwChannel + + +def test_set_timer_mocked(mock_xldriver) -> None: + canlib.xldriver.xlSetTimerRate = Mock() + bus = canlib.VectorBus(channel=0, bustype="vector", fd=True, _testing=True) + bus.set_timer_rate(timer_rate_ms=1) + assert canlib.xldriver.xlSetTimerRate.called + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_set_timer() -> None: + bus = canlib.VectorBus( + channel=0, serial=_find_virtual_can_serial(), bustype="vector" + ) + bus.handle_can_event = Mock() + bus.set_timer_rate(timer_rate_ms=1) + t0 = time.perf_counter() + while time.perf_counter() - t0 < 0.5: + bus.recv(timeout=-1) + + # call_count is incorrect when using virtual bus + # assert bus.handle_can_event.call_count > 498 + # assert bus.handle_can_event.call_count < 502 + + +@pytest.mark.skipif(IS_WINDOWS, reason="Not relevant for Windows.") +def test_called_without_testing_argument() -> None: + """This tests if an exception is thrown when we are not running on Windows.""" + with pytest.raises(can.CanInterfaceNotImplementedError): + # do not set the _testing argument, since it would suppress the exception + can.Bus(channel=0, bustype="vector") + + +def test_vector_error_pickle() -> None: + for error_type in [ + VectorError, + VectorInitializationError, + VectorOperationError, + ]: + error_code = 118 + error_string = "XL_ERROR" + function = "function_name" + + exc = error_type(error_code, error_string, function) + + # pickle and unpickle + p = pickle.dumps(exc) + exc_unpickled: VectorError = pickle.loads(p) + + assert str(exc) == str(exc_unpickled) + assert error_code == exc_unpickled.error_code + + with pytest.raises(error_type): + raise exc_unpickled + + +def test_vector_subtype_error_from_generic() -> None: + for error_type in [VectorInitializationError, VectorOperationError]: + error_code = 118 + error_string = "XL_ERROR" + function = "function_name" + + generic = VectorError(error_code, error_string, function) + + # pickle and unpickle + specific: VectorError = error_type.from_generic(generic) + + assert str(generic) == str(specific) + assert error_code == specific.error_code + + with pytest.raises(error_type): + raise specific + + +def test_get_channel_configs() -> None: + _original_func = canlib._get_xl_driver_config + canlib._get_xl_driver_config = _get_predefined_xl_driver_config + + channel_configs = canlib.get_channel_configs() + assert len(channel_configs) == 12 + + canlib._get_xl_driver_config = _original_func + + +@pytest.mark.skipif(not IS_WINDOWS, reason="Windows specific test") +def test_winapi_availability() -> None: + assert canlib.WaitForSingleObject is not None + assert canlib.INFINITE is not None + + +def test_vector_channel_config_attributes(): + assert hasattr(VectorChannelConfig, "name") + assert hasattr(VectorChannelConfig, "hwType") + assert hasattr(VectorChannelConfig, "hwIndex") + assert hasattr(VectorChannelConfig, "hwChannel") + assert hasattr(VectorChannelConfig, "channelIndex") + assert hasattr(VectorChannelConfig, "channelMask") + assert hasattr(VectorChannelConfig, "channelCapabilities") + assert hasattr(VectorChannelConfig, "channelBusCapabilities") + assert hasattr(VectorChannelConfig, "isOnBus") + assert hasattr(VectorChannelConfig, "connectedBusType") + assert hasattr(VectorChannelConfig, "serialNumber") + assert hasattr(VectorChannelConfig, "articleNumber") + assert hasattr(VectorChannelConfig, "transceiverName") + + +# ***************************************************************************** +# Utility functions +# ***************************************************************************** + + +def _find_xl_channel_config(serial: int, channel: int) -> xlclass.XLchannelConfig: + """Helper function""" + xl_driver_config = xlclass.XLdriverConfig() + canlib.xldriver.xlOpenDriver() + canlib.xldriver.xlGetDriverConfig(xl_driver_config) + canlib.xldriver.xlCloseDriver() + + for i in range(xl_driver_config.channelCount): + xl_channel_config: xlclass.XLchannelConfig = xl_driver_config.channel[i] + + if xl_channel_config.serialNumber != serial: + continue + + if xl_channel_config.hwChannel != channel: + continue + + return xl_channel_config + + raise LookupError("XLchannelConfig not found.") + + +@functools.lru_cache() +def _find_virtual_can_serial() -> int: + """Serial number might be 0 or 100 depending on driver version.""" + xl_driver_config = xlclass.XLdriverConfig() + canlib.xldriver.xlOpenDriver() + canlib.xldriver.xlGetDriverConfig(xl_driver_config) + canlib.xldriver.xlCloseDriver() + + for i in range(xl_driver_config.channelCount): + xl_channel_config: xlclass.XLchannelConfig = xl_driver_config.channel[i] + + if xl_channel_config.transceiverName.decode() == "Virtual CAN": + return xl_channel_config.serialNumber + + raise LookupError("Vector virtual CAN not found") + + +XL_DRIVER_CONFIG_EXAMPLE = ( + b"\x0E\x00\x1E\x14\x0C\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E" + b"\x65\x6C\x20\x53\x74\x72\x65\x61\x6D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x2D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04" + b"\x0A\x40\x00\x02\x00\x02\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E" + b"\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08" + b"\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31" + b"\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x01\x03\x02\x00\x00\x00\x00\x01\x02\x00\x00" + b"\x00\x00\x00\x00\x00\x02\x10\x00\x08\x07\x01\x04\x00\x00\x00\x00\x00\x00\x04\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00" + b"\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x46\x52\x70\x69\x67\x67\x79\x20\x31\x30" + b"\x38\x30\x41\x6D\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x05\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x32\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x02\x3C\x01\x00" + b"\x00\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\xA2\x03\x05\x01\x00" + b"\x00\x00\x04\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01\x00\x00" + b"\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00" + b"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4F\x6E\x20" + b"\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35\x31\x63\x61\x70\x28\x48\x69" + b"\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03" + b"\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E" + b"\x6E\x65\x6C\x20\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x2D\x00\x03\x3C\x01\x00\x00\x00\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x12" + b"\x00\x00\xA2\x03\x09\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00" + b"\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x9B\x00\x00\x00\x68\x89\x09" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00" + b"\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00" + b"\x08\x1C\x00\x00\x4F\x6E\x20\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35" + b"\x31\x63\x61\x70\x28\x48\x69\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39" + b"\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x34\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x04\x33\x01\x00\x00\x00\x00\x04\x10\x00" + b"\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x09\x02\x08\x00\x00\x00\x00\x00\x02" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x03" + b"\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4C\x49\x4E\x70\x69\x67\x67\x79\x20" + b"\x37\x32\x36\x39\x6D\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x07\x00\x00\x00\x70\x17\x00\x00\x0C\x09\x03\x04\x58\x02\x10\x0E\x30" + b"\x57\x05\x00\x00\x00\x00\x00\x88\x13\x88\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x35\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x05\x00\x00" + b"\x00\x00\x02\x00\x05\x20\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x0C\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00" + b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61" + b"\x6E\x6E\x65\x6C\x20\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x2D\x00\x06\x00\x00\x00\x00\x02\x00\x06\x40\x00\x00\x00\x00\x00\x00\x00" + b"\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00" + b"\x00\x08\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38" + b"\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x37\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x07\x00\x00\x00\x00\x02\x00\x07\x80" + b"\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x38" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x08\x3C" + b"\x01\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x12\x00\x00\xA2\x01\x00" + b"\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01" + b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00" + b"\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4F" + b"\x6E\x20\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35\x31\x63\x61\x70\x28" + b"\x48\x69\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68" + b"\x61\x6E\x6E\x65\x6C\x20\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x2D\x00\x09\x80\x02\x00\x00\x00\x00\x09\x00\x02\x00\x00\x00\x00\x00" + b"\x00\x02\x00\x00\x00\x40\x00\x40\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x03\x00\x00\x00\x00\x00" + b"\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03" + b"\x00\x00\x08\x1C\x00\x00\x44\x2F\x41\x20\x49\x4F\x70\x69\x67\x67\x79\x20\x38\x36" + b"\x34\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69" + b"\x72\x74\x75\x61\x6C\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x31\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x16\x00\x00\x00\x00\x00\x0A" + b"\x00\x04\x00\x00\x00\x00\x00\x00\x07\x00\x00\xA0\x01\x00\x01\x00\x00\x00\x00\x00" + b"\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00" + b"\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x1E" + b"\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6C" + b"\x20\x43\x41\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6C\x20\x43\x68\x61\x6E\x6E\x65\x6C" + b"\x20\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01" + b"\x16\x00\x00\x00\x00\x00\x0B\x00\x08\x00\x00\x00\x00\x00\x00\x07\x00\x00\xA0\x01" + b"\x00\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01" + b"\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x10\x00\x1E\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x56\x69\x72\x74\x75\x61\x6C\x20\x43\x41\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x02" + 11832 * b"\x00" +) + + +def _get_predefined_xl_driver_config() -> xlclass.XLdriverConfig: + return xlclass.XLdriverConfig.from_buffer_copy(XL_DRIVER_CONFIG_EXAMPLE) + + +# ***************************************************************************** +# Mock functions/side effects +# ***************************************************************************** def xlGetApplConfig( @@ -454,7 +914,3 @@ def xlCanReceive_chipstate( event.timeStamp = 0 event.chanIndex = 2 return 0 - - -if __name__ == "__main__": - unittest.main() From c3a5c7ab969cd40711dba992e6a95ec8df4551b6 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 13 Sep 2022 17:15:38 +0200 Subject: [PATCH 0918/1235] Provide meaningful error message for xlGetApplConfig error (#1392) --- can/interfaces/vector/canlib.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ae60c5754..7737a99d3 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -791,14 +791,24 @@ def get_application_config( hw_channel = ctypes.c_uint() _app_channel = ctypes.c_uint(app_channel) - xldriver.xlGetApplConfig( - app_name.encode(), - _app_channel, - hw_type, - hw_index, - hw_channel, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - ) + try: + xldriver.xlGetApplConfig( + app_name.encode(), + _app_channel, + hw_type, + hw_index, + hw_channel, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + ) + except VectorError as e: + raise VectorInitializationError( + error_code=e.error_code, + error_string=( + f"Vector HW Config: Channel '{app_channel}' of " + f"application '{app_name}' is not assigned to any interface" + ), + function="xlGetApplConfig", + ) from None return xldefine.XL_HardwareType(hw_type.value), hw_index.value, hw_channel.value @staticmethod From b2b2a80486bb2c165b710baed509a6654df76703 Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Tue, 13 Sep 2022 13:27:26 -0500 Subject: [PATCH 0919/1235] Pass file mode to compress function (#1384) --- can/io/logger.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 478651953..ec34079b4 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -88,7 +88,7 @@ def __new__( # type: ignore file_or_filename: AcceptedIOType = filename if suffix == ".gz": - suffix, file_or_filename = Logger.compress(filename) + suffix, file_or_filename = Logger.compress(filename, *args, **kwargs) try: return Logger.message_writers[suffix](file_or_filename, *args, **kwargs) @@ -98,13 +98,18 @@ def __new__( # type: ignore ) from None @staticmethod - def compress(filename: StringPathLike) -> Tuple[str, FileLike]: + def compress( + filename: StringPathLike, *args: Any, **kwargs: Any + ) -> Tuple[str, FileLike]: """ Return the suffix and io object of the decompressed file. File will automatically recompress upon close. """ real_suffix = pathlib.Path(filename).suffixes[-2].lower() - mode = "ab" if real_suffix == ".blf" else "at" + if kwargs.get("append", False): + mode = "ab" if real_suffix == ".blf" else "at" + else: + mode = "wb" if real_suffix == ".blf" else "wt" return real_suffix, gzip.open(filename, mode) From 23b6b1916532b6e6ba2e5cc6f2e0d139c428a02a Mon Sep 17 00:00:00 2001 From: Brent Barbachem Date: Mon, 3 Oct 2022 09:16:57 -0400 Subject: [PATCH 0920/1235] Update BufferedReader.get_message docstring (#1397) ** Docstring now states that the `get_message` method utilizes a FIFO ordering for grabbing messages from the queue. --- can/listener.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/listener.py b/can/listener.py index 12836a83c..6c9cdf0be 100644 --- a/can/listener.py +++ b/can/listener.py @@ -105,13 +105,13 @@ def on_message_received(self, msg: Message) -> None: def get_message(self, timeout: float = 0.5) -> Optional[Message]: """ - Attempts to retrieve the 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. + Attempts to retrieve the message that has been in the queue for the longest amount + of time (FIFO). If no message is available, it blocks for given timeout or until a + message is received (whichever is shorter), or else returns None. This method does + not block after :meth:`can.BufferedReader.stop` has been called. :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. + :return: the received :class:`can.Message` or `None`, if the queue is empty. """ try: if self.is_stopped: From 89c395fd315179b1eb2ca8e21a852c825bb385b8 Mon Sep 17 00:00:00 2001 From: Nazia Povey Date: Mon, 3 Oct 2022 09:43:41 -0400 Subject: [PATCH 0921/1235] Move windows-curses dependency to an optional extra (#1395) * Move windows-curses dependency to an optional extra Python 3.11 wheels for windows-curses are [not yet available][1], and this meant that python-can could not be installed on windows with Python 3.11. Since windows-curses is only used in viewer.py, change the dependnecy to an optional extra. [1]: https://github.com/zephyrproject-rtos/windows-curses/issues/31 * change python3 to python for Windows Co-authored-by: Hashem Nasarat Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- doc/installation.rst | 10 ++++++++++ setup.py | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/installation.rst b/doc/installation.rst index fe7204ba6..bfce72180 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -103,6 +103,16 @@ If ``python-can`` is already installed, the CANtact backend can be installed sep Additional CANtact documentation is available at `cantact.io `__. +CanViewer +~~~~~~~~~ + +``python-can`` has support for showing a simple CAN viewer terminal application +by running ``python -m can.viewer``. On Windows, this depends on the +`windows-curses library `__ which can +be installed with: + +``python -m pip install "python-can[viewer]"`` + Installing python-can in development mode ----------------------------------------- diff --git a/setup.py b/setup.py index c9defecab..adbd61f91 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,9 @@ "gs_usb": ["gs_usb>=0.2.1"], "nixnet": ["nixnet>=0.3.1"], "pcan": ["uptime~=3.0.1"], + "viewer": [ + 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"' + ], } setup( @@ -86,7 +89,6 @@ install_requires=[ "setuptools", "wrapt~=1.10", - 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"', "typing_extensions>=3.10.0.0", 'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"', 'msgpack~=1.0.0;platform_system!="Windows"', From a144f25689728bad7ed39de4a70f2260c04df9be Mon Sep 17 00:00:00 2001 From: Martin Kletzander Date: Fri, 7 Oct 2022 20:32:47 +0200 Subject: [PATCH 0922/1235] Test load_config() (#1396) * listener_test: Do not modify environment for other tests * network_test: Do not modify environment for other tests Also do not skip the test since there will always be an interface (previously set globally and now prepared in setUp function. * test_load_file_config: Test with different values Testing with the same interface values would not show possible issues in case there were any. * tests: Add test for load_config Add tests for load_config with contexts and environment variables. Closes #345 Signed-off-by: Martin Kletzander Signed-off-by: Martin Kletzander --- test/listener_test.py | 8 ++-- test/network_test.py | 11 ++++- test/test_load_config.py | 86 +++++++++++++++++++++++++++++++++++ test/test_load_file_config.py | 2 +- 4 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 test/test_load_config.py diff --git a/test/listener_test.py b/test/listener_test.py index e5abd94a2..0e64a266a 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -15,9 +15,6 @@ from .data.example_data import generate_message -channel = "virtual_channel_0" -can.rc["interface"] = "virtual" - logging.basicConfig(level=logging.DEBUG) # makes the random number generator deterministic @@ -55,10 +52,15 @@ def testClassesImportable(self): class BusTest(unittest.TestCase): def setUp(self): + # Save all can.rc defaults + self._can_rc = can.rc + can.rc = {"interface": "virtual"} self.bus = can.interface.Bus() def tearDown(self): self.bus.shutdown() + # Restore the defaults + can.rc = self._can_rc class ListenerTest(BusTest): diff --git a/test/network_test.py b/test/network_test.py index 5900cd10f..58c305a38 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -14,10 +14,8 @@ import can channel = "vcan0" -can.rc["interface"] = "virtual" -@unittest.skipIf("interface" not in can.rc, "Need a CAN interface") class ControllerAreaNetworkTestCase(unittest.TestCase): """ This test ensures that what messages go in to the bus is what comes out. @@ -42,6 +40,15 @@ class ControllerAreaNetworkTestCase(unittest.TestCase): for b in range(num_messages) ) + def setUp(self): + # Save all can.rc defaults + self._can_rc = can.rc + can.rc = {"interface": "virtual"} + + def tearDown(self): + # Restore the defaults + can.rc = self._can_rc + def producer(self, ready_event, msg_read): self.client_bus = can.interface.Bus(channel=channel) ready_event.wait() diff --git a/test/test_load_config.py b/test/test_load_config.py new file mode 100644 index 000000000..a2969b0a5 --- /dev/null +++ b/test/test_load_config.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +import os +import shutil +import tempfile +import unittest +from tempfile import NamedTemporaryFile + +import can + + +class LoadConfigTest(unittest.TestCase): + configuration = { + "default": {"interface": "serial", "channel": "0"}, + "one": {"interface": "kvaser", "channel": "1", "bitrate": 100000}, + "two": {"channel": "2"}, + } + + def setUp(self): + # Create a temporary directory + self.test_dir = tempfile.mkdtemp() + + def tearDown(self): + # Remove the directory after the test + shutil.rmtree(self.test_dir) + + def _gen_configration_file(self, sections): + with NamedTemporaryFile( + mode="w", dir=self.test_dir, delete=False + ) as tmp_config_file: + content = [] + for section in sections: + content.append("[{}]".format(section)) + for k, v in self.configuration[section].items(): + content.append("{} = {}".format(k, v)) + tmp_config_file.write("\n".join(content)) + return tmp_config_file.name + + def _dict_to_env(self, d): + return {f"CAN_{k.upper()}": str(v) for k, v in d.items()} + + def test_config_default(self): + tmp_config = self._gen_configration_file(["default"]) + config = can.util.load_config(path=tmp_config) + self.assertEqual(config, self.configuration["default"]) + + def test_config_whole_default(self): + tmp_config = self._gen_configration_file(self.configuration) + config = can.util.load_config(path=tmp_config) + self.assertEqual(config, self.configuration["default"]) + + def test_config_whole_context(self): + tmp_config = self._gen_configration_file(self.configuration) + config = can.util.load_config(path=tmp_config, context="one") + self.assertEqual(config, self.configuration["one"]) + + def test_config_merge_context(self): + tmp_config = self._gen_configration_file(self.configuration) + config = can.util.load_config(path=tmp_config, context="two") + expected = self.configuration["default"] + expected.update(self.configuration["two"]) + self.assertEqual(config, expected) + + def test_config_merge_environment_to_context(self): + tmp_config = self._gen_configration_file(self.configuration) + env_data = {"interface": "serial", "bitrate": 125000} + env_dict = self._dict_to_env(env_data) + with unittest.mock.patch.dict("os.environ", env_dict): + config = can.util.load_config(path=tmp_config, context="one") + expected = self.configuration["one"] + expected.update(env_data) + self.assertEqual(config, expected) + + def test_config_whole_environment(self): + tmp_config = self._gen_configration_file(self.configuration) + env_data = {"interface": "socketcan", "channel": "3", "bitrate": 250000} + env_dict = self._dict_to_env(env_data) + with unittest.mock.patch.dict("os.environ", env_dict): + config = can.util.load_config(path=tmp_config, context="one") + expected = self.configuration["one"] + expected.update(env_data) + self.assertEqual(config, expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py index c71e6ccd6..6b1d5a382 100644 --- a/test/test_load_file_config.py +++ b/test/test_load_file_config.py @@ -11,7 +11,7 @@ class LoadFileConfigTest(unittest.TestCase): configuration = { "default": {"interface": "virtual", "channel": "0"}, - "one": {"interface": "virtual", "channel": "1"}, + "one": {"interface": "kvaser", "channel": "1"}, "two": {"channel": "2"}, "three": {"extra": "extra value"}, } From b639560594d9dbb570efd67d64877b743fdf9aef Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Fri, 7 Oct 2022 16:20:31 -0500 Subject: [PATCH 0923/1235] Modify `file_size` help doc string (#1401) * Modify `file_size` help doc string * Add note to BLFWriter * Revert "Add note to BLFWriter" This reverts commit 18241acc527b82ee81aa0e546410a158b18ad1d1. * Add in a note about the nuanced file size in help statement based on input from @zariiii9003 * Modify help statement to clarify consistency * Minor tweak * Control BLFWriter max container size * Fix formatting * Update can/logger.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Revert "Fix formatting" This reverts commit 3afb140a09c4c0a5394f5a89d7e0d49397c4a37d. * Revert "Control BLFWriter max container size" This reverts commit c39719ccf88ef9c6eae8facffcac063a10f519c5. Co-authored-by: j-c-cook Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/logger.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/can/logger.py b/can/logger.py index 0b73dd785..8cb201987 100644 --- a/can/logger.py +++ b/can/logger.py @@ -193,9 +193,10 @@ def main() -> None: "--file_size", dest="file_size", type=int, - help="Maximum file size in bytes (or for the case of blf, maximum " - "buffer size before compression and flush to file). Rotate log " - "file when size threshold is reached.", + help="Maximum file size in bytes. Rotate log file when size threshold " + "is reached. (The resulting file sizes will be consistent, but are not " + "guaranteed to be exactly what is specified here due to the rollover " + "conditions being logger implementation specific.)", default=None, ) From 451ce48eacd529453c14ec59fe6bc5d142f2e65a Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 9 Oct 2022 23:08:36 +0200 Subject: [PATCH 0924/1235] Fix Sphinx warnings (#1405) * fix most sphinx warnings * build docs in CI * install package * fix extlink caption string deprecation warning * add Seeed Studio to title * update readthedocs configuration * add mocks for windows specifics * mypy should ignore conf.py * upload sphinx artifact * set retention to 5 days * set type annotation location --- .github/workflows/build.yml | 22 +++++++++++ .readthedocs.yml | 31 +++++++++++++++ can/broadcastmanager.py | 2 +- can/bus.py | 31 +++++++++------ can/exceptions.py | 2 +- can/interfaces/ixxat/canlib_vcinpl.py | 6 +-- can/interfaces/ixxat/canlib_vcinpl2.py | 5 +-- can/interfaces/kvaser/canlib.py | 3 +- can/interfaces/kvaser/structures.py | 7 +--- can/interfaces/nican.py | 10 ++--- can/interfaces/pcan/pcan.py | 4 +- can/interfaces/robotell.py | 8 ++-- can/interfaces/serial/serial_can.py | 10 +++-- can/interfaces/slcan.py | 4 +- can/interfaces/socketcan/socketcan.py | 13 ++++--- can/interfaces/systec/ucanbus.py | 8 ++-- can/interfaces/usb2can/usb2canInterface.py | 21 +++++----- .../usb2can/usb2canabstractionlayer.py | 22 +++++++---- can/interfaces/vector/canlib.py | 6 +-- can/interfaces/virtual.py | 4 +- can/io/logger.py | 39 ++++++++++--------- can/io/sqlite.py | 2 +- can/message.py | 2 +- can/typechecking.py | 4 +- can/util.py | 9 +++-- doc/bcm.rst | 4 ++ doc/bus.rst | 11 +++++- doc/conf.py | 29 ++++++++++++-- doc/doc-requirements.txt | 4 +- doc/interfaces.rst | 1 + doc/interfaces/etas.rst | 4 +- doc/interfaces/ixxat.rst | 19 ++++++++- doc/interfaces/kvaser.rst | 2 + doc/interfaces/neovi.rst | 5 ++- doc/interfaces/nican.rst | 1 + doc/interfaces/pcan.rst | 1 + doc/interfaces/seeedstudio.rst | 5 +-- doc/interfaces/serial.rst | 2 + doc/interfaces/socketcan.rst | 6 ++- doc/interfaces/socketcand.rst | 13 ++++--- doc/interfaces/usb2can.rst | 2 + doc/interfaces/vector.rst | 6 +-- doc/interfaces/virtual.rst | 6 ++- doc/internal-api.rst | 2 +- doc/message.rst | 2 + doc/scripts.rst | 2 +- setup.cfg | 1 + 47 files changed, 267 insertions(+), 136 deletions(-) create mode 100644 .readthedocs.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5365620cd..aad9274fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,3 +87,25 @@ jobs: - name: Code Format Check with Black run: | black --check --verbose . + + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e .[canalystii,gs_usb] + pip install -r doc/doc-requirements.txt + - name: Build documentation + run: | + python -m sphinx -an doc build + - uses: actions/upload-artifact@v3 + with: + name: sphinx-out + path: ./build/ + retention-days: 5 diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..74cb9dbdd --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,31 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: doc/conf.py + +# If using Sphinx, optionally build your docs in additional formats such as PDF +formats: + - pdf + - epub + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: doc/doc-requirements.txt + - method: pip + path: . + extra_requirements: + - canalystii + - gs_usb diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index c15186e2c..239d1d7d5 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -39,7 +39,7 @@ class CyclicTask(abc.ABC): def stop(self) -> None: """Cancel this periodic task. - :raises can.CanError: + :raises ~can.exceptions.CanError: If stop is called on an already stopped task. """ diff --git a/can/bus.py b/can/bus.py index b9ccfcfad..c0793c5f7 100644 --- a/can/bus.py +++ b/can/bus.py @@ -66,8 +66,10 @@ def __init__( Any backend dependent configurations are passed in this dictionary :raises ValueError: If parameters are out of range - :raises can.CanInterfaceNotImplementedError: If the driver cannot be accessed - :raises can.CanInitializationError: If the bus cannot be initialized + :raises ~can.exceptions.CanInterfaceNotImplementedError: + If the driver cannot be accessed + :raises ~can.exceptions.CanInitializationError: + If the bus cannot be initialized """ self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) @@ -81,9 +83,11 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: :param timeout: seconds to wait for a message or None to wait indefinitely - :return: ``None`` on timeout or a :class:`Message` object. + :return: + :obj:`None` on timeout or a :class:`~can.Message` object. - :raises can.CanOperationError: If an error occurred while reading + :raises ~can.exceptions.CanOperationError: + If an error occurred while reading """ start = time() time_left = timeout @@ -148,7 +152,8 @@ def _recv_internal( 2. a bool that is True if message filtering has already been done and else False - :raises can.CanOperationError: If an error occurred while reading + :raises ~can.exceptions.CanOperationError: + If an error occurred while reading :raises NotImplementedError: if the bus provides it's own :meth:`~can.BusABC.recv` implementation (legacy implementation) @@ -171,7 +176,8 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: Might not be supported by all interfaces. None blocks indefinitely. - :raises can.CanOperationError: If an error occurred while sending + :raises ~can.exceptions.CanOperationError: + If an error occurred while sending """ raise NotImplementedError("Trying to write to a readonly bus?") @@ -189,8 +195,8 @@ def send_periodic( - the (optional) duration expires - the Bus instance goes out of scope - the Bus instance is shutdown - - :meth:`BusABC.stop_all_periodic_tasks()` is called - - the task's :meth:`CyclicTask.stop()` method is called. + - :meth:`stop_all_periodic_tasks` is called + - the task's :meth:`~can.broadcastmanager.CyclicTask.stop` method is called. :param msgs: Message(s) to transmit @@ -204,7 +210,8 @@ def send_periodic( Disable to instead manage tasks manually. :return: A started task instance. Note the task can be stopped (and depending on - the backend modified) by calling the task's :meth:`stop` method. + the backend modified) by calling the task's + :meth:`~can.broadcastmanager.CyclicTask.stop` method. .. note:: @@ -274,8 +281,8 @@ def _send_periodic_internal( no duration is provided, the task will continue indefinitely. :return: A started task instance. Note the task can be stopped (and - depending on the backend modified) by calling the :meth:`stop` - method. + depending on the backend modified) by calling the + :meth:`~can.broadcastmanager.CyclicTask.stop` method. """ if not hasattr(self, "_lock_send_periodic"): # Create a send lock for this bus, but not for buses which override this method @@ -288,7 +295,7 @@ def _send_periodic_internal( return task def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None: - """Stop sending any messages that were started using **bus.send_periodic**. + """Stop sending any messages that were started using :meth:`send_periodic`. .. note:: The result is undefined if a single task throws an exception while being stopped. diff --git a/can/exceptions.py b/can/exceptions.py index 5a7aa0b7c..dc08be3b8 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -74,7 +74,7 @@ class CanInitializationError(CanError): """Indicates an error the occurred while initializing a :class:`can.BusABC`. If initialization fails due to a driver or platform missing/being unsupported, - a :class:`can.CanInterfaceNotImplementedError` is raised instead. + a :exc:`~can.exceptions.CanInterfaceNotImplementedError` is raised instead. If initialization fails due to a value being out of range, a :class:`ValueError` is raised. diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index cb0447b49..bdb05cda5 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -372,10 +372,8 @@ class IXXATBus(BusABC): .. warning:: This interface does implement efficient filtering of messages, but - the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` - using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` - does not work. - + the filters have to be set in ``__init__`` using the ``can_filters`` parameter. + Using :meth:`~can.BusABC.set_filters` does not work. """ CHANNEL_BITRATES = { diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 37085d74a..802168630 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -411,9 +411,8 @@ class IXXATBus(BusABC): .. warning:: This interface does implement efficient filtering of messages, but - the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` - using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` - does not work. + the filters have to be set in ``__init__`` using the ``can_filters`` parameter. + Using :meth:`~can.BusABC.set_filters` does not work. """ diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index a951e39de..f60a43bc5 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -657,7 +657,7 @@ def shutdown(self): canBusOff(self._write_handle) canClose(self._write_handle) - def get_stats(self): + def get_stats(self) -> structures.BusStatistics: """Retrieves the bus statistics. Use like so: @@ -667,7 +667,6 @@ def get_stats(self): std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0 :returns: bus statistics. - :rtype: can.interfaces.kvaser.structures.BusStatistics """ canRequestBusStatistics(self._write_handle) stats = structures.BusStatistics() diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index c7d363dd4..996f16c37 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -7,11 +7,8 @@ class BusStatistics(ctypes.Structure): - """ - This structure is used with the method :meth:`KvaserBus.get_stats`. - - .. seealso:: :meth:`KvaserBus.get_stats` - + """This structure is used with the method + :meth:`~can.interfaces.kvaser.canlib.KvaserBus.get_stats`. """ _fields_ = [ diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 0beeee429..ea13e28e8 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -179,10 +179,8 @@ class NicanBus(BusABC): .. warning:: This interface does implement efficient filtering of messages, but - the filters have to be set in :meth:`~can.interfaces.nican.NicanBus.__init__` - using the ``can_filters`` parameter. Using :meth:`~can.interfaces.nican.NicanBus.set_filters` - does not work. - + the filters have to be set in ``__init__`` using the ``can_filters`` parameter. + Using :meth:`~can.BusABC.set_filters` does not work. """ def __init__( @@ -208,9 +206,9 @@ def __init__( ``is_error_frame`` set to True and ``arbitration_id`` will identify the error (default True) - :raise can.CanInterfaceNotImplementedError: + :raise ~can.exceptions.CanInterfaceNotImplementedError: If the current operating system is not supported or the driver could not be loaded. - :raise can.interfaces.nican.NicanInitializationError: + :raise ~can.interfaces.nican.NicanInitializationError: If the bus could not be set up. """ if nican is None: diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index e5b877762..c60b9e6c9 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -113,8 +113,8 @@ def __init__( """A PCAN USB interface to CAN. On top of the usual :class:`~can.Bus` methods provided, - the PCAN interface includes the :meth:`~can.interface.pcan.PcanBus.flash` - and :meth:`~can.interface.pcan.PcanBus.status` methods. + the PCAN interface includes the :meth:`flash` + and :meth:`status` methods. :param str channel: The can interface name. An example would be 'PCAN_USBBUS1'. diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 709fad78d..0d3ad1b77 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -5,9 +5,10 @@ import io import time import logging +from typing import Optional from can import BusABC, Message -from ..exceptions import CanInterfaceNotImplementedError +from ..exceptions import CanInterfaceNotImplementedError, CanOperationError logger = logging.getLogger(__name__) @@ -377,12 +378,11 @@ def fileno(self): except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception - def get_serial_number(self, timeout): + def get_serial_number(self, timeout: Optional[int]) -> Optional[str]: """Get serial number of the slcan interface. - :type timeout: int or None + :param timeout: seconds to wait for serial number or None to wait indefinitely - :rtype str or None :return: None on timeout or a str object. """ diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index ec4bb8671..c1507b4fa 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -74,8 +74,10 @@ def __init__( :param rtscts: turn hardware handshake (RTS/CTS) on and off - :raises can.CanInitializationError: If the given parameters are invalid. - :raises can.CanInterfaceNotImplementedError: If the serial module is not installed. + :raises ~can.exceptions.CanInitializationError: + If the given parameters are invalid. + :raises ~can.exceptions.CanInterfaceNotImplementedError: + If the serial module is not installed. """ if not serial: @@ -163,10 +165,10 @@ def _recv_internal( This parameter will be ignored. The timeout value of the channel is used. :returns: - Received message and `False` (because no filtering as taken place). + Received message and :obj:`False` (because no filtering as taken place). .. warning:: - Flags like is_extended_id, is_remote_frame and is_error_frame + Flags like ``is_extended_id``, ``is_remote_frame`` and ``is_error_frame`` will not be set over this function, the flags in the return message are the default values. """ diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 63ea4ca42..212c4c85c 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -323,10 +323,10 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: """Get serial number of the slcan interface. :param timeout: - seconds to wait for serial number or ``None`` to wait indefinitely + seconds to wait for serial number or :obj:`None` to wait indefinitely :return: - ``None`` on timeout or a :class:`~builtin.str` object. + :obj:`None` on timeout or a :class:`str` object. """ cmd = "N" self._write(cmd) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index c7c038520..549998dc8 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -402,7 +402,7 @@ def stop(self) -> None: """Stop a task by sending TX_DELETE message to Linux kernel. This will delete the entry for the transmission of the CAN-message - with the specified :attr:`~task_id` identifier. The message length + with the specified ``task_id`` identifier. The message length for the command TX_DELETE is {[bcm_msg_head]} (only the header). """ log.debug("Stopping periodic task") @@ -444,7 +444,7 @@ def start(self) -> None: message to Linux kernel prior to scheduling. :raises ValueError: - If the task referenced by :attr:`~task_id` is already running. + If the task referenced by ``task_id`` is already running. """ self._tx_setup(self.messages) @@ -617,9 +617,10 @@ def __init__( If setting some socket options fails, an error will be printed but no exception will be thrown. This includes enabling: - - that own messages should be received, - - CAN-FD frames and - - error frames. + + - that own messages should be received, + - CAN-FD frames and + - error frames. :param channel: The can interface name with which to create this bus. @@ -739,7 +740,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: Wait up to this many seconds for the transmit queue to be ready. If not given, the call may fail immediately. - :raises can.CanError: + :raises ~can.exceptions.CanError: if the message could not be written. """ log.debug("We've been asked to write a message to the bus") diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 88224b856..fee110b08 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -88,10 +88,10 @@ def __init__(self, channel, can_filters=None, **kwargs): :raises ValueError: If invalid input parameter were passed. - :raises can.CanInterfaceNotImplementedError: + :raises ~can.exceptions.CanInterfaceNotImplementedError: If the platform is not supported. - :raises can.CanInitializationError: + :raises ~can.exceptions.CanInitializationError: If hardware or CAN interface initialization failed. """ try: @@ -181,7 +181,7 @@ def send(self, msg, timeout=None): :param float timeout: Transmit timeout in seconds (value 0 switches off the "auto delete") - :raises can.CanOperationError: + :raises ~can.exceptions.CanOperationError: If the message could not be sent. """ try: @@ -243,7 +243,7 @@ def flush_tx_buffer(self): """ Flushes the transmit buffer. - :raises can.CanError: + :raises ~can.exceptions.CanError: If flushing of the transmit buffer failed. """ log.info("Flushing transmit buffer") diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index e51d485cd..504b61c7b 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -67,22 +67,22 @@ class Usb2canBus(BusABC): This interface only works on Windows. Please use socketcan on Linux. - :param str channel (optional): + :param channel: The device's serial number. If not provided, Windows Management Instrumentation will be used to identify the first such device. - :param int bitrate (optional): + :param bitrate: Bitrate of channel in bit/s. Values will be limited to a maximum of 1000 Kb/s. Default is 500 Kbs - :param int flags (optional): + :param flags: Flags to directly pass to open function of the usb2can abstraction layer. - :param str dll (optional): + :param dll: Path to the DLL with the CANAL API to load Defaults to 'usb2can.dll' - :param str serial (optional): + :param serial: Alias for `channel` that is provided for legacy reasons. If both `serial` and `channel` are set, `serial` will be used and channel will be ignored. @@ -91,18 +91,19 @@ class Usb2canBus(BusABC): def __init__( self, - channel=None, - dll="usb2can.dll", - flags=0x00000008, + channel: Optional[str] = None, + dll: str = "usb2can.dll", + flags: int = 0x00000008, *_, - bitrate=500000, + bitrate: int = 500000, + serial: Optional[str] = None, **kwargs, ): self.can = Usb2CanAbstractionLayer(dll) # get the serial number of the device - device_id = kwargs.get("serial", channel) + device_id = serial or channel # search for a serial number if the device_id is None or empty if not device_id: diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index 8a3ae34ca..a6708cb42 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -9,6 +9,7 @@ import can from ...exceptions import error_check +from ...typechecking import StringPathLike log = logging.getLogger("can.usb2can") @@ -108,12 +109,13 @@ class Usb2CanAbstractionLayer: Documentation: http://www.8devices.com/media/products/usb2can/downloads/CANAL_API.pdf """ - def __init__(self, dll="usb2can.dll"): + def __init__(self, dll: StringPathLike = "usb2can.dll") -> None: """ - :type dll: str or path-like - :param dll (optional): the path to the usb2can DLL to load + :param dll: + the path to the usb2can DLL to load - :raises can.CanInterfaceNotImplementedError: if the DLL could not be loaded + :raises ~can.exceptions.CanInterfaceNotImplementedError: + if the DLL could not be loaded """ try: self.__m_dllBasic = windll.LoadLibrary(dll) @@ -128,11 +130,15 @@ def open(self, configuration: str, flags: int): """ Opens a CAN connection using `CanalOpen()`. - :param configuration: the configuration: "device_id; baudrate" - :param flags: the flags to be set + :param configuration: + the configuration: "device_id; baudrate" + :param flags: + the flags to be set + :returns: + Valid handle for CANAL API functions on success - :raises can.CanInitializationError: if any error occurred - :returns: Valid handle for CANAL API functions on success + :raises ~can.exceptions.CanInterfaceNotImplementedError: + if any error occurred """ try: # we need to convert this into bytes, since the underlying DLL cannot diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 7737a99d3..a36f67e9e 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -138,11 +138,11 @@ def __init__( :param tseg2_dbr: Bus timing value tseg2 (data) - :raise can.CanInterfaceNotImplementedError: + :raise ~can.exceptions.CanInterfaceNotImplementedError: If the current operating system is not supported or the driver could not be loaded. - :raise can.CanInitializationError: + :raise can.exceptions.CanInitializationError: If the bus could not be set up. - This may or may not be a :class:`can.interfaces.vector.VectorInitializationError`. + This may or may not be a :class:`~can.interfaces.vector.VectorInitializationError`. """ if os.name != "nt" and not kwargs.get("_testing", False): raise CanInterfaceNotImplementedError( diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index ffd5b0241..cc71469b5 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -40,7 +40,7 @@ class VirtualBus(BusABC): an identifier for connected buses. Implements :meth:`can.BusABC._detect_available_configs`; see - :meth:`can.VirtualBus._detect_available_configs` for how it + :meth:`_detect_available_configs` for how it behaves here. .. note:: @@ -84,7 +84,7 @@ def __init__( self.channel.append(self.queue) def _check_if_open(self) -> None: - """Raises :class:`~can.CanOperationError` if the bus is not open. + """Raises :exc:`~can.exceptions.CanOperationError` if the bus is not open. Has to be called in every method that accesses the bus. """ diff --git a/can/io/logger.py b/can/io/logger.py index ec34079b4..df13c6256 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -120,32 +120,31 @@ def on_message_received(self, msg: Message) -> None: class BaseRotatingLogger(Listener, BaseIOHandler, ABC): """ Base class for rotating CAN loggers. This class is not meant to be - instantiated directly. Subclasses must implement the :attr:`should_rollover` - and `do_rollover` methods according to their rotation strategy. + instantiated directly. Subclasses must implement the :meth:`should_rollover` + and :meth:`do_rollover` methods according to their rotation strategy. The rotation behavior can be further customized by the user by setting the :attr:`namer` and :attr:`rotator` attributes after instantiating the subclass. - These attributes as well as the methods `rotation_filename` and `rotate` + These attributes as well as the methods :meth:`rotation_filename` and :meth:`rotate` and the corresponding docstrings are carried over from the python builtin - `BaseRotatingHandler`. + :class:`~logging.handlers.BaseRotatingHandler`. Subclasses must set the `_writer` attribute upon initialization. - :attr namer: - If this attribute is set to a callable, the :meth:`rotation_filename` method - delegates to this callable. The parameters passed to the callable are - those passed to :meth:`rotation_filename`. - :attr rotator: - If this attribute is set to a callable, the :meth:`rotate` method delegates - to this callable. The parameters passed to the callable are those - passed to :meth:`rotate`. - :attr rollover_count: - An integer counter to track the number of rollovers. """ + #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename` + #: method delegates to this callable. The parameters passed to the callable are + #: those passed to :meth:`~BaseRotatingLogger.rotation_filename`. namer: Optional[Callable[[StringPathLike], StringPathLike]] = None + + #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotate` method + #: delegates to this callable. The parameters passed to the callable are those + #: passed to :meth:`~BaseRotatingLogger.rotate`. rotator: Optional[Callable[[StringPathLike, StringPathLike], None]] = None + + #: An integer counter to track the number of rollovers. rollover_count: int = 0 def __init__(self, *args: Any, **kwargs: Any) -> None: @@ -169,7 +168,7 @@ def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: This is provided so that a custom filename can be provided. The default implementation calls the :attr:`namer` attribute of the handler, if it's callable, passing the default name to - it. If the attribute isn't callable (the default is `None`), the name + it. If the attribute isn't callable (the default is :obj:`None`), the name is returned unchanged. :param default_name: @@ -184,8 +183,8 @@ def rotate(self, source: StringPathLike, dest: StringPathLike) -> None: """When rotating, rotate the current log. The default implementation calls the :attr:`rotator` attribute of the - handler, if it's callable, passing the source and dest arguments to - it. If the attribute isn't callable (the default is `None`), the source + handler, if it's callable, passing the `source` and `dest` arguments to + it. If the attribute isn't callable (the default is :obj:`None`), the source is simply renamed to the destination. :param source: @@ -273,8 +272,10 @@ class SizedRotatingLogger(BaseRotatingLogger): by adding a timestamp and the rollover count. A new log file is then created and written to. - This behavior can be customized by setting the :attr:`namer` and - :attr:`rotator` attribute. + This behavior can be customized by setting the + :attr:`~can.io.BaseRotatingLogger.namer` and + :attr:`~can.io.BaseRotatingLogger.rotator` + attribute. Example:: diff --git a/can/io/sqlite.py b/can/io/sqlite.py index b9cbf9f93..0a4de85f2 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -25,7 +25,7 @@ class SqliteReader(MessageReader): This class can be iterated over or used to fetch all messages in the database with :meth:`~SqliteReader.read_all`. - Calling :func:`~builtin.len` on this object might not run in constant time. + Calling :func:`len` on this object might not run in constant time. :attr str table_name: the name of the database table used for storing the messages diff --git a/can/message.py b/can/message.py index 87cb6a199..8e0c4deee 100644 --- a/can/message.py +++ b/can/message.py @@ -29,7 +29,7 @@ class Message: # pylint: disable=too-many-instance-attributes; OK for a datacla :func:`~copy.copy`/:func:`~copy.deepcopy` is supported as well. Messages do not support "dynamic" attributes, meaning any others than the - documented ones, since it uses :attr:`~object.__slots__`. + documented ones, since it uses :obj:`~object.__slots__`. """ __slots__ = ( diff --git a/can/typechecking.py b/can/typechecking.py index ed76b6c85..b3a513a3a 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -8,7 +8,9 @@ import typing_extensions -CanFilter = typing_extensions.TypedDict("CanFilter", {"can_id": int, "can_mask": int}) +CanFilter: typing_extensions = typing_extensions.TypedDict( + "CanFilter", {"can_id": int, "can_mask": int} +) CanFilterExtended = typing_extensions.TypedDict( "CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool} ) diff --git a/can/util.py b/can/util.py index d1ff643de..e64eb13b5 100644 --- a/can/util.py +++ b/can/util.py @@ -258,8 +258,10 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: def set_logging_level(level_name: str) -> None: """Set the logging level for the `"can"` logger. - :param level_name: One of: `'critical'`, `'error'`, `'warning'`, `'info'`, - `'debug'`, `'subdebug'`, or the value `None` (=default). Defaults to `'debug'`. + :param level_name: + One of: `'critical'`, `'error'`, `'warning'`, `'info'`, + `'debug'`, `'subdebug'`, or the value :obj:`None` (=default). + Defaults to `'debug'`. """ can_logger = logging.getLogger("can") @@ -316,7 +318,7 @@ def deprecated_args_alias(**aliases): """Allows to rename/deprecate a function kwarg(s) and optionally have the deprecated kwarg(s) set as alias(es) - Example: + Example:: @deprecated_args_alias(oldArg="new_arg", anotherOldArg="another_new_arg") def library_function(new_arg, another_new_arg): @@ -325,6 +327,7 @@ def library_function(new_arg, another_new_arg): @deprecated_args_alias(oldArg="new_arg", obsoleteOldArg=None) def library_function(new_arg): pass + """ def deco(f): diff --git a/doc/bcm.rst b/doc/bcm.rst index 549b06edd..94cde0e60 100644 --- a/doc/bcm.rst +++ b/doc/bcm.rst @@ -42,3 +42,7 @@ which inherits from :class:`~can.broadcastmanager.CyclicTask`. .. autoclass:: can.RestartableCyclicTaskABC :members: + +.. autoclass:: can.broadcastmanager.ThreadBasedCyclicSendTask + :members: + diff --git a/doc/bus.rst b/doc/bus.rst index bbe52cbd6..9f7077cc1 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -20,7 +20,6 @@ Autoconfig Bus .. autoclass:: can.Bus :members: - :undoc-members: API @@ -28,9 +27,17 @@ API .. autoclass:: can.BusABC :members: - :undoc-members: .. automethod:: __iter__ + .. automethod:: _recv_internal + .. automethod:: _apply_filters + .. automethod:: _detect_available_configs + .. automethod:: _send_periodic_internal + +.. autoclass:: can.bus.BusState + :members: + :undoc-members: + Transmitting '''''''''''' diff --git a/doc/conf.py b/doc/conf.py index 14390d5ad..495416078 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -8,6 +8,8 @@ import sys import os +import ctypes +from unittest.mock import MagicMock # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -15,6 +17,7 @@ sys.path.insert(0, os.path.abspath("..")) import can # pylint: disable=wrong-import-position +from can import ctypesutil # -- General configuration ----------------------------------------------------- @@ -45,11 +48,11 @@ "sphinx.ext.viewcode", "sphinx.ext.graphviz", "sphinxcontrib.programoutput", - "sphinx_autodoc_typehints", + "sphinx_rtd_theme", ] # Now, you can use the alias name as a new role, e.g. :issue:`123`. -extlinks = {"issue": ("https://github.com/hardbyte/python-can/issues/%s/", "issue ")} +extlinks = {"issue": ("https://github.com/hardbyte/python-can/issues/%s/", "issue #%s")} intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} @@ -111,11 +114,31 @@ # Keep cached intersphinx inventories indefinitely intersphinx_cache_limit = -1 +# location of typehints +autodoc_typehints = "description" + +# disable specific warnings +nitpick_ignore = [ + # Ignore warnings for type aliases. Remove once Sphinx supports PEP613 + ("py:class", "can.typechecking.BusConfig"), + ("py:class", "can.typechecking.CanFilter"), + ("py:class", "can.typechecking.CanFilterExtended"), + ("py:class", "can.typechecking.AutoDetectedConfig"), + # intersphinx fails to reference some builtins + ("py:class", "asyncio.events.AbstractEventLoop"), + ("py:class", "_thread.allocate_lock"), +] + +# mock windows specific attributes +autodoc_mock_imports = ["win32com"] +ctypes.windll = MagicMock() +ctypesutil.HRESULT = ctypes.c_long + # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "default" +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt index dead5e2e5..9732ea4ef 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,3 +1,3 @@ -sphinx>=1.8.1 +sphinx>=5.2.3 sphinxcontrib-programoutput -sphinx-autodoc-typehints +sphinx_rtd_theme diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 757cf67b4..dbe4ad426 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -26,6 +26,7 @@ The available interfaces are: interfaces/serial interfaces/slcan interfaces/socketcan + interfaces/socketcand interfaces/systec interfaces/udp_multicast interfaces/usb2can diff --git a/doc/interfaces/etas.rst b/doc/interfaces/etas.rst index cc3cbdea4..2b59a4eee 100644 --- a/doc/interfaces/etas.rst +++ b/doc/interfaces/etas.rst @@ -6,7 +6,7 @@ The ETAS BOA_ (Basic Open API) is used. Install the "ETAS ECU and Bus Interfaces – Distribution Package". Only Windows is supported by this interface. The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 USB modules. -To use these under Linux, please refer to :ref:`socketcan`. +To use these under Linux, please refer to :ref:`SocketCAN`. Bus --- @@ -25,7 +25,7 @@ The simplest configuration file would be:: Channels are the URIs used by the underlying API. -To find available URIs, use :meth:`~can.interface.detect_available_configs`:: +To find available URIs, use :meth:`~can.detect_available_configs`:: configs = can.interface.detect_available_configs(interfaces="etas") for c in configs: diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 28fb6f314..02e707c1c 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -8,7 +8,7 @@ Interface to `IXXAT `__ Virtual CAN Interface V3 SDK. Wor The Linux ECI SDK is currently unsupported, however on Linux some devices are supported with :doc:`socketcan`. -The :meth:`~can.interfaces.ixxat.canlib.IXXATBus.send_periodic` method is supported +The :meth:`~can.BusABC.send_periodic` method is supported natively through the on-board cyclic transmit list. Modifying cyclic messages is not possible. You will need to stop it, and then start a new periodic message. @@ -20,7 +20,22 @@ Bus .. autoclass:: can.interfaces.ixxat.IXXATBus :members: -.. autoclass:: can.interfaces.ixxat.canlib.CyclicSendTask +Implementation based on vcinpl.dll +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.IXXATBus + :members: + +.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.CyclicSendTask + :members: + +Implementation based on vcinpl2.dll +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.IXXATBus + :members: + +.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.CyclicSendTask :members: diff --git a/doc/interfaces/kvaser.rst b/doc/interfaces/kvaser.rst index a4a51ad09..4e0062cfa 100644 --- a/doc/interfaces/kvaser.rst +++ b/doc/interfaces/kvaser.rst @@ -46,3 +46,5 @@ This section contains Kvaser driver specific methods. .. automethod:: can.interfaces.kvaser.canlib.KvaserBus.get_stats +.. autoclass:: can.interfaces.kvaser.structures.BusStatistics + :members: diff --git a/doc/interfaces/neovi.rst b/doc/interfaces/neovi.rst index 05423ac8e..588c5e914 100644 --- a/doc/interfaces/neovi.rst +++ b/doc/interfaces/neovi.rst @@ -42,5 +42,6 @@ Bus --- .. autoclass:: can.interfaces.ics_neovi.NeoViBus - - +.. autoexception:: can.interfaces.ics_neovi.ICSApiError +.. autoexception:: can.interfaces.ics_neovi.ICSInitializationError +.. autoexception:: can.interfaces.ics_neovi.ICSOperationError diff --git a/doc/interfaces/nican.rst b/doc/interfaces/nican.rst index b2214371f..4d2a40717 100644 --- a/doc/interfaces/nican.rst +++ b/doc/interfaces/nican.rst @@ -21,6 +21,7 @@ Bus .. autoclass:: can.interfaces.nican.NicanBus .. autoexception:: can.interfaces.nican.NicanError +.. autoexception:: can.interfaces.nican.NicanInitializationError .. _National Instruments: http://www.ni.com/can/ diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index ff82ba9f4..feb40b195 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -48,3 +48,4 @@ Bus --- .. autoclass:: can.interfaces.pcan.PcanBus + :members: diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst index 5c86fa688..da4d86995 100644 --- a/doc/interfaces/seeedstudio.rst +++ b/doc/interfaces/seeedstudio.rst @@ -1,9 +1,8 @@ .. _seeeddoc: -USB-CAN Analyzer -================ -...by Seeed Studio +Seeed Studio USB-CAN Analyzer +============================= SKU: 114991193 diff --git a/doc/interfaces/serial.rst b/doc/interfaces/serial.rst index 59ffef21a..99ee54df6 100644 --- a/doc/interfaces/serial.rst +++ b/doc/interfaces/serial.rst @@ -21,6 +21,8 @@ Bus .. autoclass:: can.interfaces.serial.serial_can.SerialBus + .. automethod:: _recv_internal + Internals --------- The frames that will be sent and received over the serial interface consist of diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 1e82d8827..f6cc1ba5f 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -1,7 +1,9 @@ +.. _SocketCAN: + SocketCAN ========= -The `SocketCAN`_ documentation can be found in the Linux kernel docs at +The SocketCAN documentation can be found in the `Linux kernel docs`_ at ``networking`` directory. Quoting from the SocketCAN Linux documentation:: > The socketcan package is an implementation of CAN protocols @@ -284,7 +286,7 @@ to ensure usage of SocketCAN Linux API. The most important differences are: .. External references -.. _SocketCAN: https://www.kernel.org/doc/Documentation/networking/can.txt +.. _Linux kernel docs: https://www.kernel.org/doc/Documentation/networking/can.txt .. _Intrepid kernel module: https://github.com/intrepidcs/intrepid-socketcan-kernel-module .. _Intrepid user-space daemon: https://github.com/intrepidcs/icsscand .. _can-utils: https://github.com/linux-can/can-utils diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst index e50f134e1..3c05bcc85 100644 --- a/doc/interfaces/socketcand.rst +++ b/doc/interfaces/socketcand.rst @@ -28,8 +28,8 @@ daemon running on a remote Raspberry Pi: except KeyboardInterrupt: pass -The output may look like this: -:: +The output may look like this:: + Timestamp: 1637791111.209224 ID: 000006fd X Rx DLC: 8 c4 10 e3 2d 96 ff 25 6b Timestamp: 1637791111.233951 ID: 000001ad X Rx DLC: 4 4d 47 c7 64 Timestamp: 1637791111.409415 ID: 000005f7 X Rx DLC: 8 86 de e6 0f 42 55 5d 39 @@ -47,8 +47,9 @@ However, it will also work with any other socketcan device. Install CAN Interface for a MCP2515 based interface on a Raspberry Pi ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Add the following lines to ``/boot/config.txt``. Please take care on the frequency of the crystal on your MCP2515 board: -:: +Add the following lines to ``/boot/config.txt``. +Please take care on the frequency of the crystal on your MCP2515 board:: + dtparam=spi=on dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25,spimaxfrequency=1000000 @@ -106,8 +107,8 @@ Run socketcand ./socketcand -v -i can0 -During start, socketcand will prompt its IP address and port it listens to: -:: +During start, socketcand will prompt its IP address and port it listens to:: + Verbose output activated Using network interface 'eth0' diff --git a/doc/interfaces/usb2can.rst b/doc/interfaces/usb2can.rst index e2e8d7517..56243d41d 100644 --- a/doc/interfaces/usb2can.rst +++ b/doc/interfaces/usb2can.rst @@ -86,3 +86,5 @@ Internals .. autoclass:: can.interfaces.usb2can.Usb2CanAbstractionLayer :members: :undoc-members: + +.. autoexception:: can.interfaces.usb2can.usb2canabstractionlayer.CanalError diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index dcd45f1bf..7b5ede616 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -19,8 +19,6 @@ application named "python-can":: channel = 0, 1 app_name = python-can -If you are using Python 2.7 it is recommended to install pywin32_, otherwise a -slow and CPU intensive polling will be used when waiting for new messages. Bus @@ -29,7 +27,7 @@ Bus .. autoclass:: can.interfaces.vector.VectorBus .. autoexception:: can.interfaces.vector.VectorError - +.. autoexception:: can.interfaces.vector.VectorInitializationError +.. autoexception:: can.interfaces.vector.VectorOperationError .. _Vector: https://vector.com/ -.. _pywin32: https://sourceforge.net/projects/pywin32/ diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst index 9258c9bbd..29976ed47 100644 --- a/doc/interfaces/virtual.rst +++ b/doc/interfaces/virtual.rst @@ -70,8 +70,8 @@ arrive at the recipients exactly once. Both is not guaranteed to hold for the be these guarantees of message delivery and message ordering. The central servers receive and distribute the CAN messages to all other bus participants, unlike in a real physical CAN network. The first intra-process ``virtual`` interface only runs within one Python process, effectively the -Python instance of :class:`VirtualBus` acts as a central server. Notably the ``udp_multicast`` bus -does not require a central server. +Python instance of :class:`~can.interfaces.virtual.VirtualBus` acts as a central server. +Notably the ``udp_multicast`` bus does not require a central server. **Arbitration and throughput** are two interrelated functions/properties of CAN networks which are typically abstracted in virtual interfaces. In all four interfaces, an unlimited amount @@ -133,3 +133,5 @@ Bus Class Documentation .. autoclass:: can.interfaces.virtual.VirtualBus :members: + + .. automethod:: _detect_available_configs diff --git a/doc/internal-api.rst b/doc/internal-api.rst index c43db3394..1367dca50 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -70,7 +70,7 @@ methods: About the IO module ------------------- -Handling of the different file formats is implemented in :mod:`can.io`. +Handling of the different file formats is implemented in ``can.io``. Each file/IO type is within a separate module and ideally implements both a *Reader* and a *Writer*. The reader usually extends :class:`can.io.generic.BaseIOHandler`, while the writer often additionally extends :class:`can.Listener`, diff --git a/doc/message.rst b/doc/message.rst index e5745f6b5..78ccc0b50 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -200,3 +200,5 @@ Message Each of the bytes in the data field (when present) are represented as two-digit hexadecimal numbers. + + .. automethod:: equals diff --git a/doc/scripts.rst b/doc/scripts.rst index ace8c1e39..6b9bdf504 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -55,6 +55,6 @@ The full usage page can be seen below: can.logconvert ----------- +-------------- .. command-output:: python -m can.logconvert -h diff --git a/setup.cfg b/setup.cfg index b402ee645..043fff73a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,6 +12,7 @@ warn_unused_ignores = True exclude = (?x)( venv + |^doc/conf.py$ |^test |^setup.py$ |^can/interfaces/__init__.py From 99fea55f1aea868a64b9d6ba34ec3b713c26170b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 10 Oct 2022 17:19:39 +0200 Subject: [PATCH 0925/1235] explicitly set supported file formats (#1406) --- can/io/logger.py | 34 ++++++++++++++++++++-------------- can/io/printer.py | 13 ++++++++++--- test/test_rotating_loggers.py | 2 ++ 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index df13c6256..09312101b 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -7,14 +7,13 @@ from abc import ABC, abstractmethod from datetime import datetime import gzip -from typing import Any, Optional, Callable, Type, Tuple, cast, Dict +from typing import Any, Optional, Callable, Type, Tuple, cast, Dict, Set from types import TracebackType from typing_extensions import Literal from pkg_resources import iter_entry_points -import can.io from ..message import Message from ..listener import Listener from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter @@ -131,9 +130,10 @@ class BaseRotatingLogger(Listener, BaseIOHandler, ABC): :class:`~logging.handlers.BaseRotatingHandler`. Subclasses must set the `_writer` attribute upon initialization. - """ + _supported_formats: Set[str] = set() + #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename` #: method delegates to this callable. The parameters passed to the callable are #: those passed to :meth:`~BaseRotatingLogger.rotation_filename`. @@ -224,17 +224,21 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: :return: An instance of a writer class. """ - - logger = Logger(filename, *self.writer_args, **self.writer_kwargs) - if isinstance(logger, FileIOMessageWriter): - return logger - elif isinstance(logger, Printer) and logger.file is not None: - return cast(FileIOMessageWriter, logger) - else: - raise Exception( - f"The log format \"{''.join(pathlib.Path(filename).suffixes[-2:])}" - f'" is not supported by {self.__class__.__name__}' - ) + suffix = "".join(pathlib.Path(filename).suffixes[-2:]).lower() + + if suffix in self._supported_formats: + logger = Logger(filename, *self.writer_args, **self.writer_kwargs) + if isinstance(logger, FileIOMessageWriter): + return logger + elif isinstance(logger, Printer) and logger.file is not None: + return cast(FileIOMessageWriter, logger) + + raise Exception( + f'The log format "{suffix}" ' + f"is not supported by {self.__class__.__name__}. " + f"{self.__class__.__name__} supports the following formats: " + f"{', '.join(self._supported_formats)}" + ) def stop(self) -> None: """Stop handling new messages. @@ -306,6 +310,8 @@ class SizedRotatingLogger(BaseRotatingLogger): :meth:`~can.Listener.stop` is called. """ + _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"} + def __init__( self, base_filename: StringPathLike, diff --git a/can/io/printer.py b/can/io/printer.py index 61871e8ad..6a43c63b9 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -4,7 +4,7 @@ import logging -from typing import Optional, TextIO, Union, Any +from typing import Optional, TextIO, Union, Any, cast from ..message import Message from .generic import MessageWriter @@ -40,11 +40,18 @@ def __init__( :param append: If set to `True` messages, are appended to the file, else the file is truncated """ + self.write_to_file = file is not None mode = "a" if append else "w" super().__init__(file, mode=mode) def on_message_received(self, msg: Message) -> None: - if self.file is not None: - self.file.write(str(msg) + "\n") + if self.write_to_file: + cast(TextIO, self.file).write(str(msg) + "\n") else: print(msg) + + def file_size(self) -> int: + """Return an estimate of the current file size in bytes.""" + if self.file is not None: + return self.file.tell() + return 0 diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index d900f4f23..ad4388bf7 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -18,6 +18,8 @@ def _get_instance(path, *args, **kwargs) -> can.io.BaseRotatingLogger: class SubClass(can.io.BaseRotatingLogger): """Subclass that implements abstract methods for testing.""" + _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"} + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._writer = can.Printer(file=path / "__unused.txt") From b587878554fda1c9476788c27ffa1f9881870442 Mon Sep 17 00:00:00 2001 From: Martin Kletzander Date: Mon, 10 Oct 2022 22:41:25 +0200 Subject: [PATCH 0926/1235] setup.cfg: Use license_files instead of license_file (#1408) Without this (and recent enough setuptools) I get this warning: /home/nert/dev/python-can/.tox/.package/lib/python3.10/site-packages/setuptools/config/setupcfg.py:508: SetuptoolsDeprecationWarning: The license_file parameter is deprecated, use license_files instead. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 043fff73a..2f3ee032f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -license_file = LICENSE.txt +license_files = LICENSE.txt [mypy] warn_return_any = True From 0c82d2c442811291c5748306f6767c9a093723d6 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:43:52 +0200 Subject: [PATCH 0927/1235] update github actions (#1409) --- .github/workflows/build.yml | 47 +++++++++++++++++++++---------- .github/workflows/format-code.yml | 6 ++-- setup.py | 1 + 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aad9274fe..93094017f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,17 +13,21 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] experimental: [false] - python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"] - include: - # Only test on a single configuration while there are just pre-releases - - os: ubuntu-latest - experimental: true - python-version: "3.11.0-alpha - 3.11.0" + python-version: [ + "3.7", + "3.8", + "3.9", + "3.10", + "3.11.0-alpha - 3.11.0", + "pypy-3.7", + "pypy-3.8", + "pypy-3.9", + ] fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -34,16 +38,16 @@ jobs: run: | tox -e gh - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: fail_ci_if_error: true static-code-analysis: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies @@ -75,9 +79,9 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies @@ -91,9 +95,9 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies @@ -109,3 +113,16 @@ jobs: name: sphinx-out path: ./build/ retention-days: 5 + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Build wheel and sdist + run: pipx run build + - name: Check build artifacts + run: pipx run twine check --strict dist/* diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index b86789662..68c6f56d8 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -9,9 +9,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies @@ -22,7 +22,7 @@ jobs: run: | black --verbose . - name: Commit Formated Code - uses: EndBug/add-and-commit@v7 + uses: EndBug/add-and-commit@v9 with: message: "Format code with black" # Ref https://git-scm.com/docs/git-add#_examples diff --git a/setup.py b/setup.py index adbd61f91..841425482 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ url="https://github.com/hardbyte/python-can", description="Controller Area Network interface module for Python", long_description=long_description, + long_description_content_type="text/x-rst", classifiers=[ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", From 4b43f337878091bce1a737b0acf98a4fa2bbd968 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:38:44 +0200 Subject: [PATCH 0928/1235] improve vector documentation (#1420) --- can/interfaces/vector/__init__.py | 2 +- can/interfaces/vector/canlib.py | 3 ++ doc/interfaces/vector.rst | 65 +++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index cdeb1d3cb..f543a6109 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,5 +1,5 @@ """ """ -from .canlib import VectorBus, VectorChannelConfig +from .canlib import VectorBus, VectorChannelConfig, get_channel_configs from .exceptions import VectorError, VectorOperationError, VectorInitializationError diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index a36f67e9e..8f5c557d6 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -876,6 +876,8 @@ def set_timer_rate(self, timer_rate_ms: int) -> None: class VectorChannelConfig(NamedTuple): + """NamedTuple which contains the channel properties from Vector XL API.""" + name: str hwType: xldefine.XL_HardwareType hwIndex: int @@ -906,6 +908,7 @@ def _get_xl_driver_config() -> xlclass.XLdriverConfig: def get_channel_configs() -> List[VectorChannelConfig]: + """Read channel properties from Vector XL API.""" try: driver_config = _get_xl_driver_config() except VectorError: diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index 7b5ede616..a4708c28f 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -4,7 +4,7 @@ Vector This interface adds support for CAN controllers by `Vector`_. Only Windows is supported. By default this library uses the channel configuration for CANalyzer. -To use a different application, open Vector Hardware Config program and create +To use a different application, open **Vector Hardware Configuration** program and create a new application and assign the channels you may want to use. Specify the application name as ``app_name='Your app name'`` when constructing the bus or in a config file. @@ -21,13 +21,72 @@ application named "python-can":: -Bus ---- +VectorBus +--------- .. autoclass:: can.interfaces.vector.VectorBus + :show-inheritance: + :member-order: bysource + :members: + set_filters, + recv, + send, + send_periodic, + stop_all_periodic_tasks, + flush_tx_buffer, + reset, + shutdown, + popup_vector_hw_configuration, + get_application_config, + set_application_config + +Exceptions +---------- .. autoexception:: can.interfaces.vector.VectorError + :show-inheritance: .. autoexception:: can.interfaces.vector.VectorInitializationError + :show-inheritance: .. autoexception:: can.interfaces.vector.VectorOperationError + :show-inheritance: + +Miscellaneous +------------- + +.. autofunction:: can.interfaces.vector.get_channel_configs + +.. autoclass:: can.interfaces.vector.VectorChannelConfig + :show-inheritance: + :class-doc-from: class + +.. autoclass:: can.interfaces.vector.xldefine.XL_HardwareType + :show-inheritance: + :member-order: bysource + :members: + :undoc-members: + +.. autoclass:: can.interfaces.vector.xldefine.XL_ChannelCapabilities + :show-inheritance: + :member-order: bysource + :members: + :undoc-members: + +.. autoclass:: can.interfaces.vector.xldefine.XL_BusCapabilities + :show-inheritance: + :member-order: bysource + :members: + :undoc-members: + +.. autoclass:: can.interfaces.vector.xldefine.XL_BusTypes + :show-inheritance: + :member-order: bysource + :members: + :undoc-members: + +.. autoclass:: can.interfaces.vector.xldefine.XL_Status + :show-inheritance: + :member-order: bysource + :members: + :undoc-members: .. _Vector: https://vector.com/ From d3103d83813e4f077d1b0c010e3b063bc5ad5f58 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 30 Oct 2022 23:25:05 +0100 Subject: [PATCH 0929/1235] update docs (#1421) --- can/interface.py | 51 ++++++++++++++++------------ can/interfaces/cantact.py | 2 +- doc/api.rst | 2 +- doc/bus.rst | 21 +++--------- doc/conf.py | 1 + doc/configuration.rst | 45 +++++++++++++++++++------ doc/development.rst | 12 ++++--- doc/doc-requirements.txt | 1 + doc/interfaces.rst | 65 ++++++++++++++++++++++++++++++------ doc/interfaces/cantact.rst | 8 +++++ doc/interfaces/neousys.rst | 13 ++++++++ doc/internal-api.rst | 4 +-- examples/receive_all.py | 10 +++--- examples/send_one.py | 10 +++--- examples/serial_com.py | 4 +-- examples/vcan_filtered.py | 2 +- examples/virtual_can_demo.py | 4 +-- 17 files changed, 170 insertions(+), 85 deletions(-) create mode 100644 doc/interfaces/cantact.rst create mode 100644 doc/interfaces/neousys.rst diff --git a/can/interface.py b/can/interface.py index f1a0087e3..527f84d20 100644 --- a/can/interface.py +++ b/can/interface.py @@ -60,37 +60,44 @@ class Bus(BusABC): # pylint: disable=abstract-method Instantiates a CAN Bus of the given ``interface``, falls back to reading a configuration file from default locations. - """ - @staticmethod - def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg - cls: Any, channel: Optional[Channel] = None, *args: Any, **kwargs: Any - ) -> BusABC: - """ - Takes the same arguments as :class:`can.BusABC.__init__`. - Some might have a special meaning, see below. + :param channel: + Channel identification. Expected type is backend dependent. + Set to ``None`` to let it be resolved automatically from the default + :ref:`configuration`. - :param channel: - Set to ``None`` to let it be resolved automatically from the default - configuration. That might fail, see below. + :param interface: + See :ref:`interface names` for a list of supported interfaces. + Set to ``None`` to let it be resolved automatically from the default + :ref:`configuration`. - Expected type is backend dependent. + :param args: + ``interface`` specific positional arguments. - :param dict kwargs: - Should contain an ``interface`` key with a valid interface name. If not, - it is completed using :meth:`can.util.load_config`. + :param kwargs: + ``interface`` specific keyword arguments. - :raises: can.CanInterfaceNotImplementedError - if the ``interface`` isn't recognized or cannot be loaded + :raises ~can.exceptions.CanInterfaceNotImplementedError: + if the ``interface`` isn't recognized or cannot be loaded - :raises: can.CanInitializationError - if the bus cannot be instantiated + :raises ~can.exceptions.CanInitializationError: + if the bus cannot be instantiated - :raises: ValueError - if the ``channel`` could not be determined - """ + :raises ValueError: + if the ``channel`` could not be determined + """ + @staticmethod + def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg + cls: Any, + channel: Optional[Channel] = None, + interface: Optional[str] = None, + *args: Any, + **kwargs: Any, + ) -> BusABC: # figure out the rest of the configuration; this might raise an error + if interface is not None: + kwargs["interface"] = interface if channel is not None: kwargs["channel"] = channel if "context" in kwargs: diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 056a64a6b..9ad7fbef8 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -59,7 +59,7 @@ def __init__( Bitrate in bits/s :param bool monitor: If true, operate in listen-only monitoring mode - :param BitTiming bit_timing + :param BitTiming bit_timing: Optional BitTiming to use for custom bit timing setting. Overrides bitrate if not None. """ diff --git a/doc/api.rst b/doc/api.rst index 011553b1b..23342f992 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -1,7 +1,7 @@ Library API =========== -The main objects are the :class:`~can.BusABC` and the :class:`~can.Message`. +The main objects are the :class:`~can.Bus` and the :class:`~can.Message`. A form of CAN interface is also required. .. hint:: diff --git a/doc/bus.rst b/doc/bus.rst index 9f7077cc1..4db49ee29 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -15,24 +15,11 @@ and implements the :class:`~can.BusABC` API. A thread safe bus wrapper is also available, see `Thread safe bus`_. -Autoconfig Bus -'''''''''''''' - .. autoclass:: can.Bus + :class-doc-from: class + :show-inheritance: :members: - - -API -''' - -.. autoclass:: can.BusABC - :members: - - .. automethod:: __iter__ - .. automethod:: _recv_internal - .. automethod:: _apply_filters - .. automethod:: _detect_available_configs - .. automethod:: _send_periodic_internal + :inherited-members: .. autoclass:: can.bus.BusState :members: @@ -81,7 +68,7 @@ Example defining two filters, one to pass 11-bit ID ``0x451``, the other to pass See :meth:`~can.BusABC.set_filters` for the implementation. Thread safe bus ---------------- +''''''''''''''' This thread safe version of the :class:`~can.BusABC` class can be used by multiple threads at once. Sending and receiving is locked separately to avoid unnecessary delays. diff --git a/doc/conf.py b/doc/conf.py index 495416078..c1409b8c2 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,6 +48,7 @@ "sphinx.ext.viewcode", "sphinx.ext.graphviz", "sphinxcontrib.programoutput", + "sphinx_inline_tabs", "sphinx_rtd_theme", ] diff --git a/doc/configuration.rst b/doc/configuration.rst index 9bda3030f..d92d6164f 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -1,3 +1,5 @@ +.. _configuration: + Configuration ============= @@ -100,6 +102,7 @@ For example: ``CAN_INTERFACE=socketcan CAN_CONFIG={"receive_own_messages": true, "fd": true}`` +.. _interface names: Interface Names --------------- @@ -109,31 +112,51 @@ Lookup table of interface names: +---------------------+-------------------------------------+ | Name | Documentation | +=====================+=====================================+ -| ``"socketcan"`` | :doc:`interfaces/socketcan` | +| ``"canalystii"`` | :doc:`interfaces/canalystii` | +---------------------+-------------------------------------+ -| ``"kvaser"`` | :doc:`interfaces/kvaser` | +| ``"cantact"`` | :doc:`interfaces/cantact` | +---------------------+-------------------------------------+ -| ``"serial"`` | :doc:`interfaces/serial` | +| ``"etas"`` | :doc:`interfaces/etas` | +---------------------+-------------------------------------+ -| ``"slcan"`` | :doc:`interfaces/slcan` | +| ``"gs_usb"`` | :doc:`interfaces/gs_usb` | ++---------------------+-------------------------------------+ +| ``"iscan"`` | :doc:`interfaces/iscan` | +---------------------+-------------------------------------+ | ``"ixxat"`` | :doc:`interfaces/ixxat` | +---------------------+-------------------------------------+ -| ``"pcan"`` | :doc:`interfaces/pcan` | +| ``"kvaser"`` | :doc:`interfaces/kvaser` | +---------------------+-------------------------------------+ -| ``"usb2can"`` | :doc:`interfaces/usb2can` | +| ``"neousys"`` | :doc:`interfaces/neousys` | ++---------------------+-------------------------------------+ +| ``"neovi"`` | :doc:`interfaces/neovi` | +---------------------+-------------------------------------+ | ``"nican"`` | :doc:`interfaces/nican` | +---------------------+-------------------------------------+ -| ``"iscan"`` | :doc:`interfaces/iscan` | +| ``"nixnet"`` | :doc:`interfaces/nixnet` | +---------------------+-------------------------------------+ -| ``"neovi"`` | :doc:`interfaces/neovi` | +| ``"pcan"`` | :doc:`interfaces/pcan` | +---------------------+-------------------------------------+ -| ``"vector"`` | :doc:`interfaces/vector` | +| ``"robotell"`` | :doc:`interfaces/robotell` | +---------------------+-------------------------------------+ -| ``"virtual"`` | :doc:`interfaces/virtual` | +| ``"seeedstudio"`` | :doc:`interfaces/seeedstudio` | +---------------------+-------------------------------------+ -| ``"canalystii"`` | :doc:`interfaces/canalystii` | +| ``"serial"`` | :doc:`interfaces/serial` | ++---------------------+-------------------------------------+ +| ``"slcan"`` | :doc:`interfaces/slcan` | ++---------------------+-------------------------------------+ +| ``"socketcan"`` | :doc:`interfaces/socketcan` | ++---------------------+-------------------------------------+ +| ``"socketcand"`` | :doc:`interfaces/socketcand` | +---------------------+-------------------------------------+ | ``"systec"`` | :doc:`interfaces/systec` | +---------------------+-------------------------------------+ +| ``"udp_multicast"`` | :doc:`interfaces/udp_multicast` | ++---------------------+-------------------------------------+ +| ``"usb2can"`` | :doc:`interfaces/usb2can` | ++---------------------+-------------------------------------+ +| ``"vector"`` | :doc:`interfaces/vector` | ++---------------------+-------------------------------------+ +| ``"virtual"`` | :doc:`interfaces/virtual` | ++---------------------+-------------------------------------+ + +Additional interface types can be added via the :ref:`plugin interface`. \ No newline at end of file diff --git a/doc/development.rst b/doc/development.rst index 03bf9a374..055401bdc 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -35,8 +35,8 @@ The following assumes that the commands are executed from the root of the reposi The project can be built with:: - pip install wheel - python setup.py sdist bdist_wheel + pipx run build + pipx run twine check dist/* The project can be installed in editable mode with:: @@ -44,8 +44,7 @@ The project can be installed in editable mode with:: The unit tests can be run with:: - pip install tox - tox -e py + pipx run tox -e py The documentation can be built with:: @@ -79,6 +78,11 @@ These steps are a guideline on how to add a new backend to python-can. To get started, have a look at ``back2back_test.py``: Simply add a test case like ``BasicTestSocketCan`` and some basic tests will be executed for the new interface. +.. attention:: + We strongly recommend using the :ref:`plugin interface` to extend python-can. + Publish a python package that contains your :class:`can.BusABC` subclass and use + it within the python-can API. We will mention your package inside this documentation + and add it as an optional dependency. Code Structure -------------- diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt index 9732ea4ef..b1c2da632 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,3 +1,4 @@ sphinx>=5.2.3 sphinxcontrib-programoutput sphinx_rtd_theme +sphinx-inline-tabs diff --git a/doc/interfaces.rst b/doc/interfaces.rst index dbe4ad426..c25ea8bea 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -1,3 +1,5 @@ +.. _can interface modules: + CAN Interface Modules --------------------- @@ -12,11 +14,13 @@ The available interfaces are: :maxdepth: 1 interfaces/canalystii + interfaces/cantact interfaces/etas interfaces/gs_usb interfaces/iscan interfaces/ixxat interfaces/kvaser + interfaces/neousys interfaces/neovi interfaces/nican interfaces/nixnet @@ -33,19 +37,58 @@ The available interfaces are: interfaces/vector interfaces/virtual -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. +The *Interface Names* are listed in :doc:`configuration`. -The format of the entry point is ``interface_name=module:classname`` where -``classname`` is a concrete :class:`can.BusABC` implementation. -:: +.. _plugin interface: - entry_points={ - 'can.interface': [ - "interface_name=module:classname", - ] - }, +Plugin Interface +^^^^^^^^^^^^^^^^ +External packages can register a new interfaces by using the ``can.interface`` entry point +in its project configuration. The format of the entry point depends on your project +configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*). -The *Interface Names* are listed in :doc:`configuration`. +In the following example ``module`` defines the location of your bus class inside your +package e.g. ``my_package.subpackage.bus_module`` and ``classname`` is the name of +your :class:`can.BusABC` subclass. + +.. tab:: pyproject.toml (PEP 621) + + .. code-block:: toml + + # Note the quotes around can.interface in order to escape the dot . + [project.entry-points."can.interface"] + interface_name = "module:classname" + +.. tab:: setup.cfg + + .. code-block:: ini + + [options.entry_points] + can.interface = + interface_name = module:classname + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup + + setup( + # ..., + entry_points = { + 'can.interface': [ + 'interface_name = module:classname' + ] + } + ) + +The ``interface_name`` can be used to +create an instance of the bus in the **python-can** API: + +.. code-block:: python + + import can + + bus = can.Bus(interface="interface_name", channel=0) diff --git a/doc/interfaces/cantact.rst b/doc/interfaces/cantact.rst new file mode 100644 index 000000000..dc9667218 --- /dev/null +++ b/doc/interfaces/cantact.rst @@ -0,0 +1,8 @@ +CANtact CAN Interface +===================== + +Interface for CANtact devices from Linklayer Labs + +.. autoclass:: can.interfaces.cantact.CantactBus + :show-inheritance: + :members: diff --git a/doc/interfaces/neousys.rst b/doc/interfaces/neousys.rst new file mode 100644 index 000000000..97a37868c --- /dev/null +++ b/doc/interfaces/neousys.rst @@ -0,0 +1,13 @@ +Neousys CAN Interface +===================== + +This kind of interface can be found for example on Neousys POC-551VTC +One needs to have correct drivers and DLL (Share object for Linux) from +`Neousys `_. + +Beware this is only tested on Linux kernel higher than v5.3. This should be drop in +with Windows but you have to replace with correct named DLL + +.. autoclass:: can.interfaces.neousys.NeousysBus + :show-inheritance: + :members: diff --git a/doc/internal-api.rst b/doc/internal-api.rst index 1367dca50..3ef599598 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -55,15 +55,15 @@ configuration into account. Bus Internals ~~~~~~~~~~~~~ -Several methods are not documented in the main :class:`can.BusABC` +Several methods are not documented in the main :class:`can.Bus` as they are primarily useful for library developers as opposed to library users. This is the entire ABC bus class with all internal methods: .. autoclass:: can.BusABC + :members: :private-members: :special-members: - :noindex: diff --git a/examples/receive_all.py b/examples/receive_all.py index 7ff532079..7b94d526f 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Shows how the receive messages via polling. +Shows how to receive messages via polling. """ import can @@ -11,11 +11,9 @@ def receive_all(): """Receives all messages and prints them to the console until Ctrl+C is pressed.""" - with can.interface.Bus( - bustype="pcan", channel="PCAN_USBBUS1", bitrate=250000 - ) as bus: - # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) - # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + with can.Bus(interface="pcan", channel="PCAN_USBBUS1", bitrate=250000) as bus: + # bus = can.Bus(interface='ixxat', channel=0, bitrate=250000) + # bus = can.Bus(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000) # set to read-only, only supported on some interfaces bus.state = BusState.PASSIVE diff --git a/examples/send_one.py b/examples/send_one.py index 49a4f1ee1..7e3fb8a4c 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -12,13 +12,13 @@ def send_one(): # this uses the default configuration (for example from the config file) # see https://python-can.readthedocs.io/en/stable/configuration.html - with can.interface.Bus() as bus: + with can.Bus() as bus: # Using specific buses works similar: - # bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000) - # bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) - # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) - # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + # bus = can.Bus(interface='socketcan', channel='vcan0', bitrate=250000) + # bus = can.Bus(interface='pcan', channel='PCAN_USBBUS1', bitrate=250000) + # bus = can.Bus(interface='ixxat', channel=0, bitrate=250000) + # bus = can.Bus(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000) # ... msg = can.Message( diff --git a/examples/serial_com.py b/examples/serial_com.py index c57207a77..76b95c3e7 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -48,8 +48,8 @@ def receive(bus, stop_event): def main(): """Controls the sender and receiver.""" - with can.interface.Bus(interface="serial", channel="/dev/ttyS10") as server: - with can.interface.Bus(interface="serial", channel="/dev/ttyS11") as client: + with can.Bus(interface="serial", channel="/dev/ttyS10") as server: + with can.Bus(interface="serial", channel="/dev/ttyS11") as client: tx_msg = can.Message( arbitration_id=0x01, diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index fa6c71547..a43fbe821 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -11,7 +11,7 @@ def main(): """Send some messages to itself and apply filtering.""" - with can.Bus(bustype="virtual", receive_own_messages=True) as bus: + with can.Bus(interface="virtual", receive_own_messages=True) as bus: can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}] bus.set_filters(can_filters) diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index d0d6a4a6a..af50a87a7 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -14,9 +14,9 @@ def producer(thread_id: int, message_count: int = 16) -> None: """Spam the bus with messages including the data id. :param thread_id: the id of the thread/process - :param message_count: the number of messages that shall be send + :param message_count: the number of messages that shall be sent """ - with can.Bus(bustype="socketcan", channel="vcan0") as bus: # type: ignore + with can.Bus(interface="socketcan", channel="vcan0") as bus: # type: ignore for i in range(message_count): msg = can.Message( arbitration_id=0x0CF02200 + thread_id, From fb892d6f3b24ad57ad37e97ed7031950009ad423 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 31 Oct 2022 01:51:32 +0100 Subject: [PATCH 0930/1235] add busParams and use snake case for VectorChannelConfig (#1422) --- can/interfaces/vector/__init__.py | 9 +- can/interfaces/vector/canlib.py | 140 ++++++++++++++++++++++-------- doc/interfaces/vector.rst | 24 +++++ test/test_vector.py | 56 +++++++++--- 4 files changed, 179 insertions(+), 50 deletions(-) diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index f543a6109..c5eae7140 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,5 +1,12 @@ """ """ -from .canlib import VectorBus, VectorChannelConfig, get_channel_configs +from .canlib import ( + VectorBus, + get_channel_configs, + VectorChannelConfig, + VectorBusParams, + VectorCanParams, + VectorCanFdParams, +) from .exceptions import VectorError, VectorOperationError, VectorInitializationError diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 8f5c557d6..d015f2407 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -295,12 +295,12 @@ def _find_global_channel_idx( if serial is not None: hw_type: Optional[xldefine.XL_HardwareType] = None for channel_config in channel_configs: - if channel_config.serialNumber != serial: + if channel_config.serial_number != serial: continue - hw_type = xldefine.XL_HardwareType(channel_config.hwType) - if channel_config.hwChannel == channel: - return channel_config.channelIndex + hw_type = xldefine.XL_HardwareType(channel_config.hw_type) + if channel_config.hw_channel == channel: + return channel_config.channel_index if hw_type is None: err_msg = f"No interface with serial {serial} found." @@ -331,7 +331,7 @@ def _find_global_channel_idx( # check if channel is a valid global channel index for channel_config in channel_configs: - if channel == channel_config.channelIndex: + if channel == channel_config.channel_index: return channel raise CanInitializationError( @@ -727,26 +727,28 @@ def _detect_available_configs() -> List[AutoDetectedConfig]: LOG.info("Found %d channels", len(channel_configs)) for channel_config in channel_configs: if ( - not channel_config.channelBusCapabilities + not channel_config.channel_bus_capabilities & xldefine.XL_BusCapabilities.XL_BUS_ACTIVE_CAP_CAN ): continue LOG.info( - "Channel index %d: %s", channel_config.channelIndex, channel_config.name + "Channel index %d: %s", + channel_config.channel_index, + channel_config.name, ) configs.append( { # data for use in VectorBus.__init__(): "interface": "vector", - "channel": channel_config.hwChannel, - "serial": channel_config.serialNumber, + "channel": channel_config.hw_channel, + "serial": channel_config.serial_number, # data for use in VectorBus.set_application_config(): - "hw_type": channel_config.hwType, - "hw_index": channel_config.hwIndex, - "hw_channel": channel_config.hwChannel, + "hw_type": channel_config.hw_type, + "hw_index": channel_config.hw_index, + "hw_channel": channel_config.hw_channel, # additional information: "supports_fd": bool( - channel_config.channelCapabilities + channel_config.channel_capabilities & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT ), "vector_channel_config": channel_config, @@ -875,22 +877,53 @@ def set_timer_rate(self, timer_rate_ms: int) -> None: self.xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) +class VectorCanParams(NamedTuple): + bitrate: int + sjw: int + tseg1: int + tseg2: int + sam: int + output_mode: xldefine.XL_OutputMode + can_op_mode: xldefine.XL_CANFD_BusParams_CanOpMode + + +class VectorCanFdParams(NamedTuple): + bitrate: int + data_bitrate: int + sjw_abr: int + tseg1_abr: int + tseg2_abr: int + sam_abr: int + sjw_dbr: int + tseg1_dbr: int + tseg2_dbr: int + output_mode: xldefine.XL_OutputMode + can_op_mode: xldefine.XL_CANFD_BusParams_CanOpMode + + +class VectorBusParams(NamedTuple): + bus_type: xldefine.XL_BusTypes + can: VectorCanParams + canfd: VectorCanFdParams + + class VectorChannelConfig(NamedTuple): """NamedTuple which contains the channel properties from Vector XL API.""" name: str - hwType: xldefine.XL_HardwareType - hwIndex: int - hwChannel: int - channelIndex: int - channelMask: int - channelCapabilities: xldefine.XL_ChannelCapabilities - channelBusCapabilities: xldefine.XL_BusCapabilities - isOnBus: bool - connectedBusType: xldefine.XL_BusTypes - serialNumber: int - articleNumber: int - transceiverName: str + hw_type: xldefine.XL_HardwareType + hw_index: int + hw_channel: int + channel_index: int + channel_mask: int + channel_capabilities: xldefine.XL_ChannelCapabilities + channel_bus_capabilities: xldefine.XL_BusCapabilities + is_on_bus: bool + connected_bus_type: xldefine.XL_BusTypes + bus_params: VectorBusParams + serial_number: int + article_number: int + transceiver_name: str def _get_xl_driver_config() -> xlclass.XLdriverConfig: @@ -907,6 +940,38 @@ def _get_xl_driver_config() -> xlclass.XLdriverConfig: return driver_config +def _read_bus_params_from_c_struct(bus_params: xlclass.XLbusParams) -> VectorBusParams: + return VectorBusParams( + bus_type=xldefine.XL_BusTypes(bus_params.busType), + can=VectorCanParams( + bitrate=bus_params.data.can.bitRate, + sjw=bus_params.data.can.sjw, + tseg1=bus_params.data.can.tseg1, + tseg2=bus_params.data.can.tseg2, + sam=bus_params.data.can.sam, + output_mode=xldefine.XL_OutputMode(bus_params.data.can.outputMode), + can_op_mode=xldefine.XL_CANFD_BusParams_CanOpMode( + bus_params.data.can.canOpMode + ), + ), + canfd=VectorCanFdParams( + bitrate=bus_params.data.canFD.arbitrationBitRate, + data_bitrate=bus_params.data.canFD.dataBitRate, + sjw_abr=bus_params.data.canFD.sjwAbr, + tseg1_abr=bus_params.data.canFD.tseg1Abr, + tseg2_abr=bus_params.data.canFD.tseg2Abr, + sam_abr=bus_params.data.canFD.samAbr, + sjw_dbr=bus_params.data.canFD.sjwDbr, + tseg1_dbr=bus_params.data.canFD.tseg1Dbr, + tseg2_dbr=bus_params.data.canFD.tseg2Dbr, + output_mode=xldefine.XL_OutputMode(bus_params.data.canFD.outputMode), + can_op_mode=xldefine.XL_CANFD_BusParams_CanOpMode( + bus_params.data.canFD.canOpMode + ), + ), + ) + + def get_channel_configs() -> List[VectorChannelConfig]: """Read channel properties from Vector XL API.""" try: @@ -919,22 +984,23 @@ def get_channel_configs() -> List[VectorChannelConfig]: xlcc: xlclass.XLchannelConfig = driver_config.channel[i] vcc = VectorChannelConfig( name=xlcc.name.decode(), - hwType=xldefine.XL_HardwareType(xlcc.hwType), - hwIndex=xlcc.hwIndex, - hwChannel=xlcc.hwChannel, - channelIndex=xlcc.channelIndex, - channelMask=xlcc.channelMask, - channelCapabilities=xldefine.XL_ChannelCapabilities( + hw_type=xldefine.XL_HardwareType(xlcc.hwType), + hw_index=xlcc.hwIndex, + hw_channel=xlcc.hwChannel, + channel_index=xlcc.channelIndex, + channel_mask=xlcc.channelMask, + channel_capabilities=xldefine.XL_ChannelCapabilities( xlcc.channelCapabilities ), - channelBusCapabilities=xldefine.XL_BusCapabilities( + channel_bus_capabilities=xldefine.XL_BusCapabilities( xlcc.channelBusCapabilities ), - isOnBus=bool(xlcc.isOnBus), - connectedBusType=xldefine.XL_BusTypes(xlcc.connectedBusType), - serialNumber=xlcc.serialNumber, - articleNumber=xlcc.articleNumber, - transceiverName=xlcc.transceiverName.decode(), + is_on_bus=bool(xlcc.isOnBus), + bus_params=_read_bus_params_from_c_struct(xlcc.busParams), + connected_bus_type=xldefine.XL_BusTypes(xlcc.connectedBusType), + serial_number=xlcc.serialNumber, + article_number=xlcc.articleNumber, + transceiver_name=xlcc.transceiverName.decode(), ) channel_list.append(vcc) return channel_list diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index a4708c28f..7f9aa1f3f 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -59,6 +59,18 @@ Miscellaneous :show-inheritance: :class-doc-from: class +.. autoclass:: can.interfaces.vector.canlib.VectorBusParams + :show-inheritance: + :class-doc-from: class + +.. autoclass:: can.interfaces.vector.canlib.VectorCanParams + :show-inheritance: + :class-doc-from: class + +.. autoclass:: can.interfaces.vector.canlib.VectorCanFdParams + :show-inheritance: + :class-doc-from: class + .. autoclass:: can.interfaces.vector.xldefine.XL_HardwareType :show-inheritance: :member-order: bysource @@ -83,6 +95,18 @@ Miscellaneous :members: :undoc-members: +.. autoclass:: can.interfaces.vector.xldefine.XL_OutputMode + :show-inheritance: + :member-order: bysource + :members: + :undoc-members: + +.. autoclass:: can.interfaces.vector.xldefine.XL_CANFD_BusParams_CanOpMode + :show-inheritance: + :member-order: bysource + :members: + :undoc-members: + .. autoclass:: can.interfaces.vector.xldefine.XL_Status :show-inheritance: :member-order: bysource diff --git a/test/test_vector.py b/test/test_vector.py index c4ae21f4e..9aea76281 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -22,6 +22,7 @@ VectorOperationError, VectorChannelConfig, ) +from can.interfaces.vector import VectorBusParams, VectorCanParams, VectorCanFdParams from test.config import IS_WINDOWS XLDRIVER_FOUND = canlib.xldriver is not None @@ -604,18 +605,49 @@ def test_winapi_availability() -> None: def test_vector_channel_config_attributes(): assert hasattr(VectorChannelConfig, "name") - assert hasattr(VectorChannelConfig, "hwType") - assert hasattr(VectorChannelConfig, "hwIndex") - assert hasattr(VectorChannelConfig, "hwChannel") - assert hasattr(VectorChannelConfig, "channelIndex") - assert hasattr(VectorChannelConfig, "channelMask") - assert hasattr(VectorChannelConfig, "channelCapabilities") - assert hasattr(VectorChannelConfig, "channelBusCapabilities") - assert hasattr(VectorChannelConfig, "isOnBus") - assert hasattr(VectorChannelConfig, "connectedBusType") - assert hasattr(VectorChannelConfig, "serialNumber") - assert hasattr(VectorChannelConfig, "articleNumber") - assert hasattr(VectorChannelConfig, "transceiverName") + assert hasattr(VectorChannelConfig, "hw_type") + assert hasattr(VectorChannelConfig, "hw_index") + assert hasattr(VectorChannelConfig, "hw_channel") + assert hasattr(VectorChannelConfig, "channel_index") + assert hasattr(VectorChannelConfig, "channel_mask") + assert hasattr(VectorChannelConfig, "channel_capabilities") + assert hasattr(VectorChannelConfig, "channel_bus_capabilities") + assert hasattr(VectorChannelConfig, "is_on_bus") + assert hasattr(VectorChannelConfig, "bus_params") + assert hasattr(VectorChannelConfig, "connected_bus_type") + assert hasattr(VectorChannelConfig, "serial_number") + assert hasattr(VectorChannelConfig, "article_number") + assert hasattr(VectorChannelConfig, "transceiver_name") + + +def test_vector_bus_params_attributes(): + assert hasattr(VectorBusParams, "bus_type") + assert hasattr(VectorBusParams, "can") + assert hasattr(VectorBusParams, "canfd") + + +def test_vector_can_params_attributes(): + assert hasattr(VectorCanParams, "bitrate") + assert hasattr(VectorCanParams, "sjw") + assert hasattr(VectorCanParams, "tseg1") + assert hasattr(VectorCanParams, "tseg2") + assert hasattr(VectorCanParams, "sam") + assert hasattr(VectorCanParams, "output_mode") + assert hasattr(VectorCanParams, "can_op_mode") + + +def test_vector_canfd_params_attributes(): + assert hasattr(VectorCanFdParams, "bitrate") + assert hasattr(VectorCanFdParams, "data_bitrate") + assert hasattr(VectorCanFdParams, "sjw_abr") + assert hasattr(VectorCanFdParams, "tseg1_abr") + assert hasattr(VectorCanFdParams, "tseg2_abr") + assert hasattr(VectorCanFdParams, "sam_abr") + assert hasattr(VectorCanFdParams, "sjw_dbr") + assert hasattr(VectorCanFdParams, "tseg1_dbr") + assert hasattr(VectorCanFdParams, "tseg2_dbr") + assert hasattr(VectorCanFdParams, "output_mode") + assert hasattr(VectorCanFdParams, "can_op_mode") # ***************************************************************************** From 9d2620bea467352def01e3898e5da9e63a2a82e4 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 31 Oct 2022 02:00:30 +0100 Subject: [PATCH 0931/1235] Add python 3.11 to supported versions (#1423) --- .github/workflows/build.yml | 5 ++++- setup.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 93094017f..39eae343f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: "3.8", "3.9", "3.10", - "3.11.0-alpha - 3.11.0", + "3.11", "pypy-3.7", "pypy-3.8", "pypy-3.9", @@ -67,6 +67,9 @@ jobs: - name: mypy 3.10 run: | mypy --python-version 3.10 . + - name: mypy 3.11 + run: | + mypy --python-version 3.11 . - name: pylint run: | pylint --rcfile=.pylintrc \ diff --git a/setup.py b/setup.py index 841425482..a32471844 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Natural Language :: English", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", From c9d6d4e287975154d776f555aa509edbda1a3e66 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 31 Oct 2022 03:40:50 +0100 Subject: [PATCH 0932/1235] fix typo --- doc/interfaces.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/interfaces.rst b/doc/interfaces.rst index c25ea8bea..54d70ca86 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -45,7 +45,7 @@ The *Interface Names* are listed in :doc:`configuration`. Plugin Interface ^^^^^^^^^^^^^^^^ -External packages can register a new interfaces by using the ``can.interface`` entry point +External packages can register new interfaces by using the ``can.interface`` entry point in its project configuration. The format of the entry point depends on your project configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*). From a344a1305d248a9a065aada119ef90ef4951c491 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:08:19 +0100 Subject: [PATCH 0933/1235] Fix #1376 (#1412) * try to fix test * find u32 --- test/test_socketcan.py | 61 +++++++++++------------------------------- 1 file changed, 16 insertions(+), 45 deletions(-) diff --git a/test/test_socketcan.py b/test/test_socketcan.py index a2c4faed3..1c38e1583 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -3,13 +3,10 @@ """ Test functions in `can.interfaces.socketcan.socketcan`. """ +import ctypes +import struct import unittest - -from unittest.mock import Mock from unittest.mock import patch -from unittest.mock import call - -import ctypes from can.interfaces.socketcan.socketcan import ( bcm_header_factory, @@ -240,51 +237,25 @@ def side_effect_ctypes_alignment(value): ] self.assertEqual(expected_fields, BcmMsgHead._fields_) - @unittest.skipIf( - not ( - ctypes.sizeof(ctypes.c_long) == 4 and ctypes.alignment(ctypes.c_long) == 4 - ), - "Should only run on platforms where sizeof(long) == 4 and alignof(long) == 4", - ) - def test_build_bcm_header_sizeof_long_4_alignof_long_4(self): - expected_result = ( - b"\x02\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x01\x04\x00\x00" - b"\x01\x00\x00\x00\x00\x00\x00\x00" - ) + def test_build_bcm_header(self): + def _find_u32_fmt_char() -> str: + for _fmt in ("H", "I", "L", "Q"): + if struct.calcsize(_fmt) == 4: + return _fmt - self.assertEqual( - expected_result, - build_bcm_header( - opcode=CAN_BCM_TX_DELETE, - flags=0, - count=0, - ival1_seconds=0, - ival1_usec=0, - ival2_seconds=0, - ival2_usec=0, - can_id=0x401, - nframes=1, - ), - ) + def _standard_size_little_endian_to_native(data: bytes) -> bytes: + std_le_fmt = " Date: Mon, 31 Oct 2022 14:39:14 +0100 Subject: [PATCH 0934/1235] Fix #1424 (#1425) --- test/test_vector.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_vector.py b/test/test_vector.py index 9aea76281..619277443 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -7,6 +7,7 @@ import ctypes import functools import pickle +import sys import time from unittest.mock import Mock @@ -587,6 +588,9 @@ def test_vector_subtype_error_from_generic() -> None: raise specific +@pytest.mark.skipif( + sys.byteorder != "little", reason="Test relies on little endian data." +) def test_get_channel_configs() -> None: _original_func = canlib._get_xl_driver_config canlib._get_xl_driver_config = _get_predefined_xl_driver_config From cd51ec4c07041c7b3a15b999aa4be5ec3829b05e Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 31 Oct 2022 15:47:32 +0100 Subject: [PATCH 0935/1235] check whether CAN settings were correctly applied (#1426) --- can/interfaces/vector/canlib.py | 66 ++++++++++++++++++++++++++++++++- test/test_vector.py | 7 +++- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index d015f2407..8f699f3f7 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -140,7 +140,7 @@ def __init__( :raise ~can.exceptions.CanInterfaceNotImplementedError: If the current operating system is not supported or the driver could not be loaded. - :raise can.exceptions.CanInitializationError: + :raise ~can.exceptions.CanInitializationError: If the bus could not be set up. This may or may not be a :class:`~can.interfaces.vector.VectorInitializationError`. """ @@ -218,6 +218,7 @@ def __init__( interface_version, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, ) + self.permission_mask = permission_mask.value LOG.debug( "Open Port: PortHandle: %d, PermissionMask: 0x%X", @@ -225,8 +226,9 @@ def __init__( permission_mask.value, ) + # set CAN settings for channel in self.channels: - if permission_mask.value & self.channel_masks[channel]: + if self._has_init_access(channel): if fd: self._set_bitrate_canfd( channel=channel, @@ -242,6 +244,51 @@ def __init__( elif bitrate: self._set_bitrate_can(channel=channel, bitrate=bitrate) + # Check CAN settings + for channel in self.channels: + if kwargs.get("_testing", False): + # avoid check if xldriver is mocked for testing + break + + bus_params = self._read_bus_params(channel) + if fd: + _canfd = bus_params.canfd + if not all( + [ + bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + _canfd.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD, + _canfd.bitrate == bitrate if bitrate else True, + _canfd.sjw_abr == sjw_abr if bitrate else True, + _canfd.tseg1_abr == tseg1_abr if bitrate else True, + _canfd.tseg2_abr == tseg2_abr if bitrate else True, + _canfd.data_bitrate == data_bitrate if data_bitrate else True, + _canfd.sjw_dbr == sjw_dbr if data_bitrate else True, + _canfd.tseg1_dbr == tseg1_dbr if data_bitrate else True, + _canfd.tseg2_dbr == tseg2_dbr if data_bitrate else True, + ] + ): + raise CanInitializationError( + f"The requested CAN FD settings could not be set for channel {channel}. " + f"Another application might have set incompatible settings. " + f"These are the currently active settings: {_canfd._asdict()}" + ) + else: + _can = bus_params.can + if not all( + [ + bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + _can.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20, + _can.bitrate == bitrate if bitrate else True, + ] + ): + raise CanInitializationError( + f"The requested CAN settings could not be set for channel {channel}. " + f"Another application might have set incompatible settings. " + f"These are the currently active settings: {_can._asdict()}" + ) + # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 self.xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) @@ -340,6 +387,21 @@ def _find_global_channel_idx( error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, ) + def _has_init_access(self, channel: int) -> bool: + return bool(self.permission_mask & self.channel_masks[channel]) + + def _read_bus_params(self, channel: int) -> "VectorBusParams": + channel_mask = self.channel_masks[channel] + + vcc_list = get_channel_configs() + for vcc in vcc_list: + if vcc.channel_mask == channel_mask: + return vcc.bus_params + + raise CanInitializationError( + f"Channel configuration for channel {channel} not found." + ) + def _set_bitrate_can( self, channel: int, diff --git a/test/test_vector.py b/test/test_vector.py index 619277443..b0f305821 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -62,6 +62,7 @@ def mock_xldriver() -> None: # backup unmodified values real_xldriver = canlib.xldriver real_waitforsingleobject = canlib.WaitForSingleObject + real_has_events = canlib.HAS_EVENTS # set mock canlib.xldriver = xldriver_mock @@ -72,6 +73,7 @@ def mock_xldriver() -> None: # cleanup canlib.xldriver = real_xldriver canlib.WaitForSingleObject = real_waitforsingleobject + canlib.HAS_EVENTS = real_has_events def test_bus_creation_mocked(mock_xldriver) -> None: @@ -870,13 +872,14 @@ def xlGetChannelIndex( 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), + access_mask: int, + permission_mask: xlclass.XLaccess, rx_queue_size: ctypes.c_uint, xl_interface_version: ctypes.c_uint, bus_type: ctypes.c_uint, ) -> int: port_handle_p.value = 0 + permission_mask.value = access_mask return 0 From 5f485dbeffaa4fd729dc2576625ec73bfda0011b Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Wed, 9 Nov 2022 12:08:04 -0500 Subject: [PATCH 0936/1235] Fixing memory leak in neoVI bus where message_receipts grows (#1427) message_receipts was growing without bound on msg Rx side --- can/interfaces/ics_neovi/neovi_bus.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 5366f8155..bd4fc5445 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -318,8 +318,9 @@ def _process_msg_queue(self, timeout=0.1): if is_tx: if bool(ics_msg.StatusBitField & ics.SPY_STATUS_GLOBAL_ERR): continue - if ics_msg.DescriptionID: - receipt_key = (ics_msg.ArbIDOrHeader, ics_msg.DescriptionID) + + receipt_key = (ics_msg.ArbIDOrHeader, ics_msg.DescriptionID) + if ics_msg.DescriptionID and receipt_key in self.message_receipts: self.message_receipts[receipt_key].set() if not self._receive_own_messages: continue @@ -477,11 +478,10 @@ def send(self, msg, timeout=0): else: raise ValueError("msg.channel must be set when using multiple channels.") - msg_desc_id = next(description_id) - message.DescriptionID = msg_desc_id - receipt_key = (msg.arbitration_id, msg_desc_id) - if timeout != 0: + msg_desc_id = next(description_id) + message.DescriptionID = msg_desc_id + receipt_key = (msg.arbitration_id, msg_desc_id) self.message_receipts[receipt_key].clear() try: @@ -492,5 +492,9 @@ def send(self, msg, timeout=0): # If timeout is set, wait for ACK # This requires a notifier for the bus or # some other thread calling recv periodically - if timeout != 0 and not self.message_receipts[receipt_key].wait(timeout): - raise CanTimeoutError("Transmit timeout") + if timeout != 0: + got_receipt = self.message_receipts[receipt_key].wait(timeout) + # We no longer need this receipt, so no point keeping it in memory + del self.message_receipts[receipt_key] + if not got_receipt: + raise CanTimeoutError("Transmit timeout") From f739bccf7ffd882c42f4409948bf60ccd8a7dc4a Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Sun, 13 Nov 2022 12:50:08 -0600 Subject: [PATCH 0937/1235] Add gzip check to compress method (#1429) * Add gzip check to compress method Addresses @zariiii9003 from https://github.com/hardbyte/python-can/pull/1385#issuecomment-1297372655 * Update can/io/logger.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update logger.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/io/logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/can/io/logger.py b/can/io/logger.py index 09312101b..a254fb146 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -105,6 +105,10 @@ def compress( File will automatically recompress upon close. """ real_suffix = pathlib.Path(filename).suffixes[-2].lower() + if real_suffix in (".blf", ".db"): + raise ValueError( + f"The file type {real_suffix} is currently incompatible with gzip." + ) if kwargs.get("append", False): mode = "ab" if real_suffix == ".blf" else "at" else: From 8c4ed5ffe61617daafbc9418929a37aeadc989d8 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 14 Nov 2022 09:12:06 +0100 Subject: [PATCH 0938/1235] Update CHANGELOG for v4.1.0 release (#1363) --- CHANGELOG.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ can/__init__.py | 2 +- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86612f9b9..1f10e2b78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,77 @@ +Version 4.1.0 +==== + +Features +-------- + +### IO + +* The canutils logger preserves message direction (#1244) + and uses common interface names (e.g. can0) instead of just + channel numbers (#1271). +* The ``can.logger`` script accepts the ``-a, --append`` option + to add new data to an existing log file (#1326, #1327, #1361). + Currently only the blf-, canutils- and csv-formats are supported. +* All CLI ``extra_args`` are passed to the bus, logger + and player initialisation (#1366). + +### Type Annotations +* python-can now includes the ``py.typed`` marker to support type checking + according to PEP 561 (#1344). + +### Interface Improvements +* The gs_usb interface can be selected by device index instead + of USB bus/address. Loopback frames are now correctly marked + with the ``is_rx`` flag (#1270). +* The PCAN interface can be selected by its device ID instead + of just the channel name (#1346). +* The PCAN Bus implementation supports auto bus-off reset (#1345). +* SocketCAN: Make ``find_available_interfaces()`` find slcanX interfaces (#1369). +* Vector: Add xlGetReceiveQueueLevel, xlGenerateSyncPulse and + xlFlushReceiveQueue to xldriver (#1387). +* Vector: Raise a CanInitializationError, if the CAN settings can not + be applied according to the arguments of ``VectorBus.__init__`` (#1426). + +Bug Fixes +--------- + +* Improve robustness of USB2CAN serial number detection (#1129). +* Fix channel2int conversion (#1268, #1269). +* Fix BLF timestamp conversion (#1266, #1273). +* Fix timestamp handling in udp_multicast on macOS (#1275, #1278). +* Fix failure to initiate the Neousys DLL (#1281). +* Fix AttributeError in IscanError (#1292, #1293). +* Add missing vector devices (#1296). +* Fix error for DLC > 8 in ASCReader (#1299, #1301). +* Set default mode for FileIOMessageWriter to wt instead of rt (#1303). +* Fix conversion for port number from config file (#1309). +* Fix fileno error on Windows (#1312, #1313, #1333). +* Remove redundant ``writer.stop()`` call that throws error (#1316, #1317). +* Detect and cast types of CLI ``extra_args`` (#1280, #1328). +* Fix ASC/CANoe incompatibility due to timestamp format (#1315, #1362). +* Fix MessageSync timings (#1372, #1374). +* Fix file name for compressed files in SizedRotatingLogger (#1382, #1683). +* Fix memory leak in neoVI bus where message_receipts grows with no limit (#1427). +* Raise ValueError if gzip is used with incompatible log formats (#1429). + +Miscellaneous +------------- + +* Allow ICSApiError to be pickled and un-pickled (#1341) +* Sort interface names in CLI API to make documentation reproducible (#1342) +* Exclude repository-configuration from git-archive (#1343) +* Improve documentation (#1397, #1401, #1405, #1420, #1421) +* Officially support Python 3.11 (#1423) + +Breaking Changes +---------------- + +* ``windows-curses`` was moved to optional dependencies (#1395). + Use ``pip install python-can[viewer]`` if you are using the ``can.viewer`` + script on Windows. +* The attributes of ``can.interfaces.vector.VectorChannelConfig`` were renamed + from camelCase to snake_case (#1422). + Version 4.0.0 ==== diff --git a/can/__init__.py b/can/__init__.py index 2a0b805ac..18d226867 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.0.0" +__version__ = "4.1.0.dev0" log = logging.getLogger("can") From 4b1acdee8dbf6557f71911275106fff06287bb5b Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Mon, 14 Nov 2022 10:04:53 +0100 Subject: [PATCH 0939/1235] Ixxat bus state and hardware errors detection (#1141) * Added comment to CAN_MSGFLAGS_* and CAN_MSGFLAGS2_* constants * CANMSGINFO.bAddFlags has been renamed to bFlags2 in IXXAT VCI4 * Added a comment that CANMSGINFO.Bytes.bAddFlags is called bFlags2 in VCI v4. * Implementation is now tested against VCI v4 * Dropped manual timeout handling as it is a job done by BusABC.read(). Better handling of CAN error messages: - In case of HW errors (overrun, warning limit exceeded, bus coupling error) raise VCIError - In case of error log IXXAT-specific error codes * Fixed unbound variable usage. Use log.warning instead of deprecated log.warn. * Now wrapping IXXAT VCI v4 * Added CAN_OPMODE_AUTOBAUD controller operating mode. * Mapped symbol canChannelGetStatus, used to implement the 'state' property. Hardware error checking in _recv_internal() handles BUS OFF situation. * Use CANMSGINFO.bAddFlags instead of CANMSGINFO.bFlags2 * Call canControlClose() AFTER canControlReset() or it will always fail * Added CANMSG.__str__ * Removed conflict marker * Renamed parameter 'msg' to 'msgs' in _send_periodic_internal(), consistent with BusABC * Changed plain format() calls to f-strings as per review * Removed binascii module dependency using memoryview Co-authored-by: Giuseppe Corbelli --- can/interfaces/ixxat/__init__.py | 4 +- can/interfaces/ixxat/canlib.py | 19 ++- can/interfaces/ixxat/canlib_vcinpl.py | 205 +++++++++++++++---------- can/interfaces/ixxat/canlib_vcinpl2.py | 12 +- can/interfaces/ixxat/constants.py | 30 ++-- can/interfaces/ixxat/exceptions.py | 2 +- can/interfaces/ixxat/structures.py | 33 ++-- 7 files changed, 183 insertions(+), 122 deletions(-) diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index 347caed50..1419d97a1 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -1,7 +1,7 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems -Copyright (C) 2016 Giuseppe Corbelli +Copyright (C) 2016-2021 Giuseppe Corbelli """ from can.interfaces.ixxat.canlib import IXXATBus diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 4dc0d3e6e..1e4055f89 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -2,6 +2,8 @@ import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 from can import BusABC, Message +from can.bus import BusState + from typing import Optional @@ -11,7 +13,8 @@ class IXXATBus(BusABC): Based on the C implementation of IXXAT, two different dlls are provided by IXXAT, one to work with CAN, the other with CAN-FD. - This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2) class depending on fd user option. + This class only delegates to related implementation (in calib_vcinpl or canlib_vcinpl2) + class depending on fd user option. """ def __init__( @@ -140,8 +143,18 @@ def _recv_internal(self, timeout): def send(self, msg: Message, timeout: Optional[float] = None) -> None: return self.bus.send(msg, timeout) - def _send_periodic_internal(self, msg, period, duration=None): - return self.bus._send_periodic_internal(msg, period, duration) + def _send_periodic_internal(self, msgs, period, duration=None): + return self.bus._send_periodic_internal(msgs, period, duration) def shutdown(self): return self.bus.shutdown() + + @property + def state(self) -> BusState: + """ + Return the current state of the hardware + """ + return self.bus.state + + +# ~class IXXATBus(BusABC): --------------------------------------------------- diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index bdb05cda5..fa88e5f90 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -1,5 +1,5 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems TODO: We could implement this interface such that setting other filters could work when the initial filters were set to zero using the @@ -16,6 +16,7 @@ from typing import Optional, Callable, Tuple from can import BusABC, Message +from can.bus import BusState from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, @@ -38,7 +39,6 @@ log = logging.getLogger("can.ixxat") -from time import perf_counter # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -225,6 +225,13 @@ def __check_status(result, function, args): (HANDLE, ctypes.c_uint32, structures.PCANMSG), __check_status, ) + # HRESULT canChannelGetStatus (HANDLE hCanChn, PCANCHANSTATUS pStatus ); + _canlib.map_symbol( + "canChannelGetStatus", + ctypes.c_long, + (HANDLE, structures.PCANCHANSTATUS), + __check_status, + ) # EXTERN_C HRESULT VCIAPI canControlOpen( IN HANDLE hDevice, IN UINT32 dwCanNo, OUT PHANDLE phCanCtl ); _canlib.map_symbol( @@ -509,11 +516,11 @@ def __init__( == bytes(unique_hardware_id, "ascii") ): break - else: - log.debug( - "Ignoring IXXAT with hardware id '%s'.", - self._device_info.UniqueHardwareId.AsChar.decode("ascii"), - ) + + log.debug( + "Ignoring IXXAT with hardware id '%s'.", + self._device_info.UniqueHardwareId.AsChar.decode("ascii"), + ) _canlib.vciEnumDeviceClose(self._device_handle) try: @@ -522,7 +529,9 @@ def __init__( ctypes.byref(self._device_handle), ) except Exception as exception: - raise CanInitializationError(f"Could not open device: {exception}") + raise CanInitializationError( + f"Could not open device: {exception}" + ) from exception log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) @@ -543,7 +552,7 @@ def __init__( except Exception as exception: raise CanInitializationError( f"Could not open and initialize channel: {exception}" - ) + ) from exception # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize( @@ -637,85 +646,88 @@ def flush_tx_buffer(self): def _recv_internal(self, timeout): """Read a message from IXXAT device.""" - - # TODO: handling CAN error messages? data_received = False - if timeout == 0: + if self._inWaiting() or timeout == 0: # Peek without waiting - try: - _canlib.canChannelPeekMessage( - self._channel_handle, ctypes.byref(self._message) - ) - except (VCITimeout, VCIRxQueueEmptyError): - return None, True - else: - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True + recv_function = functools.partial( + _canlib.canChannelPeekMessage, + self._channel_handle, + ctypes.byref(self._message), + ) else: # Wait if no message available - if timeout is None or timeout < 0: - remaining_ms = constants.INFINITE - t0 = None - else: - timeout_ms = int(timeout * 1000) - remaining_ms = timeout_ms - t0 = perf_counter() - - while True: - try: - _canlib.canChannelReadMessage( - self._channel_handle, remaining_ms, ctypes.byref(self._message) + timeout = ( + constants.INFINITE + if (timeout is None or timeout < 0) + else int(timeout * 1000) + ) + recv_function = functools.partial( + _canlib.canChannelReadMessage, + self._channel_handle, + timeout, + ctypes.byref(self._message), + ) + + try: + recv_function() + except (VCITimeout, VCIRxQueueEmptyError): + # Ignore the 2 errors, overall timeout is handled by BusABC.recv + pass + else: + # See if we got a data or info/error messages + if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: + data_received = True + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: + log.info( + CAN_INFO_MESSAGES.get( + self._message.abData[0], + f"Unknown CAN info message code {self._message.abData[0]}", ) - except (VCITimeout, VCIRxQueueEmptyError): - # Ignore the 2 errors, the timeout is handled manually with the perf_counter() - pass + ) + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: + if self._message.uMsgInfo.Bytes.bFlags & constants.CAN_MSGFLAGS_OVR: + log.warning("CAN error: data overrun") else: - # See if we got a data or info/error messages - if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: - data_received = True - break - elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_INFO: - log.info( - CAN_INFO_MESSAGES.get( - self._message.abData[0], - "Unknown CAN info message code {}".format( - self._message.abData[0] - ), - ) - ) - - elif ( - self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR - ): - log.warning( - CAN_ERROR_MESSAGES.get( - self._message.abData[0], - "Unknown CAN error message code {}".format( - self._message.abData[0] - ), - ) + log.warning( + CAN_ERROR_MESSAGES.get( + self._message.abData[0], + f"Unknown CAN error message code {self._message.abData[0]}", ) - - elif ( - self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_STATUS - ): - log.info(_format_can_status(self._message.abData[0])) - if self._message.abData[0] & constants.CAN_STATUS_BUSOFF: - raise VCIBusOffError() - - elif ( - self._message.uMsgInfo.Bits.type - == constants.CAN_MSGTYPE_TIMEOVR - ): - pass - else: - log.warning("Unexpected message info type") - - if t0 is not None: - remaining_ms = timeout_ms - int((perf_counter() - t0) * 1000) - if remaining_ms < 0: - break + ) + log.warning( + "CAN message flags bAddFlags/bFlags2 0x%02X bflags 0x%02X", + self._message.uMsgInfo.Bytes.bAddFlags, + self._message.uMsgInfo.Bytes.bFlags, + ) + elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: + pass + else: + log.warning( + "Unexpected message info type 0x%X", + self._message.uMsgInfo.Bits.type, + ) + finally: + if not data_received: + # Check hard errors + status = structures.CANLINESTATUS() + _canlib.canControlGetStatus(self._control_handle, ctypes.byref(status)) + error_byte_1 = status.dwStatus & 0x0F + error_byte_2 = status.dwStatus & 0xF0 + if error_byte_1 > constants.CAN_STATUS_TXPEND: + # CAN_STATUS_OVRRUN = 0x02 # data overrun occurred + # CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded + # CAN_STATUS_BUSOFF = 0x08 # bus off status + if error_byte_1 & constants.CAN_STATUS_OVRRUN: + raise VCIError("Data overrun occurred") + elif error_byte_1 & constants.CAN_STATUS_ERRLIM: + raise VCIError("Error warning limit exceeded") + elif error_byte_1 & constants.CAN_STATUS_BUSOFF: + raise VCIError("Bus off status") + elif error_byte_2 > constants.CAN_STATUS_ININIT: + # CAN_STATUS_BUSCERR = 0x20 # bus coupling error + if error_byte_2 & constants.CAN_STATUS_BUSCERR: + raise VCIError("Bus coupling error") if not data_received: # Timed out / can message type is not DATA @@ -764,11 +776,12 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: _canlib.canChannelSendMessage( self._channel_handle, int(timeout * 1000), message ) - else: _canlib.canChannelPostMessage(self._channel_handle, message) + # Want to log outgoing messages? + # log.log(self.RECV_LOGGING_LEVEL, "Sent: %s", message) - def _send_periodic_internal(self, msg, period, duration=None): + def _send_periodic_internal(self, msgs, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" if self._scheduler is None: self._scheduler = HANDLE() @@ -778,7 +791,7 @@ def _send_periodic_internal(self, msg, period, duration=None): self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) return CyclicSendTask( - self._scheduler, msg, period, duration, self._scheduler_resolution + self._scheduler, msgs, period, duration, self._scheduler_resolution ) def shutdown(self): @@ -786,9 +799,35 @@ def shutdown(self): _canlib.canSchedulerClose(self._scheduler) _canlib.canChannelClose(self._channel_handle) _canlib.canControlStart(self._control_handle, constants.FALSE) + _canlib.canControlReset(self._control_handle) _canlib.canControlClose(self._control_handle) _canlib.vciDeviceClose(self._device_handle) + @property + def state(self) -> BusState: + """ + Return the current state of the hardware + """ + status = structures.CANLINESTATUS() + _canlib.canControlGetStatus(self._control_handle, ctypes.byref(status)) + if status.bOpMode == constants.CAN_OPMODE_LISTONLY: + return BusState.PASSIVE + + error_byte_1 = status.dwStatus & 0x0F + # CAN_STATUS_BUSOFF = 0x08 # bus off status + if error_byte_1 & constants.CAN_STATUS_BUSOFF: + return BusState.ERROR + + error_byte_2 = status.dwStatus & 0xF0 + # CAN_STATUS_BUSCERR = 0x20 # bus coupling error + if error_byte_2 & constants.CAN_STATUS_BUSCERR: + raise BusState.ERROR + + return BusState.ACTIVE + + +# ~class IXXATBus(BusABC): --------------------------------------------------- + class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 802168630..108ad2c02 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -825,9 +825,7 @@ def _recv_internal(self, timeout): log.info( CAN_INFO_MESSAGES.get( self._message.abData[0], - "Unknown CAN info message code {}".format( - self._message.abData[0] - ), + f"Unknown CAN info message code {self._message.abData[0]}", ) ) @@ -837,9 +835,7 @@ def _recv_internal(self, timeout): log.warning( CAN_ERROR_MESSAGES.get( self._message.abData[0], - "Unknown CAN error message code {}".format( - self._message.abData[0] - ), + f"Unknown CAN error message code {self._message.abData[0]}", ) ) @@ -933,7 +929,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: else: _canlib.canChannelPostMessage(self._channel_handle, message) - def _send_periodic_internal(self, msg, period, duration=None): + def _send_periodic_internal(self, msgs, period, duration=None): """Send a message using built-in cyclic transmit list functionality.""" if self._scheduler is None: self._scheduler = HANDLE() @@ -945,7 +941,7 @@ def _send_periodic_internal(self, msg, period, duration=None): ) # TODO: confirm _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) return CyclicSendTask( - self._scheduler, msg, period, duration, self._scheduler_resolution + self._scheduler, msgs, period, duration, self._scheduler_resolution ) def shutdown(self): diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index 1dbc22a44..3bc1aa42e 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -1,5 +1,5 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems Copyright (C) 2016 Giuseppe Corbelli """ @@ -106,20 +106,21 @@ VCI_E_WRONG_FLASHFWVERSION = SEV_VCI_ERROR | 0x001A # Controller status -CAN_STATUS_TXPEND = 0x01 -CAN_STATUS_OVRRUN = 0x02 -CAN_STATUS_ERRLIM = 0x04 -CAN_STATUS_BUSOFF = 0x08 -CAN_STATUS_ININIT = 0x10 -CAN_STATUS_BUSCERR = 0x20 +CAN_STATUS_TXPEND = 0x01 # transmission pending +CAN_STATUS_OVRRUN = 0x02 # data overrun occurred +CAN_STATUS_ERRLIM = 0x04 # error warning limit exceeded +CAN_STATUS_BUSOFF = 0x08 # bus off status +CAN_STATUS_ININIT = 0x10 # init mode active +CAN_STATUS_BUSCERR = 0x20 # bus coupling error # Controller operating modes -CAN_OPMODE_UNDEFINED = 0x00 -CAN_OPMODE_STANDARD = 0x01 -CAN_OPMODE_EXTENDED = 0x02 -CAN_OPMODE_ERRFRAME = 0x04 -CAN_OPMODE_LISTONLY = 0x08 -CAN_OPMODE_LOWSPEED = 0x10 +CAN_OPMODE_UNDEFINED = 0x00 # undefined +CAN_OPMODE_STANDARD = 0x01 # reception of 11-bit id messages +CAN_OPMODE_EXTENDED = 0x02 # reception of 29-bit id messages +CAN_OPMODE_ERRFRAME = 0x04 # reception of error frames +CAN_OPMODE_LISTONLY = 0x08 # listen only mode (TX passive) +CAN_OPMODE_LOWSPEED = 0x10 # use low speed bus interface +CAN_OPMODE_AUTOBAUD = 0x20 # automatic bit rate detection # Extended operating modes CAN_EXMODE_DISABLED = 0x00 @@ -167,13 +168,14 @@ CAN_FILTER_EXCL = 0x04 # exclusive filtering (inhibit registered IDs) +# message information flags (used by ) CAN_MSGFLAGS_DLC = 0x0F # [bit 0] data length code CAN_MSGFLAGS_OVR = 0x10 # [bit 4] data overrun flag CAN_MSGFLAGS_SRR = 0x20 # [bit 5] self reception request CAN_MSGFLAGS_RTR = 0x40 # [bit 6] remote transmission request CAN_MSGFLAGS_EXT = 0x80 # [bit 7] frame format (0=11-bit, 1=29-bit) - +# extended message information flags (used by ) CAN_MSGFLAGS2_SSM = 0x01 # [bit 0] single shot mode CAN_MSGFLAGS2_HPM = 0x02 # [bit 1] high priority message CAN_MSGFLAGS2_EDL = 0x04 # [bit 2] extended data length diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index babe08e3b..50b84dfa4 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -1,5 +1,5 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems Copyright (C) 2016 Giuseppe Corbelli Copyright (C) 2019 Marcel Kanter diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index f76a39a38..b784437e0 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -1,5 +1,5 @@ """ -Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems +Ctypes wrapper module for IXXAT Virtual CAN Interface V4 on win32 systems Copyright (C) 2016 Giuseppe Corbelli """ @@ -70,11 +70,13 @@ def __str__(self): class CANLINESTATUS(ctypes.Structure): _fields_ = [ + # current CAN operating mode. Value is a logical combination of + # one or more CAN_OPMODE_xxx constants ("bOpMode", ctypes.c_uint8), - ("bBtReg0", ctypes.c_uint8), - ("bBtReg1", ctypes.c_uint8), - ("bBusLoad", ctypes.c_uint8), - ("dwStatus", ctypes.c_uint32), + ("bBtReg0", ctypes.c_uint8), # current bus timing register 0 value + ("bBtReg1", ctypes.c_uint8), # current bus timing register 1 value + ("bBusLoad", ctypes.c_uint8), # average bus load in percent (0..100) + ("dwStatus", ctypes.c_uint32), # status of the CAN controller (see CAN_STATUS_) ] @@ -83,11 +85,11 @@ class CANLINESTATUS(ctypes.Structure): class CANCHANSTATUS(ctypes.Structure): _fields_ = [ - ("sLineStatus", CANLINESTATUS), - ("fActivated", ctypes.c_uint32), - ("fRxOverrun", ctypes.c_uint32), - ("bRxFifoLoad", ctypes.c_uint8), - ("bTxFifoLoad", ctypes.c_uint8), + ("sLineStatus", CANLINESTATUS), # current CAN line status + ("fActivated", ctypes.c_uint32), # TRUE if the channel is activated + ("fRxOverrun", ctypes.c_uint32), # TRUE if receive FIFO overrun occurred + ("bRxFifoLoad", ctypes.c_uint8), # receive FIFO load in percent (0..100) + ("bTxFifoLoad", ctypes.c_uint8), # transmit FIFO load in percent (0..100) ] @@ -118,7 +120,7 @@ class Bytes(ctypes.Structure): ( "bAddFlags", ctypes.c_uint8, - ), # extended flags (see CAN_MSGFLAGS2_ constants) + ), # extended flags (see CAN_MSGFLAGS2_ constants). AKA bFlags2 in VCI v4 ("bFlags", ctypes.c_uint8), # flags (see CAN_MSGFLAGS_ constants) ("bAccept", ctypes.c_uint8), # accept code (see CAN_ACCEPT_ constants) ] @@ -153,11 +155,20 @@ class Bits(ctypes.Structure): class CANMSG(ctypes.Structure): _fields_ = [ ("dwTime", ctypes.c_uint32), + # CAN ID of the message in Intel format (aligned right) without RTR bit. ("dwMsgId", ctypes.c_uint32), ("uMsgInfo", CANMSGINFO), ("abData", ctypes.c_uint8 * 8), ] + def __str__(self) -> str: + return """ID: 0x{0:04x}{1} DLC: {2:02d} DATA: {3}""".format( + self.dwMsgId, + "[RTR]" if self.uMsgInfo.Bits.rtr else "", + self.uMsgInfo.Bits.dlc, + memoryview(self.abData)[: self.uMsgInfo.Bits.dlc].hex(sep=" "), + ) + PCANMSG = ctypes.POINTER(CANMSG) From b0a44001c68d6e30c3b308f558c78685de8460bd Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 15 Nov 2022 18:47:43 +1300 Subject: [PATCH 0940/1235] Switch from codecov to coveralls (#1430) --- .github/workflows/build.yml | 20 +++++++++++++++++--- README.rst | 6 +++--- doc/development.rst | 2 +- test/test_message_class.py | 5 +++-- tox.ini | 12 +++++------- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39eae343f..2ed1742c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,10 +37,24 @@ jobs: - name: Test with pytest via tox run: | tox -e gh - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + + - name: Coveralls Parallel + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + flag-name: Unittests-${{ matrix.os }}-${{ matrix.python-version }} + parallel: true + path-to-lcov: ./coverage.lcov + + coveralls: + needs: test + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@master with: - fail_ci_if_error: true + github-token: ${{ secrets.github_token }} + parallel-finished: true static-code-analysis: runs-on: ubuntu-latest diff --git a/README.rst b/README.rst index 0e5d79e3f..11404785a 100644 --- a/README.rst +++ b/README.rst @@ -37,9 +37,9 @@ python-can :target: https://app.travis-ci.com/github/hardbyte/python-can :alt: Travis CI Server for develop branch -.. |coverage| image:: https://codecov.io/gh/hardbyte/python-can/branch/develop/graph/badge.svg - :target: https://codecov.io/gh/hardbyte/python-can/branch/develop - :alt: Test coverage reports on Codecov.io +.. |coverage| image:: https://coveralls.io/repos/github/hardbyte/python-can/badge.svg?branch=develop + :target: https://coveralls.io/github/hardbyte/python-can?branch=develop + :alt: Test coverage reports on Coveralls.io .. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/hardbyte/python-can&style=flat :target: https://mergify.io diff --git a/doc/development.rst b/doc/development.rst index 055401bdc..cfb8dbe5d 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -108,7 +108,7 @@ The modules in ``python-can`` are: Creating a new Release ---------------------- -- Release from the ``master`` branch (except for pre-releases). +- Release from the ``main`` branch (except for pre-releases). - Update the library version in ``__init__.py`` using `semantic versioning `__. - Check if any deprecations are pending. - Run all tests and examples against available hardware. diff --git a/test/test_message_class.py b/test/test_message_class.py index 688cda24f..9fae7262e 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -7,7 +7,7 @@ import pickle from datetime import timedelta -from hypothesis import given, settings +from hypothesis import HealthCheck, given, settings import hypothesis.errors import hypothesis.strategies as st @@ -42,12 +42,13 @@ class TestMessageClass(unittest.TestCase): # The first run may take a second on CI runners and will hit the deadline @settings( max_examples=2000, + suppress_health_check=[HealthCheck.too_slow], deadline=None if IS_GITHUB_ACTIONS else timedelta(milliseconds=500), ) @pytest.mark.xfail( IS_WINDOWS and IS_PYPY, raises=hypothesis.errors.Flaky, - reason="Hypothesis generates inconistent timestamp floats on Windows+PyPy-3.7", + reason="Hypothesis generates inconsistent timestamp floats on Windows+PyPy-3.7", ) def test_methods(self, **kwargs): is_valid = not ( diff --git a/tox.ini b/tox.ini index 248a6fd37..0dbd6423e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,9 +5,9 @@ isolated_build = true deps = pytest==7.1.*,>=7.1.2 pytest-timeout==2.0.2 - pytest-cov==3.0.0 - coverage==6.3 - codecov==2.1.12 + coveralls==3.3.1 + pytest-cov==4.0.0 + coverage==6.5.0 hypothesis~=6.35.0 pyserial~=3.5 parameterized~=0.8 @@ -24,6 +24,7 @@ recreate = True passenv = CI GITHUB_* + COVERALLS_* PY_COLORS [testenv:travis] @@ -31,15 +32,12 @@ passenv = CI TRAVIS TRAVIS_* - CODECOV_* TEST_SOCKETCAN -commands_post = - codecov -X gcov [pytest] testpaths = test -addopts = -v --timeout=300 --cov=can --cov-config=tox.ini --cov-report=xml --cov-report=term +addopts = -v --timeout=300 --cov=can --cov-config=tox.ini --cov-report=lcov --cov-report=term [coverage:run] From 2d6e996a7efda5fbf7da51642422ca49b315dfa1 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Tue, 15 Nov 2022 07:20:25 +0100 Subject: [PATCH 0941/1235] Trc file support (#1217) * Added file for implementation of trc file read and write * Added trc file header template * Added link where to lookup the trc file format description * Add trc reader and writer to backends * Add example trace file as conversion from asc log * Add TRCWriter and TRCReader to can package * Basic header extraction in reader * Implement read of file version * Remove useless format string * Implement basic message parsing * Move message parsing to separate function * Implement parse of first message * Handle none rx messages * Handle empty files * Add test method for trc files * Format code with black * Implement stop at end of log file * Implement basic write of header version 2.1 * Add newline after header * Implement write of messages * Enable test for trace file writing * Add test files for different pcan trace file versions * Use binary mode for write to ensure correct line ending on all platforms * Add handler for file write * Move some header lines to specific write function * Move lines to function * Move write of file header to class * Use new function for better code readability * Add enum for file versions * Handle text io streams correctly * send line ending setting to logger * Add check for file version in writer * Add TRCFileVersion as export * Add test for wrong file version * Add format and header for version 1 trace file format * Use correct format according to selected version * Introduce handler method _parse_line * Skip empty lines * Add check for type before eval message * Print info on unsupported types * Add test for new test data files trc format * Implement file version reading * More flexible implementation of line parsing for different versions * Implement Version 1 trace file parsing * Add test case for Version 1.0 trc file * Implement parsing for version 1.1 trace files * Add test for version 1.1 reading * Add test case for version 1.0 trace files * Avoid multi test runs with same input and output by separate generic tests from file version tests * Correct first timestamp * Add type information for file attribute * Add type definitions for init function * Add type for first_timestamp * Add info for return types * Drop type casting for file. Should already be done in init * Add type info * Always use text read write * Update types for initialization * Use text io mode by default --- can/__init__.py | 1 + can/io/__init__.py | 1 + can/io/logger.py | 3 + can/io/player.py | 3 + can/io/trc.py | 368 ++++++++++++++++++++++++ test/data/test_CanMessage.trc | 23 ++ test/data/test_CanMessage_V1_0_BUS1.trc | 28 ++ test/data/test_CanMessage_V1_1.trc | 25 ++ test/data/test_CanMessage_V2_0_BUS1.trc | 28 ++ test/data/test_CanMessage_V2_1.trc | 29 ++ test/logformats_test.py | 128 +++++++++ 11 files changed, 637 insertions(+) create mode 100644 can/io/trc.py create mode 100644 test/data/test_CanMessage.trc create mode 100644 test/data/test_CanMessage_V1_0_BUS1.trc create mode 100644 test/data/test_CanMessage_V1_1.trc create mode 100644 test/data/test_CanMessage_V2_0_BUS1.trc create mode 100644 test/data/test_CanMessage_V2_1.trc diff --git a/can/__init__.py b/can/__init__.py index 18d226867..8af42009a 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -41,6 +41,7 @@ from .io import CanutilsLogReader, CanutilsLogWriter from .io import CSVWriter, CSVReader from .io import SqliteWriter, SqliteReader +from .io import TRCReader, TRCWriter, TRCFileVersion from .broadcastmanager import ( CyclicSendTaskABC, diff --git a/can/io/__init__.py b/can/io/__init__.py index 0d3741b05..6dc9ac1af 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -14,3 +14,4 @@ from .csv import CSVWriter, CSVReader from .sqlite import SqliteReader, SqliteWriter from .printer import Printer +from .trc import TRCReader, TRCWriter, TRCFileVersion diff --git a/can/io/logger.py b/can/io/logger.py index a254fb146..a08cf9869 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -23,6 +23,7 @@ from .csv import CSVWriter from .sqlite import SqliteWriter from .printer import Printer +from .trc import TRCWriter from ..typechecking import StringPathLike, FileLike, AcceptedIOType @@ -36,6 +37,7 @@ class Logger(MessageWriter): # pylint: disable=abstract-method * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` * .log :class:`can.CanutilsLogWriter` + * .trc :class:`can.TRCWriter` * .txt :class:`can.Printer` Any of these formats can be used with gzip compression by appending @@ -58,6 +60,7 @@ class Logger(MessageWriter): # pylint: disable=abstract-method ".csv": CSVWriter, ".db": SqliteWriter, ".log": CanutilsLogWriter, + ".trc": TRCWriter, ".txt": Printer, } diff --git a/can/io/player.py b/can/io/player.py index 8eb4ba24f..82f851502 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -16,6 +16,7 @@ from .canutils import CanutilsLogReader from .csv import CSVReader from .sqlite import SqliteReader +from .trc import TRCReader from ..typechecking import StringPathLike, FileLike, AcceptedIOType from ..message import Message @@ -30,6 +31,7 @@ class LogReader(MessageReader): * .csv * .db * .log + * .trc Gzip compressed files can be used as long as the original files suffix is one of the above (e.g. filename.asc.gz). @@ -56,6 +58,7 @@ class LogReader(MessageReader): ".csv": CSVReader, ".db": SqliteReader, ".log": CanutilsLogReader, + ".trc": TRCReader, } @staticmethod diff --git a/can/io/trc.py b/can/io/trc.py new file mode 100644 index 000000000..25ba0f5c6 --- /dev/null +++ b/can/io/trc.py @@ -0,0 +1,368 @@ +# coding: utf-8 + +""" +Reader and writer for can logging files in peak trc format + +See https://www.peak-system.com/produktcd/Pdf/English/PEAK_CAN_TRC_File_Format.pdf +for file format description + +Version 1.1 will be implemented as it is most commonly used +""" # noqa + +from typing import Generator, Optional, Union, TextIO +from datetime import datetime, timedelta +from enum import Enum +from io import TextIOWrapper +import os +import logging + +from ..message import Message +from ..util import channel2int +from .generic import FileIOMessageWriter, MessageReader +from ..typechecking import StringPathLike + + +logger = logging.getLogger("can.io.trc") + + +class TRCFileVersion(Enum): + UNKNOWN = 0 + V1_0 = 100 + V1_1 = 101 + V1_2 = 102 + V1_3 = 103 + V2_0 = 200 + V2_1 = 201 + + +class TRCReader(MessageReader): + """ + Iterator of CAN messages from a TRC logging file. + """ + + file: TextIO + + def __init__( + self, + file: Union[StringPathLike, TextIO], + ) -> None: + """ + :param file: a path-like object or as file-like object to read from + If this is a file-like object, is has to opened in text + read mode, not binary read mode. + """ + super(TRCReader, self).__init__(file, mode="r") + self.file_version = TRCFileVersion.UNKNOWN + + if not self.file: + raise ValueError("The given file cannot be None") + + def _extract_header(self): + for line in self.file: + line = line.strip() + if line.startswith(";$FILEVERSION"): + logger.debug(f"TRCReader: Found file version '{line}'") + try: + file_version = line.split("=")[1] + if file_version == "1.1": + self.file_version = TRCFileVersion.V1_1 + elif file_version == "2.1": + self.file_version = TRCFileVersion.V2_1 + else: + self.file_version = TRCFileVersion.UNKNOWN + except IndexError: + logger.debug("TRCReader: Failed to parse version") + elif line.startswith(";"): + continue + else: + break + + if self.file_version == TRCFileVersion.UNKNOWN: + logger.info( + "TRCReader: No file version was found, so version 1.0 is assumed" + ) + self._parse_cols = self._parse_msg_V1_0 + elif self.file_version == TRCFileVersion.V1_0: + self._parse_cols = self._parse_msg_V1_0 + elif self.file_version == TRCFileVersion.V1_1: + self._parse_cols = self._parse_cols_V1_1 + elif self.file_version == TRCFileVersion.V2_1: + self._parse_cols = self._parse_cols_V2_1 + else: + raise NotImplementedError("File version not fully implemented for reading") + + return line + + def _parse_msg_V1_0(self, cols): + arbit_id = cols[2] + if arbit_id == "FFFFFFFF": + logger.info("TRCReader: Dropping bus info line") + return None + + msg = Message() + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(arbit_id, 16) + msg.is_extended_id = len(arbit_id) > 4 + msg.channel = 1 + msg.dlc = int(cols[3]) + msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) + return msg + + def _parse_msg_V1_1(self, cols): + arbit_id = cols[3] + + msg = Message() + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(arbit_id, 16) + msg.is_extended_id = len(arbit_id) > 4 + msg.channel = 1 + msg.dlc = int(cols[4]) + msg.data = bytearray([int(cols[i + 5], 16) for i in range(msg.dlc)]) + msg.is_rx = cols[2] == "Rx" + return msg + + def _parse_msg_V2_1(self, cols): + msg = Message() + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(cols[4], 16) + msg.is_extended_id = len(cols[4]) > 4 + msg.channel = int(cols[3]) + msg.dlc = int(cols[7]) + msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) + msg.is_rx = cols[5] == "Rx" + return msg + + def _parse_cols_V1_1(self, cols): + dtype = cols[2] + if dtype == "Tx" or dtype == "Rx": + return self._parse_msg_V1_1(cols) + else: + logger.info(f"TRCReader: Unsupported type '{dtype}'") + return None + + def _parse_cols_V2_1(self, cols): + dtype = cols[2] + if dtype == "DT": + return self._parse_msg_V2_1(cols) + else: + logger.info(f"TRCReader: Unsupported type '{dtype}'") + return None + + def _parse_line(self, line): + logger.debug(f"TRCReader: Parse '{line}'") + try: + cols = line.split() + return self._parse_cols(cols) + except IndexError: + logger.warning(f"TRCReader: Failed to parse message '{line}'") + return None + + def __iter__(self) -> Generator[Message, None, None]: + first_line = self._extract_header() + + if first_line is not None: + msg = self._parse_line(first_line) + if msg is not None: + yield msg + + for line in self.file: + temp = line.strip() + if temp.startswith(";"): + # Comment line + continue + + if len(temp) == 0: + # Empty line + continue + + msg = self._parse_line(temp) + if msg is not None: + yield msg + + self.stop() + + +class TRCWriter(FileIOMessageWriter): + """Logs CAN data to text file (.trc). + + The measurement starts with the timestamp of the first registered message. + If a message has a timestamp smaller than the previous one or None, + it gets assigned the timestamp that was written for the last message. + If the first message does not have a timestamp, it is set to zero. + """ + + file: TextIO + first_timestamp: Optional[float] + + FORMAT_MESSAGE = ( + "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" + ) + FORMAT_MESSAGE_V1_0 = "{msgnr:>6}) {time:7.0f} {id:>8} {dlc:<1} {data}" + + def __init__( + self, + file: Union[StringPathLike, TextIO], + channel: int = 1, + ) -> None: + """ + :param file: a path-like object or as file-like object to write to + If this is a file-like object, is has to opened in text + write mode, not binary write mode. + :param channel: a default channel to use when the message does not + have a channel set + """ + super(TRCWriter, self).__init__(file, mode="w") + self.channel = channel + if type(file) is str: + self.filepath = os.path.abspath(file) + elif type(file) is TextIOWrapper: + self.filepath = "Unknown" + logger.warning("TRCWriter: Text mode io can result in wrong line endings") + logger.debug( + f"TRCWriter: Text mode io line ending setting: {file.newlines}" + ) + else: + self.filepath = "Unknown" + + self.header_written = False + self.msgnr = 0 + self.first_timestamp = None + self.file_version = TRCFileVersion.V2_1 + self._format_message = self._format_message_init + + def _write_line(self, line: str) -> None: + self.file.write(line + "\r\n") + + def _write_lines(self, lines: list) -> None: + for line in lines: + self._write_line(line) + + def _write_header_V1_0(self, start_time: timedelta) -> None: + self._write_line( + ";##########################################################################" + ) + self._write_line(f"; {self.filepath}") + self._write_line(";") + self._write_line("; Generated by python-can TRCWriter") + self._write_line(f"; Start time: {start_time}") + self._write_line("; PCAN-Net: N/A") + self._write_line(";") + self._write_line("; Columns description:") + self._write_line("; ~~~~~~~~~~~~~~~~~~~~~") + self._write_line("; +-current number in actual sample") + self._write_line("; | +time offset of message (ms)") + self._write_line("; | | +ID of message (hex)") + self._write_line("; | | | +data length code") + self._write_line("; | | | | +data bytes (hex) ...") + self._write_line("; | | | | |") + self._write_line(";----+- ---+--- ----+--- + -+ -- -- ...") + + def _write_header_V2_1(self, header_time: timedelta, start_time: datetime) -> None: + milliseconds = int( + (header_time.seconds * 1000) + (header_time.microseconds / 1000) + ) + + self._write_line(";$FILEVERSION=2.1") + self._write_line(f";$STARTTIME={header_time.days}.{milliseconds}") + self._write_line(";$COLUMNS=N,O,T,B,I,d,R,L,D") + self._write_line(";") + self._write_line(f"; {self.filepath}") + self._write_line(";") + self._write_line(f"; Start time: {start_time}") + self._write_line("; Generated by python-can TRCWriter") + self._write_line( + ";-------------------------------------------------------------------------------" + ) + self._write_line("; Bus Name Connection Protocol") + self._write_line("; N/A N/A N/A N/A") + self._write_line( + ";-------------------------------------------------------------------------------" + ) + self._write_lines( + [ + "; Message Time Type ID Rx/Tx", + "; Number Offset | Bus [hex] | Reserved", + "; | [ms] | | | | | Data Length Code", + "; | | | | | | | | Data [hex] ...", + "; | | | | | | | | |", + ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --", + ] + ) + + def _format_message_by_format(self, msg, channel): + if msg.is_extended_id: + arb_id = f"{msg.arbitration_id:07X}" + else: + arb_id = f"{msg.arbitration_id:04X}" + + data = [f"{byte:02X}" for byte in msg.data] + + serialized = self._msg_fmt_string.format( + msgnr=self.msgnr, + time=(msg.timestamp - self.first_timestamp) * 1000, + channel=channel, + id=arb_id, + dir="Rx" if msg.is_rx else "Tx", + dlc=msg.dlc, + data=" ".join(data), + ) + return serialized + + def _format_message_init(self, msg, channel): + if self.file_version == TRCFileVersion.V1_0: + self._format_message = self._format_message_by_format + self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 + elif self.file_version == TRCFileVersion.V2_1: + self._format_message = self._format_message_by_format + self._msg_fmt_string = self.FORMAT_MESSAGE + else: + raise NotImplementedError("File format is not supported") + + return self._format_message(msg, channel) + + def write_header(self, timestamp: float) -> None: + # write start of file header + ref_time = datetime(year=1899, month=12, day=30) + start_time = datetime.now() + timedelta(seconds=timestamp) + header_time = start_time - ref_time + + if self.file_version == TRCFileVersion.V1_0: + self._write_header_V1_0(header_time) + elif self.file_version == TRCFileVersion.V2_1: + self._write_header_V2_1(header_time, start_time) + else: + raise NotImplementedError("File format is not supported") + self.header_written = True + + def log_event(self, message: str, timestamp: float) -> None: + if not self.header_written: + self.write_header(timestamp) + + self._write_line(message) + + def on_message_received(self, msg: Message) -> None: + if self.first_timestamp is None: + self.first_timestamp = msg.timestamp + + if msg.is_error_frame: + logger.warning("TRCWriter: Logging error frames is not implemented") + return + + if msg.is_remote_frame: + logger.warning("TRCWriter: Logging remote frames is not implemented") + return + + channel = channel2int(msg.channel) + if channel is None: + channel = self.channel + else: + # Many interfaces start channel numbering at 0 which is invalid + channel += 1 + + if msg.is_fd: + logger.warning("TRCWriter: Logging CAN FD is not implemented") + return + else: + serialized = self._format_message(msg, channel) + self.msgnr += 1 + self.log_event(serialized, msg.timestamp) diff --git a/test/data/test_CanMessage.trc b/test/data/test_CanMessage.trc new file mode 100644 index 000000000..215997b57 --- /dev/null +++ b/test/data/test_CanMessage.trc @@ -0,0 +1,23 @@ +;$FILEVERSION=2.1 +;$STARTTIME=0 +;$COLUMNS=N,O,T,B,I,d,R,L,D +; +; C:\Users\User\Desktop\python-can\test\data\test_CanMessage.trc +; Start time: 30.09.2017 22:06:13.191.000 +; Generated by PEAK-Converter Version 2.2.4.136 +; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage.asc +;------------------------------------------------------------------------------- +; Bus Name Connection Protocol +; N/A N/A N/A N/A +;------------------------------------------------------------------------------- +; Message Time Type ID Rx/Tx +; Number Offset | Bus [hex] | Reserved +; | [ms] | | | | | Data Length Code +; | | | | | | | | Data [hex] ... +; | | | | | | | | | +;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- +;Begin Triggerblock Sat Sep 30 10:06:13.191 PM 2017 +; 0.000000 Start of measurement + 1 2501.000 DT 2 00C8 Tx - 8 09 08 07 06 05 04 03 02 + 2 17876.708 DT 1 06F9 Rx - 8 05 0C 00 00 00 00 00 00 +;End TriggerBlock diff --git a/test/data/test_CanMessage_V1_0_BUS1.trc b/test/data/test_CanMessage_V1_0_BUS1.trc new file mode 100644 index 000000000..8985db188 --- /dev/null +++ b/test/data/test_CanMessage_V1_0_BUS1.trc @@ -0,0 +1,28 @@ +;########################################################################## +; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_0_BUS1.trc +; +; CAN activities imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +; Start time: 18.12.2021 14:28:07.062 +; PCAN-Net: N/A +; Generated by PEAK-Converter Version 2.2.4.136 +; +; Columns description: +; ~~~~~~~~~~~~~~~~~~~~~ +; +-current number in actual sample +; | +time offset of message (ms) +; | | +ID of message (hex) +; | | | +data length code +; | | | | +data bytes (hex) ... +; | | | | | +;----+- ---+--- ----+--- + -+ -- -- ... + 1) 17535 00000100 8 00 00 00 00 00 00 00 00 + 2) 17540 FFFFFFFF 4 00 00 00 08 -- -- -- -- BUSHEAVY + 3) 17700 00000100 8 00 00 00 00 00 00 00 00 + 4) 17873 00000100 8 00 00 00 00 00 00 00 00 + 5) 19295 0000 8 00 00 00 00 00 00 00 00 + 6) 19500 0000 8 00 00 00 00 00 00 00 00 + 7) 19705 0000 8 00 00 00 00 00 00 00 00 + 8) 20592 00000100 8 00 00 00 00 00 00 00 00 + 9) 20798 00000100 8 00 00 00 00 00 00 00 00 + 10) 20956 00000100 8 00 00 00 00 00 00 00 00 + 11) 21097 00000100 8 00 00 00 00 00 00 00 00 diff --git a/test/data/test_CanMessage_V1_1.trc b/test/data/test_CanMessage_V1_1.trc new file mode 100644 index 000000000..5a02cd59b --- /dev/null +++ b/test/data/test_CanMessage_V1_1.trc @@ -0,0 +1,25 @@ +;$FILEVERSION=1.1 +;$STARTTIME=44548.6028595139 +; +; Start time: 18.12.2021 14:28:07.062.0 +; Generated by PCAN-View v5.0.0.814 +; +; Message Number +; | Time Offset (ms) +; | | Type +; | | | ID (hex) +; | | | | Data Length +; | | | | | Data Bytes (hex) ... +; | | | | | | +;---+-- ----+---- --+-- ----+--- + -+ -- -- -- -- -- -- -- + 1) 17535.4 Tx 00000100 8 00 00 00 00 00 00 00 00 + 2) 17540.3 Warng FFFFFFFF 4 00 00 00 08 BUSHEAVY + 3) 17700.3 Tx 00000100 8 00 00 00 00 00 00 00 00 + 4) 17873.8 Tx 00000100 8 00 00 00 00 00 00 00 00 + 5) 19295.4 Tx 0000 8 00 00 00 00 00 00 00 00 + 6) 19500.6 Tx 0000 8 00 00 00 00 00 00 00 00 + 7) 19705.2 Tx 0000 8 00 00 00 00 00 00 00 00 + 8) 20592.7 Tx 00000100 8 00 00 00 00 00 00 00 00 + 9) 20798.6 Tx 00000100 8 00 00 00 00 00 00 00 00 + 10) 20956.0 Tx 00000100 8 00 00 00 00 00 00 00 00 + 11) 21097.1 Tx 00000100 8 00 00 00 00 00 00 00 00 diff --git a/test/data/test_CanMessage_V2_0_BUS1.trc b/test/data/test_CanMessage_V2_0_BUS1.trc new file mode 100644 index 000000000..cf2384df0 --- /dev/null +++ b/test/data/test_CanMessage_V2_0_BUS1.trc @@ -0,0 +1,28 @@ +;$FILEVERSION=2.0 +;$STARTTIME=44548.6028595139 +;$COLUMNS=N,O,T,I,d,l,D +; +; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V2_0_BUS1.trc +; Start time: 18.12.2021 14:28:07.062.001 +; Generated by PEAK-Converter Version 2.2.4.136 +; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +;------------------------------------------------------------------------------- +; Connection Bit rate +; N/A N/A +;------------------------------------------------------------------------------- +; Message Time Type ID Rx/Tx +; Number Offset | [hex] | Data Length +; | [ms] | | | | Data [hex] ... +; | | | | | | | +;---+-- ------+------ +- --+----- +- +- +- -- -- -- -- -- -- -- + 1 17535.400 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 2 17540.300 ST Rx 00 00 00 08 + 3 17700.300 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 4 17873.800 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 5 19295.400 DT 0000 Tx 8 00 00 00 00 00 00 00 00 + 6 19500.600 DT 0000 Tx 8 00 00 00 00 00 00 00 00 + 7 19705.200 DT 0000 Tx 8 00 00 00 00 00 00 00 00 + 8 20592.700 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 9 20798.600 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 10 20956.000 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 11 21097.100 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 diff --git a/test/data/test_CanMessage_V2_1.trc b/test/data/test_CanMessage_V2_1.trc new file mode 100644 index 000000000..55ceefaf1 --- /dev/null +++ b/test/data/test_CanMessage_V2_1.trc @@ -0,0 +1,29 @@ +;$FILEVERSION=2.1 +;$STARTTIME=44548.6028595139 +;$COLUMNS=N,O,T,B,I,d,R,L,D +; +; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V2_1.trc +; Start time: 18.12.2021 14:28:07.062.001 +; Generated by PEAK-Converter Version 2.2.4.136 +; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +;------------------------------------------------------------------------------- +; Bus Name Connection Protocol +; N/A N/A N/A N/A +;------------------------------------------------------------------------------- +; Message Time Type ID Rx/Tx +; Number Offset | Bus [hex] | Reserved +; | [ms] | | | | | Data Length Code +; | | | | | | | | Data [hex] ... +; | | | | | | | | | +;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- + 1 17535.400 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 2 17540.300 ST 1 - Rx - 4 00 00 00 08 + 3 17700.300 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 4 17873.800 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 5 19295.400 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00 + 6 19500.600 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00 + 7 19705.200 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00 + 8 20592.700 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 9 20798.600 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 10 20956.000 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 11 21097.100 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 diff --git a/test/logformats_test.py b/test/logformats_test.py index 6a0eafac1..435b651b6 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -13,6 +13,7 @@ """ import logging import unittest +from parameterized import parameterized import tempfile import os from abc import abstractmethod, ABCMeta @@ -777,8 +778,135 @@ def test_not_crashes_with_file(self): printer(message) +class TestTrcFileFormatBase(ReaderWriterTest): + """ + Base class for Tests with can.TRCWriter and can.TRCReader + + .. note:: + This class is prevented from being executed as a test + case itself by a *del* statement in at the end of the file. + """ + + def _setup_instance(self): + super()._setup_instance_helper( + can.TRCWriter, + can.TRCReader, + check_remote_frames=False, + check_error_frames=False, + check_fd=False, + check_comments=False, + preserves_channel=False, + allowed_timestamp_delta=0.001, + adds_default_channel=0, + ) + + def _read_log_file(self, filename, **kwargs): + logfile = os.path.join(os.path.dirname(__file__), "data", filename) + with can.TRCReader(logfile, **kwargs) as reader: + return list(reader) + + +class TestTrcFileFormatGen(TestTrcFileFormatBase): + """Generic tests for can.TRCWriter and can.TRCReader with different file versions""" + + def test_can_message(self): + expected_messages = [ + can.Message( + timestamp=2.5010, + arbitration_id=0xC8, + is_extended_id=False, + is_rx=False, + channel=1, + dlc=8, + data=[9, 8, 7, 6, 5, 4, 3, 2], + ), + can.Message( + timestamp=17.876708, + arbitration_id=0x6F9, + is_extended_id=False, + channel=0, + dlc=0x8, + data=[5, 0xC, 0, 0, 0, 0, 0, 0], + ), + ] + actual = self._read_log_file("test_CanMessage.trc") + self.assertMessagesEqual(actual, expected_messages) + + @parameterized.expand( + [ + ("V1_0", "test_CanMessage_V1_0_BUS1.trc", False), + ("V1_1", "test_CanMessage_V1_1.trc", True), + ("V2_1", "test_CanMessage_V2_1.trc", True), + ] + ) + def test_can_message_versions(self, name, filename, is_rx_support): + with self.subTest(name): + + def msg_std(timestamp): + msg = can.Message( + timestamp=timestamp, + arbitration_id=0x000, + is_extended_id=False, + channel=1, + dlc=8, + data=[0, 0, 0, 0, 0, 0, 0, 0], + ) + if is_rx_support: + msg.is_rx = False + return msg + + def msg_ext(timestamp): + msg = can.Message( + timestamp=timestamp, + arbitration_id=0x100, + is_extended_id=True, + channel=1, + dlc=8, + data=[0, 0, 0, 0, 0, 0, 0, 0], + ) + if is_rx_support: + msg.is_rx = False + return msg + + expected_messages = [ + msg_ext(17.5354), + msg_ext(17.7003), + msg_ext(17.8738), + msg_std(19.2954), + msg_std(19.5006), + msg_std(19.7052), + msg_ext(20.5927), + msg_ext(20.7986), + msg_ext(20.9560), + msg_ext(21.0971), + ] + actual = self._read_log_file(filename) + self.assertMessagesEqual(actual, expected_messages) + + def test_not_supported_version(self): + with self.assertRaises(NotImplementedError): + writer = can.TRCWriter("test.trc") + writer.file_version = can.TRCFileVersion.UNKNOWN + writer.on_message_received(can.Message()) + + +class TestTrcFileFormatV1_0(TestTrcFileFormatBase): + """Tests can.TRCWriter and can.TRCReader with file version 1.0""" + + @staticmethod + def Writer(filename): + writer = can.TRCWriter(filename) + writer.file_version = can.TRCFileVersion.V1_0 + return writer + + def _setup_instance(self): + super()._setup_instance() + self.writer_constructor = TestTrcFileFormatV1_0.Writer + + # this excludes the base class from being executed as a test case itself del ReaderWriterTest +del TestTrcFileFormatBase if __name__ == "__main__": From 42549c810f3b85ecbc5c01e0639e8523d30b2370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Szl=C4=99g?= Date: Tue, 15 Nov 2022 11:56:52 +0100 Subject: [PATCH 0942/1235] Add with statement to example in README.md Not using with expression in code might cause lack of cleanup and hard to trace errors, also because Bus doesn't override __del__. So it's a good idea to provide a 100% percent correct example in README for people who don't go into examples folder and read into the code. --- README.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 11404785a..cd2731696 100644 --- a/README.rst +++ b/README.rst @@ -91,21 +91,21 @@ Example usage # create a bus instance # many other interfaces are supported as well (see documentation) - bus = can.Bus(interface='socketcan', + with can.Bus(interface='socketcan', channel='vcan0', - receive_own_messages=True) + receive_own_messages=True) as bus: - # send a message - message = can.Message(arbitration_id=123, is_extended_id=True, - data=[0x11, 0x22, 0x33]) - bus.send(message, timeout=0.2) + # send a message + message = can.Message(arbitration_id=123, is_extended_id=True, + data=[0x11, 0x22, 0x33]) + bus.send(message, timeout=0.2) - # iterate over received messages - for msg in bus: - print(f"{msg.arbitration_id:X}: {msg.data}") + # iterate over received messages + for msg in bus: + print(f"{msg.arbitration_id:X}: {msg.data}") - # or use an asynchronous notifier - notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()]) + # or use an asynchronous notifier + notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()]) You can find more information in the documentation, online at `python-can.readthedocs.org `__. From 2e2f157eb02c0505c4e38ca8cbbc54433c65351c Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 16 Nov 2022 21:43:10 +1300 Subject: [PATCH 0943/1235] Documentation update for 4.1.0 (#1434) * Quick pass through all interface docs * Move plugin and virtual interface docs up * Minor doc spring cleaning * Update readme to include basic installation * Remove (mostly unused) mailing list from the readme * Include TRC section in listener docs * Fix broken links to virtual interfaces * Add to changelog * Refactor api docs * Fix bug in bus example * Doc updates after review --- CHANGELOG.md | 1 + README.rst | 9 ++-- can/interfaces/cantact.py | 2 +- can/interfaces/udp_multicast/bus.py | 2 +- can/interfaces/virtual.py | 2 +- doc/api.rst | 28 +--------- doc/bus.rst | 65 +++++++++++++++-------- doc/errors.rst | 8 +++ doc/history.rst | 4 +- doc/index.rst | 16 +++--- doc/installation.rst | 21 +++++--- doc/interfaces.rst | 67 +++--------------------- doc/interfaces/canalystii.rst | 2 +- doc/interfaces/etas.rst | 29 +++++++---- doc/interfaces/gs_usb.rst | 37 +++++++++---- doc/interfaces/ixxat.rst | 61 +++++++++++----------- doc/interfaces/kvaser.rst | 4 +- doc/interfaces/neovi.rst | 12 ++--- doc/interfaces/nican.rst | 6 +-- doc/interfaces/nixnet.rst | 8 +-- doc/interfaces/pcan.rst | 24 +++++---- doc/interfaces/robotell.rst | 14 +---- doc/interfaces/seeedstudio.rst | 17 ++---- doc/interfaces/socketcan.rst | 26 ++++----- doc/interfaces/socketcand.rst | 4 +- doc/interfaces/systec.rst | 14 ++--- doc/interfaces/udp_multicast.rst | 2 +- doc/interfaces/usb2can.rst | 81 ++++++++++++++++------------- doc/interfaces/vector.rst | 3 ++ doc/interfaces/virtual.rst | 72 +------------------------ doc/internal-api.rst | 9 ++++ doc/listeners.rst | 40 +++++++++++++- doc/plugin-interface.rst | 54 +++++++++++++++++++ doc/scripts.rst | 2 +- doc/utils.rst | 7 +++ doc/virtual-interfaces.rst | 77 +++++++++++++++++++++++++++ examples/print_notifier.py | 19 +++++++ examples/vcan_filtered.py | 2 +- 38 files changed, 486 insertions(+), 365 deletions(-) create mode 100644 doc/errors.rst create mode 100644 doc/plugin-interface.rst create mode 100644 doc/utils.rst create mode 100644 doc/virtual-interfaces.rst create mode 100755 examples/print_notifier.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f10e2b78..04ffb9b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Features xlFlushReceiveQueue to xldriver (#1387). * Vector: Raise a CanInitializationError, if the CAN settings can not be applied according to the arguments of ``VectorBus.__init__`` (#1426). +* Ixxat bus now implements BusState api and detects errors (#1141) Bug Fixes --------- diff --git a/README.rst b/README.rst index 11404785a..79b985129 100644 --- a/README.rst +++ b/README.rst @@ -62,7 +62,7 @@ Library Version Python ------------------------------ ----------- 2.x 2.6+, 3.4+ 3.x 2.7+, 3.5+ - 4.x *(currently on develop)* 3.7+ + 4.x 3.7+ ============================== =========== @@ -74,7 +74,7 @@ Features - receiving, sending, and periodically sending messages - normal and extended arbitration IDs - `CAN FD `__ support -- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), CSV, SQLite and Canutils log +- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), TRC, CSV, SQLite, and Canutils log - efficient in-kernel or in-hardware filtering of messages on supported interfaces - bus configuration reading from a file or from environment variables - command line tools for working with CAN buses (see the `docs `__) @@ -84,6 +84,8 @@ Features Example usage ------------- +``pip install python-can`` + .. code:: python # import the library @@ -117,9 +119,6 @@ Discussion If you run into bugs, you can file them in our `issue tracker `__ on GitHub. -There is also a `python-can `__ -mailing list for development discussion. - `Stackoverflow `__ has several questions and answers tagged with ``python+can``. diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 9ad7fbef8..d735b7ee3 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -20,7 +20,7 @@ except ImportError: cantact = None logger.warning( - "The CANtact module is not installed. Install it using `python -m pip install cantact`" + "The CANtact module is not installed. Install it using `pip install cantact`" ) diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 7f74c685f..2ba1205b1 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -55,7 +55,7 @@ class UdpMulticastBus(BusABC): .. warning:: This interface does not make guarantees on reliable delivery and message ordering, and also does not implement rate limiting or ID arbitration/prioritization under high loads. Please refer to the section - :ref:`other_virtual_interfaces` for more information on this and a comparison to alternatives. + :ref:`virtual_interfaces_doc` for more information on this and a comparison to alternatives. :param channel: A multicast IPv4 address (in `224.0.0.0/4`) or an IPv6 address (in `ff00::/8`). This defines which version of IP is used. See diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index cc71469b5..25b7abfb0 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -51,7 +51,7 @@ class VirtualBus(BusABC): .. warning:: This interface guarantees reliable delivery and message ordering, but does *not* implement rate limiting or ID arbitration/prioritization under high loads. Please refer to the section - :ref:`other_virtual_interfaces` for more information on this and a comparison to alternatives. + :ref:`virtual_interfaces_doc` for more information on this and a comparison to alternatives. """ def __init__( diff --git a/doc/api.rst b/doc/api.rst index 23342f992..053bd34a4 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -17,33 +17,9 @@ A form of CAN interface is also required. listeners asyncio bcm + errors bit_timing + utils internal-api -Utilities ---------- - - -.. autofunction:: can.detect_available_configs - - -.. _notifier: - -Notifier --------- - -The Notifier object is used as a message distributor for a bus. Notifier creates a thread to read messages from the bus and distributes them to listeners. - -.. autoclass:: can.Notifier - :members: - - -.. _errors: - -Errors ------- - -.. automodule:: can.exceptions - :members: - :show-inheritance: diff --git a/doc/bus.rst b/doc/bus.rst index 4db49ee29..06a740829 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -3,48 +3,55 @@ Bus --- -The :class:`~can.BusABC` class, as the name suggests, provides an abstraction of a CAN bus. -The bus provides a wrapper around a physical or virtual CAN Bus. -An interface specific instance of the :class:`~can.BusABC` is created by the :class:`~can.Bus` -class, for example:: +The :class:`~can.Bus` provides a wrapper around a physical or virtual CAN Bus. + +An interface specific instance is created by instantiating the :class:`~can.Bus` +class with a particular ``interface``, for example:: vector_bus = can.Bus(interface='vector', ...) -That bus is then able to handle the interface specific software/hardware interactions -and implements the :class:`~can.BusABC` API. +The created bus is then able to handle the interface specific software/hardware interactions +while giving the user the same top level API. A thread safe bus wrapper is also available, see `Thread safe bus`_. -.. autoclass:: can.Bus - :class-doc-from: class - :show-inheritance: - :members: - :inherited-members: - -.. autoclass:: can.bus.BusState - :members: - :undoc-members: - Transmitting '''''''''''' Writing individual messages to the bus is done by calling the :meth:`~can.BusABC.send` method -and passing a :class:`~can.Message` instance. Periodic sending is controlled by the -:ref:`broadcast manager `. +and passing a :class:`~can.Message` instance. + +.. code-block:: python + :emphasize-lines: 8 + + with can.Bus() as bus: + msg = can.Message( + arbitration_id=0xC0FFEE, + data=[0, 25, 0, 1, 3, 1, 4, 1], + is_extended_id=True + ) + try: + bus.send(msg) + print(f"Message sent on {bus.channel_info}") + except can.CanError: + print("Message NOT sent") +Periodic sending is controlled by the :ref:`broadcast manager `. + Receiving ''''''''' Reading from the bus is achieved by either calling the :meth:`~can.BusABC.recv` method or by directly iterating over the bus:: - for msg in bus: - print(msg.data) + with can.Bus() as bus: + for msg in bus: + print(msg.data) -Alternatively the :class:`~can.Listener` api can be used, which is a list of :class:`~can.Listener` -subclasses that receive notifications when new messages arrive. +Alternatively the :ref:`listeners_doc` api can be used, which is a list of various +:class:`~can.Listener` implementations that receive and handle messages from a :class:`~can.Notifier`. Filtering @@ -67,6 +74,20 @@ Example defining two filters, one to pass 11-bit ID ``0x451``, the other to pass See :meth:`~can.BusABC.set_filters` for the implementation. +Bus API +''''''' + +.. autoclass:: can.Bus + :class-doc-from: class + :show-inheritance: + :members: + :inherited-members: + +.. autoclass:: can.bus.BusState + :members: + :undoc-members: + + Thread safe bus ''''''''''''''' diff --git a/doc/errors.rst b/doc/errors.rst new file mode 100644 index 000000000..bc954738a --- /dev/null +++ b/doc/errors.rst @@ -0,0 +1,8 @@ +.. _errors: + +Error Handling +============== + +.. automodule:: can.exceptions + :members: + :show-inheritance: diff --git a/doc/history.rst b/doc/history.rst index 9ae0581b0..73371af4c 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -1,5 +1,5 @@ -History and Roadmap -=================== +History +======= Background ---------- diff --git a/doc/index.rst b/doc/index.rst index f24831c7c..505c8b87b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -8,22 +8,22 @@ different hardware devices, and a suite of utilities for sending and receiving messages on a CAN bus. **python-can** runs any where Python runs; from high powered computers -with commercial `CAN to usb` devices right down to low powered devices running +with commercial `CAN to USB` devices right down to low powered devices running linux such as a BeagleBone or RaspberryPi. More concretely, some example uses of the library: -- Passively logging what occurs on a CAN bus. For example monitoring a +* Passively logging what occurs on a CAN bus. For example monitoring a commercial vehicle using its **OBD-II** port. -- Testing of hardware that interacts via CAN. Modules found in - modern cars, motocycles, boats, and even wheelchairs have had components tested +* Testing of hardware that interacts via CAN. Modules found in + modern cars, motorcycles, boats, and even wheelchairs have had components tested from Python using this library. -- Prototyping new hardware modules or software algorithms in-the-loop. Easily +* Prototyping new hardware modules or software algorithms in-the-loop. Easily interact with an existing bus. -- Creating virtual modules to prototype CAN bus communication. +* Creating virtual modules to prototype CAN bus communication. Brief example of the library in action: connecting to a CAN bus, creating and sending a message: @@ -37,12 +37,14 @@ Brief example of the library in action: connecting to a CAN bus, creating and se Contents: .. toctree:: - :maxdepth: 2 + :maxdepth: 1 installation configuration api interfaces + virtual-interfaces + plugin-interface scripts development history diff --git a/doc/installation.rst b/doc/installation.rst index bfce72180..6b2a2cfb2 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -2,15 +2,24 @@ Installation ============ -Install ``can`` with ``pip``: -:: +Install the ``can`` package from PyPi with ``pip`` or similar:: $ pip install python-can -As most likely you will want to interface with some hardware, you may -also have to install platform dependencies. Be sure to check any other -specifics for your hardware in :doc:`interfaces`. + + +.. warning:: + As most likely you will want to interface with some hardware, you may + also have to install platform dependencies. Be sure to check any other + specifics for your hardware in :doc:`interfaces`. + + Many interfaces can install their dependencies at the same time as ``python-can``, + for instance the interface ``serial`` includes the ``pyserial`` dependency which can + be installed with the ``serial`` extra:: + + $ pip install python-can[serial] + GNU/Linux dependencies @@ -99,7 +108,7 @@ To install ``python-can`` using the CANtact driver backend: If ``python-can`` is already installed, the CANtact backend can be installed separately: -``python3 -m pip install cantact`` +``pip install cantact`` Additional CANtact documentation is available at `cantact.io `__. diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 54d70ca86..cc686d2d5 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -1,14 +1,18 @@ .. _can interface modules: -CAN Interface Modules ---------------------- +Hardware Interfaces +=================== **python-can** hides the low-level, device-specific interfaces to controller area network adapters in interface dependant modules. However as each hardware device is different, you should carefully go through your interface's documentation. -The available interfaces are: +.. note:: + The *Interface Names* are listed in :doc:`configuration`. + + +The available hardware interfaces are: .. toctree:: :maxdepth: 1 @@ -32,63 +36,6 @@ The available interfaces are: interfaces/socketcan interfaces/socketcand interfaces/systec - interfaces/udp_multicast interfaces/usb2can interfaces/vector - interfaces/virtual - -The *Interface Names* are listed in :doc:`configuration`. - - -.. _plugin interface: - -Plugin Interface -^^^^^^^^^^^^^^^^ - -External packages can register new interfaces by using the ``can.interface`` entry point -in its project configuration. The format of the entry point depends on your project -configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*). - -In the following example ``module`` defines the location of your bus class inside your -package e.g. ``my_package.subpackage.bus_module`` and ``classname`` is the name of -your :class:`can.BusABC` subclass. - -.. tab:: pyproject.toml (PEP 621) - - .. code-block:: toml - - # Note the quotes around can.interface in order to escape the dot . - [project.entry-points."can.interface"] - interface_name = "module:classname" - -.. tab:: setup.cfg - - .. code-block:: ini - - [options.entry_points] - can.interface = - interface_name = module:classname - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup - - setup( - # ..., - entry_points = { - 'can.interface': [ - 'interface_name = module:classname' - ] - } - ) - -The ``interface_name`` can be used to -create an instance of the bus in the **python-can** API: - -.. code-block:: python - - import can - bus = can.Bus(interface="interface_name", channel=0) diff --git a/doc/interfaces/canalystii.rst b/doc/interfaces/canalystii.rst index 375e1b754..b48782259 100644 --- a/doc/interfaces/canalystii.rst +++ b/doc/interfaces/canalystii.rst @@ -12,7 +12,7 @@ Windows, Linux and Mac. .. note:: - The backend driver depends on `pyusb ` so a ``pyusb`` backend driver library such as ``libusb`` must be installed. On Windows a tool such as `Zadig ` can be used to set the Canalyst-II USB device driver to ``libusb-win32``. + The backend driver depends on `pyusb `_ so a ``pyusb`` backend driver library such as ``libusb`` must be installed. On Windows a tool such as `Zadig `_ can be used to set the Canalyst-II USB device driver to ``libusb-win32``. Limitations ----------- diff --git a/doc/interfaces/etas.rst b/doc/interfaces/etas.rst index 2b59a4eee..7986142be 100644 --- a/doc/interfaces/etas.rst +++ b/doc/interfaces/etas.rst @@ -3,19 +3,22 @@ ETAS This interface adds support for CAN interfaces by `ETAS`_. The ETAS BOA_ (Basic Open API) is used. + +Installation +------------ + Install the "ETAS ECU and Bus Interfaces – Distribution Package". -Only Windows is supported by this interface. -The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 USB modules. -To use these under Linux, please refer to :ref:`SocketCAN`. -Bus ---- +.. warning:: + Only Windows is supported by this interface. -.. autoclass:: can.interfaces.etas.EtasBus - :members: + The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 + USB modules. To use these under Linux, please refer to the :ref:`SocketCAN` interface + documentation. -Configuration file ------------------- + +Configuration +------------- The simplest configuration file would be:: @@ -31,5 +34,13 @@ To find available URIs, use :meth:`~can.detect_available_configs`:: for c in configs: print(c) + +Bus +--- + +.. autoclass:: can.interfaces.etas.EtasBus + :members: + + .. _ETAS: https://www.etas.com/ .. _BOA: https://www.etas.com/de/downloadcenter/18102.php diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index 232786fb7..af69581be 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -1,9 +1,10 @@ .. _gs_usb: -CAN driver for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces -================================================================================================================== +Geschwister Schneider and candleLight +===================================== -Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces. +Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schneider USB/CAN devices +and candleLight USB CAN interfaces. Install: ``pip install "python-can[gs_usb]"`` @@ -17,13 +18,19 @@ Usage: pass device ``index`` (starting from 0) if using automatic device detecti Alternatively, pass ``bus`` and ``address`` to open a specific device. The parameters can be got by ``pyusb`` as shown below: -:: +.. code-block:: python import usb import can dev = usb.core.find(idVendor=0x1D50, idProduct=0x606F) - bus = can.Bus(bustype="gs_usb", channel=dev.product, bus=dev.bus, address=dev.address, bitrate=250000) + bus = can.Bus( + bustype="gs_usb", + channel=dev.product, + bus=dev.bus, + address=dev.address, + bitrate=250000 + ) Supported devices @@ -39,21 +46,29 @@ Windows, Linux and Mac. .. note:: - The backend driver depends on `pyusb ` so a ``pyusb`` backend driver library such as ``libusb`` must be installed. On Windows a tool such as `Zadig ` can be used to set the USB device driver to ``libusb-win32``. + The backend driver depends on `pyusb `_ so a ``pyusb`` backend driver library such as + ``libusb`` must be installed. + + On Windows a tool such as `Zadig `_ can be used to set the USB device driver to + ``libusb-win32``. -Supplementary Info on ``gs_usb`` ------------------------------------ +Supplementary Info +------------------ The firmware implementation for Geschwister Schneider USB/CAN devices and candleLight USB CAN can be found in `candle-usb/candleLight_fw `_. The Linux kernel driver can be found in `linux/drivers/net/can/usb/gs_usb.c `_. -The ``gs_usb`` interface in ``PythonCan`` relys on upstream ``gs_usb`` package, which can be found in `https://pypi.org/project/gs-usb/ `_ or `https://github.com/jxltom/gs_usb `_. -The ``gs_usb`` package is using ``pyusb`` as backend, which brings better crossplatform compatibility. +The ``gs_usb`` interface in ``python-can`` relies on upstream ``gs_usb`` package, which can be found in +`https://pypi.org/project/gs-usb/ `_ or +`https://github.com/jxltom/gs_usb `_. + +The ``gs_usb`` package uses ``pyusb`` as backend, which brings better cross-platform compatibility. Note: The bitrate ``10K``, ``20K``, ``50K``, ``83.333K``, ``100K``, ``125K``, ``250K``, ``500K``, ``800K`` and ``1M`` are supported in this interface, as implemented in the upstream ``gs_usb`` package's ``set_bitrate`` method. -Note: Message filtering is not supported in Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces. +.. warning:: + Message filtering is not supported in Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces. Bus --- diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 02e707c1c..61df70638 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -1,9 +1,9 @@ .. _ixxatdoc: -IXXAT Virtual CAN Interface -=========================== +IXXAT Virtual Communication Interface +===================================== -Interface to `IXXAT `__ Virtual CAN Interface V3 SDK. Works on Windows. +Interface to `IXXAT `__ Virtual Communication Interface V3 SDK. Works on Windows. The Linux ECI SDK is currently unsupported, however on Linux some devices are supported with :doc:`socketcan`. @@ -14,33 +14,8 @@ Modifying cyclic messages is not possible. You will need to stop it, and then start a new periodic message. -Bus ---- - -.. autoclass:: can.interfaces.ixxat.IXXATBus - :members: - -Implementation based on vcinpl.dll -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.IXXATBus - :members: - -.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.CyclicSendTask - :members: - -Implementation based on vcinpl2.dll -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.IXXATBus - :members: - -.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.CyclicSendTask - :members: - - -Configuration file ------------------- +Configuration +------------- The simplest configuration file would be:: [default] @@ -91,6 +66,32 @@ To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids( Found IXXAT with hardware id 'HW107422'. +Bus +--- + +.. autoclass:: can.interfaces.ixxat.IXXATBus + :members: + +Implementation based on vcinpl.dll +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.IXXATBus + :members: + +.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.CyclicSendTask + :members: + +Implementation based on vcinpl2.dll +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.IXXATBus + :members: + +.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.CyclicSendTask + :members: + + + Internals --------- diff --git a/doc/interfaces/kvaser.rst b/doc/interfaces/kvaser.rst index 4e0062cfa..f2c93f85b 100644 --- a/doc/interfaces/kvaser.rst +++ b/doc/interfaces/kvaser.rst @@ -20,7 +20,7 @@ Internals The Kvaser :class:`~can.Bus` object with a physical CAN Bus can be operated in two modes; ``single_handle`` mode with one shared bus handle used for both reading and writing to the CAN bus, or with two separate bus handles. -Two separate handles are needed if receiving and sending messages are done in +Two separate handles are needed if receiving and sending messages in different threads (see `Kvaser documentation `_). @@ -40,7 +40,7 @@ in the ``recv`` method. If a message does not match any of the filters, Custom methods -~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~ This section contains Kvaser driver specific methods. diff --git a/doc/interfaces/neovi.rst b/doc/interfaces/neovi.rst index 588c5e914..0baf08055 100644 --- a/doc/interfaces/neovi.rst +++ b/doc/interfaces/neovi.rst @@ -1,7 +1,7 @@ -neoVI -===== +Intrepid Control Systems neoVI +============================== -.. warning:: +.. note:: This ``ICS neoVI`` documentation is a work in progress. Feedback and revisions are most welcome! @@ -14,16 +14,16 @@ wrapper on Windows. Installation ------------ -This neoVI interface requires the installation of the ICS neoVI DLL and python-ics +This neoVI interface requires the installation of the ICS neoVI DLL and ``python-ics`` package. - Download and install the Intrepid Product Drivers `Intrepid Product Drivers `__ -- Install python-ics +- Install ``python-can`` with the ``neovi`` extras: .. code-block:: bash - pip install python-ics + pip install python-ics[neovi] Configuration diff --git a/doc/interfaces/nican.rst b/doc/interfaces/nican.rst index 4d2a40717..6e802a3d4 100644 --- a/doc/interfaces/nican.rst +++ b/doc/interfaces/nican.rst @@ -1,7 +1,7 @@ -NI-CAN -====== +National Instruments NI-CAN +=========================== -This interface adds support for CAN controllers by `National Instruments`_. +This interface adds support for NI-CAN controllers by `National Instruments`_. .. warning:: diff --git a/doc/interfaces/nixnet.rst b/doc/interfaces/nixnet.rst index 36d1a8ef0..8cf2ee72d 100644 --- a/doc/interfaces/nixnet.rst +++ b/doc/interfaces/nixnet.rst @@ -1,12 +1,12 @@ -NI-XNET -======= +National Instruments NI-XNET +============================ This interface adds support for NI-XNET CAN controllers by `National Instruments`_. -.. warning:: +.. note:: - NI-XNET only seems to support windows platforms. + NI-XNET only supports windows platforms. Bus diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index feb40b195..790264627 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -3,7 +3,7 @@ PCAN Basic API ============== -Interface to `Peak-System `__'s PCAN-Basic API. +Interface to `Peak-System `__'s PCAN-Basic API. Configuration ------------- @@ -18,15 +18,9 @@ Here is an example configuration file for using `PCAN-USB The socketcan package is an implementation of CAN protocols -> (Controller Area Network) for Linux. CAN is a networking technology -> which has widespread use in automation, embedded devices, and -> automotive fields. While there have been other CAN implementations -> for Linux based on character devices, SocketCAN uses the Berkeley -> socket API, the Linux network stack and implements the CAN device -> drivers as network interfaces. The CAN socket API has been designed -> as similar as possible to the TCP/IP protocols to allow programmers, -> familiar with network programming, to easily learn how to use CAN -> sockets. +The SocketCAN documentation can be found in the `Linux kernel docs`_ in the +``networking`` directory. Quoting from the SocketCAN Linux documentation: + + The socketcan package is an implementation of CAN protocols + (Controller Area Network) for Linux. CAN is a networking technology + which has widespread use in automation, embedded devices, and + automotive fields. While there have been other CAN implementations + for Linux based on character devices, SocketCAN uses the Berkeley + socket API, the Linux network stack and implements the CAN device + drivers as network interfaces. The CAN socket API has been designed + as similar as possible to the TCP/IP protocols to allow programmers, + familiar with network programming, to easily learn how to use CAN + sockets. .. important:: diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst index 3c05bcc85..2f313470c 100644 --- a/doc/interfaces/socketcand.rst +++ b/doc/interfaces/socketcand.rst @@ -4,7 +4,7 @@ socketcand Interface ==================== `Socketcand `__ is part of the `Linux-CAN `__ project, providing a -Network-to-CAN bridge as Linux damon. It implements a specific +Network-to-CAN bridge as a Linux damon. It implements a specific `TCP/IP based communication protocol `__ to transfer CAN frames and control commands. @@ -24,7 +24,7 @@ daemon running on a remote Raspberry Pi: try: while True: msg = bus.recv() - print (msg) + print(msg) except KeyboardInterrupt: pass diff --git a/doc/interfaces/systec.rst b/doc/interfaces/systec.rst index 0aa4d9444..6b04fdfe0 100644 --- a/doc/interfaces/systec.rst +++ b/doc/interfaces/systec.rst @@ -28,12 +28,6 @@ The interface supports following devices: - USB-CANmodul1 G4, - USB-CANmodul2 G4. -Bus ---- - -.. autoclass:: can.interfaces.systec.ucanbus.UcanBus - :members: - Configuration ------------- @@ -57,6 +51,14 @@ Optional parameters: * ``state`` (default BusState.ACTIVE) BusState of the channel * ``receive_own_messages`` (default False) If messages transmitted should also be received back + +Bus +--- + +.. autoclass:: can.interfaces.systec.ucanbus.UcanBus + :members: + + Internals --------- diff --git a/doc/interfaces/udp_multicast.rst b/doc/interfaces/udp_multicast.rst index f2775727c..b15354ed5 100644 --- a/doc/interfaces/udp_multicast.rst +++ b/doc/interfaces/udp_multicast.rst @@ -16,7 +16,7 @@ sufficiently reliable for this interface to function properly. .. note:: For an overview over the different virtual buses in this library and beyond, please refer - to the section :ref:`other_virtual_interfaces`. It also describes important limitations + to the section :ref:`virtual_interfaces_doc`. It also describes important limitations of this interface. Please refer to the `Bus class documentation`_ below for configuration options and useful resources diff --git a/doc/interfaces/usb2can.rst b/doc/interfaces/usb2can.rst index 56243d41d..1ac9dd61f 100644 --- a/doc/interfaces/usb2can.rst +++ b/doc/interfaces/usb2can.rst @@ -1,36 +1,35 @@ USB2CAN Interface ================= -OVERVIEW --------- - The `USB2CAN `_ is a cheap CAN interface based on an ARM7 chip (STR750FV2). There is support for this device on Linux through the :doc:`socketcan` interface and for Windows using this ``usb2can`` interface. - -WINDOWS SUPPORT ---------------- - Support though windows is achieved through a DLL very similar to the way the PCAN functions. The API is called CANAL (CAN Abstraction Layer) which is a separate project designed to be used with VSCP which is a socket like messaging system -that is not only cross platform but also supports other types of devices. This device can be used through one of three ways -1)Through python-can -2)CANAL API either using the DLL and C/C++ or through the python wrapper that has been added to this project -3)VSCP -Using python-can is strongly suggested as with little extra work the same interface can be used on both Windows and Linux. +that is not only cross platform but also supports other types of devices. + + +Installation +------------ + +1. To install on Windows download the USB2CAN Windows driver. It is compatible with XP, Vista, Win7, Win8/8.1. (Written against driver version v1.0.2.1) + +2. Install the appropriate version of `pywin32 `_ (win32com) + +3. Download the USB2CAN CANAL DLL from the USB2CAN website. + Place this in either the same directory you are running usb2can.py from or your DLL folder in your python install. + Note that only a 32-bit version is currently available, so this only works in a 32-bit Python environment. + + +Internals +--------- -WINDOWS INSTALL ---------------- +This interface originally written against CANAL DLL version ``v1.0.6``. - 1. To install on Windows download the USB2CAN Windows driver. It is compatible with XP, Vista, Win7, Win8/8.1. (Written against driver version v1.0.2.1) - 2. Install the appropriate version of `pywin32 `_ (win32com) - 3. Download the USB2CAN CANAL DLL from the USB2CAN website. Place this in either the same directory you are running usb2can.py from or your DLL folder in your python install. - Note that only a 32-bit version is currently available, so this only works in a 32-bit Python environment. - (Written against CANAL DLL version v1.0.6) Interface Layout ----------------- +~~~~~~~~~~~~~~~~ - ``usb2canabstractionlayer.py`` This file is only a wrapper for the CANAL API that the interface expects. There are also a couple of constants here to try and make dealing with the @@ -50,20 +49,26 @@ Interface Layout Interface Specific Items ------------------------ -There are a few things that are kinda strange about this device and are not overly obvious about the code or things that are not done being implemented in the DLL. - -1. You need the Serial Number to connect to the device under Windows. This is part of the "setup string" that configures the device. There are a few options for how to get this. - 1. Use usb2canWin.py to find the serial number - 2. Look on the device and enter it either through a prompt/barcode scanner/hardcode it.(Not recommended) - 3. Reprogram the device serial number to something and do that for all the devices you own. (Really Not Recommended, can no longer use multiple devices on one computer) +There are a few things that are kinda strange about this device and are not overly obvious about the code or things that +are not done being implemented in the DLL. + +1. You need the Serial Number to connect to the device under Windows. This is part of the "setup string" that configures the device. There are a few options for how to get this. + + 1. Use ``usb2canWin.py`` to find the serial number. + 2. Look on the device and enter it either through a prompt/barcode scanner/hardcode it. (Not recommended) + 3. Reprogram the device serial number to something and do that for all the devices you own. (Really Not Recommended, can no longer use multiple devices on one computer) -2. In usb2canabstractionlayer.py there is a structure called CanalMsg which has a unsigned byte array of size 8. In the usb2canInterface file it passes in an unsigned byte array of - size 8 also which if you pass less than 8 bytes in it stuffs it with extra zeros. So if the data "01020304" is sent the message would look like "0102030400000000". - There is also a part of this structure called sizeData which is the actual length of the data that was sent not the stuffed message (in this case would be 4). - What then happens is although a message of size 8 is sent to the device only the length of information so the first 4 bytes of information would be sent. This - is done because the DLL expects a length of 8 and nothing else. So to make it compatible that has to be sent through the wrapper. If usb2canInterface sent an - array of length 4 with sizeData of 4 as well the array would throw an incompatible data type error. There is a Wireshark file posted in Issue #36 that demonstrates - that the bus is only sending the data and not the extra zeros. +2. In ``usb2canabstractionlayer.py`` there is a structure called ``CanalMsg`` which has a unsigned byte array of size 8. + In the ``usb2canInterface`` file it passes in an unsigned byte array of size 8 also which if you pass less than 8 + bytes in it stuffs it with extra zeros. So if the data ``"01020304"`` is sent the message would look like + ``"0102030400000000"``. + + There is also a part of this structure called ``sizeData`` which is the actual length of the data that was sent not + the stuffed message (in this case would be 4). What then happens is although a message of size 8 is sent to the device + only the first 4 bytes of information would be sent. This is done because the DLL expects a length of 8 and nothing + else. So to make it compatible that has to be sent through the wrapper. If ``usb2canInterface`` sent an + array of length 4 with sizeData of 4 as well the array would throw an incompatible data type error. + 3. The masking features have not been implemented currently in the CANAL interface in the version currently on the USB2CAN website. @@ -79,12 +84,16 @@ Bus .. autoclass:: can.interfaces.usb2can.Usb2canBus +Exceptions +---------- + +.. autoexception:: can.interfaces.usb2can.usb2canabstractionlayer.CanalError -Internals ---------- + +Miscellaneous +------------- .. autoclass:: can.interfaces.usb2can.Usb2CanAbstractionLayer :members: :undoc-members: -.. autoexception:: can.interfaces.usb2can.usb2canabstractionlayer.CanalError diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index 7f9aa1f3f..56961e7e2 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -3,6 +3,9 @@ Vector This interface adds support for CAN controllers by `Vector`_. Only Windows is supported. +Configuration +------------- + By default this library uses the channel configuration for CANalyzer. To use a different application, open **Vector Hardware Configuration** program and create a new application and assign the channels you may want to use. diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst index 29976ed47..7569ffeb9 100644 --- a/doc/interfaces/virtual.rst +++ b/doc/interfaces/virtual.rst @@ -8,79 +8,9 @@ Any `VirtualBus` instances connecting to the same channel (from within the same process) will receive each others messages. If messages shall be sent across process or host borders, consider using the -:ref:`udp_multicast_doc` and refer to (:ref:`the next section `) +:ref:`udp_multicast_doc` and refer to :ref:`virtual_interfaces_doc` for a comparison and general discussion of different virtual interfaces. -.. _other_virtual_interfaces: - -Other Virtual Interfaces ------------------------- - -There are quite a few implementations for CAN networks that do not require physical -CAN hardware. -This section also describes common limitations of current virtual interfaces. - -Comparison -'''''''''' - -The following table compares some known virtual interfaces: - -+----------------------------------------------------+-----------------------------------------------------------------------+---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ -| **Name** | **Availability** | **Applicability** | **Implementation** | -| | +-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ -| | | **Within | **Between | **Via (IP) | **Without Central | **Transport | **Serialization | -| | | Process** | Processes** | Networks** | Server** | Technology** | Format** | -+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ -| ``virtual`` (this) | *included* | ✓ | ✗ | ✗ | ✓ | Singleton & Mutex | none | -| | | | | | | (reliable) | | -+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ -| ``udp_multicast`` (:ref:`doc `) | *included* | ✓ | ✓ | ✓ | ✓ | UDP via IP multicast | custom using `msgpack `__ | -| | | | | | | (unreliable) | | -+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ -| *christiansandberg/ | `external `__ | ✓ | ✓ | ✓ | ✗ | Websockets via TCP/IP | custom binary | -| python-can-remote* | | | | | | (reliable) | | -+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ -| *windelbouwman/ | `external `__ | ✓ | ✓ | ✓ | ✗ | `ZeroMQ `__ via TCP/IP | custom binary [#f1]_ | -| virtualcan* | | | | | | (reliable) | | -+----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ - -.. [#f1] - The only option in this list that implements interoperability with other languages - out of the box. For the others (except the first intra-process one), other programs written - in potentially different languages could effortlessly interface with the bus - once they mimic the serialization format. The last one, however, has already implemented - the entire bus functionality in *C++* and *Rust*, besides the Python variant. - -Common Limitations -'''''''''''''''''' - -**Guaranteed delivery** and **message ordering** is one major point of difference: -While in a physical CAN network, a message is either sent or in queue (or an explicit error occurred), -this may not be the case for virtual networks. -The ``udp_multicast`` bus for example, drops this property for the benefit of lower -latencies by using unreliable UDP/IP instead of reliable TCP/IP (and because normal IP multicast -is inherently unreliable, as the recipients are unknown by design). The other three buses faithfully -model a physical CAN network in this regard: They ensure that all recipients actually receive -(and acknowledge each message), much like in a physical CAN network. They also ensure that -messages are relayed in the order they have arrived at the central server and that messages -arrive at the recipients exactly once. Both is not guaranteed to hold for the best-effort -``udp_multicast`` bus as it uses UDP/IP as a transport layer. - -**Central servers** are, however, required by interfaces 3 and 4 (the external tools) to provide -these guarantees of message delivery and message ordering. The central servers receive and distribute -the CAN messages to all other bus participants, unlike in a real physical CAN network. -The first intra-process ``virtual`` interface only runs within one Python process, effectively the -Python instance of :class:`~can.interfaces.virtual.VirtualBus` acts as a central server. -Notably the ``udp_multicast`` bus does not require a central server. - -**Arbitration and throughput** are two interrelated functions/properties of CAN networks which -are typically abstracted in virtual interfaces. In all four interfaces, an unlimited amount -of messages can be sent per unit of time (given the computational power of the machines and -networks that are involved). In a real CAN/CAN FD networks, however, throughput is usually much -more restricted and prioritization of arbitration IDs is thus an important feature once the bus -is starting to get saturated. None of the interfaces presented above support any sort of throttling -or ID arbitration under high loads. - Example ------- diff --git a/doc/internal-api.rst b/doc/internal-api.rst index 3ef599598..b8c108fb5 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -7,6 +7,15 @@ Here we document the odds and ends that are more helpful for creating your own i or listeners but generally shouldn't be required to interact with python-can. +BusABC +------ + +The :class:`~can.BusABC` class, as the name suggests, provides an abstraction of a CAN bus. +The bus provides a wrapper around a physical or virtual CAN Bus. + +An interface specific instance of the :class:`~can.BusABC` is created by the :class:`~can.Bus` +class, see :ref:`bus` for the user facing API. + .. _businternals: diff --git a/doc/listeners.rst b/doc/listeners.rst index ad18aff24..260854d2a 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -1,5 +1,18 @@ -Listeners -========= + +Reading and Writing Messages +============================ + +.. _notifier: + +Notifier +-------- + +The Notifier object is used as a message distributor for a bus. Notifier creates a thread to read messages from the bus and distributes them to listeners. + +.. autoclass:: can.Notifier + :members: + +.. _listeners_doc: Listener -------- @@ -12,6 +25,12 @@ message, or by calling the method **on_message_received**. Listeners are registered with :ref:`notifier` object(s) which ensure they are notified whenever a new message is received. +.. literalinclude:: ../examples/print_notifier.py + :language: python + :linenos: + :emphasize-lines: 8,9 + + Subclasses of Listener that do not override **on_message_received** will cause :class:`NotImplementedError` to be thrown when a message is received on the CAN bus. @@ -191,3 +210,20 @@ The following class can be used to read messages from BLF file: .. autoclass:: can.BLFReader :members: + +TRC +---- + +Implements basic support for the TRC file format. + + +.. note:: + Comments and contributions are welcome on what file versions might be relevant. + +.. autoclass:: can.TRCWriter + :members: + +The following class can be used to read messages from TRC file: + +.. autoclass:: can.TRCReader + :members: diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst new file mode 100644 index 000000000..14c3f51d5 --- /dev/null +++ b/doc/plugin-interface.rst @@ -0,0 +1,54 @@ + +.. _plugin interface: + +Plugin Interface +================ + +External packages can register new interfaces by using the ``can.interface`` entry point +in its project configuration. The format of the entry point depends on your project +configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*). + +In the following example ``module`` defines the location of your bus class inside your +package e.g. ``my_package.subpackage.bus_module`` and ``classname`` is the name of +your :class:`can.BusABC` subclass. + +.. tab:: pyproject.toml (PEP 621) + + .. code-block:: toml + + # Note the quotes around can.interface in order to escape the dot . + [project.entry-points."can.interface"] + interface_name = "module:classname" + +.. tab:: setup.cfg + + .. code-block:: ini + + [options.entry_points] + can.interface = + interface_name = module:classname + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup + + setup( + # ..., + entry_points = { + 'can.interface': [ + 'interface_name = module:classname' + ] + } + ) + +The ``interface_name`` can be used to +create an instance of the bus in the **python-can** API: + +.. code-block:: python + + import can + + bus = can.Bus(interface="interface_name", channel=0) + diff --git a/doc/scripts.rst b/doc/scripts.rst index 6b9bdf504..5a615afa7 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -1,7 +1,7 @@ Scripts ======= -The following modules are callable from python-can. +The following modules are callable from ``python-can``. They can be called for example by ``python -m can.logger`` or ``can_logger.py`` (if installed using pip). diff --git a/doc/utils.rst b/doc/utils.rst new file mode 100644 index 000000000..a87d411a9 --- /dev/null +++ b/doc/utils.rst @@ -0,0 +1,7 @@ +Utilities +--------- + + +.. autofunction:: can.detect_available_configs + + diff --git a/doc/virtual-interfaces.rst b/doc/virtual-interfaces.rst new file mode 100644 index 000000000..70ac601fa --- /dev/null +++ b/doc/virtual-interfaces.rst @@ -0,0 +1,77 @@ + +.. _virtual_interfaces_doc: + +Virtual Interfaces +================== + +There are quite a few implementations for CAN networks that do not require physical +CAN hardware. The built in virtual interfaces are: + +.. toctree:: + :maxdepth: 1 + + interfaces/virtual + interfaces/udp_multicast + + +Comparison +---------- + +The following table compares some known virtual interfaces: + ++----------------------------------------------------+-----------------------------------------------------------------------+---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ +| **Name** | **Availability** | **Applicability** | **Implementation** | +| | +-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| | | **Within | **Between | **Via (IP) | **Without Central | **Transport | **Serialization | +| | | Process** | Processes** | Networks** | Server** | Technology** | Format** | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| ``virtual`` (this) | *included* | ✓ | ✗ | ✗ | ✓ | Singleton & Mutex | none | +| | | | | | | (reliable) | | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| ``udp_multicast`` (:ref:`doc `) | *included* | ✓ | ✓ | ✓ | ✓ | UDP via IP multicast | custom using `msgpack `__ | +| | | | | | | (unreliable) | | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| *christiansandberg/ | `external `__ | ✓ | ✓ | ✓ | ✗ | Websockets via TCP/IP | custom binary | +| python-can-remote* | | | | | | (reliable) | | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ +| *windelbouwman/ | `external `__ | ✓ | ✓ | ✓ | ✗ | `ZeroMQ `__ via TCP/IP | custom binary [#f1]_ | +| virtualcan* | | | | | | (reliable) | | ++----------------------------------------------------+-----------------------------------------------------------------------+-----------+-------------+-------------+--------------------+---------------------------------------------+---------------------------------------------------------------------+ + +.. [#f1] + The only option in this list that implements interoperability with other languages + out of the box. For the others (except the first intra-process one), other programs written + in potentially different languages could effortlessly interface with the bus + once they mimic the serialization format. The last one, however, has already implemented + the entire bus functionality in *C++* and *Rust*, besides the Python variant. + +Common Limitations +------------------ + +**Guaranteed delivery** and **message ordering** is one major point of difference: +While in a physical CAN network, a message is either sent or in queue (or an explicit error occurred), +this may not be the case for virtual networks. +The ``udp_multicast`` bus for example, drops this property for the benefit of lower +latencies by using unreliable UDP/IP instead of reliable TCP/IP (and because normal IP multicast +is inherently unreliable, as the recipients are unknown by design). The other three buses faithfully +model a physical CAN network in this regard: They ensure that all recipients actually receive +(and acknowledge each message), much like in a physical CAN network. They also ensure that +messages are relayed in the order they have arrived at the central server and that messages +arrive at the recipients exactly once. Both is not guaranteed to hold for the best-effort +``udp_multicast`` bus as it uses UDP/IP as a transport layer. + +**Central servers** are, however, required by interfaces 3 and 4 (the external tools) to provide +these guarantees of message delivery and message ordering. The central servers receive and distribute +the CAN messages to all other bus participants, unlike in a real physical CAN network. +The first intra-process ``virtual`` interface only runs within one Python process, effectively the +Python instance of :class:`~can.interfaces.virtual.VirtualBus` acts as a central server. +Notably the ``udp_multicast`` bus does not require a central server. + +**Arbitration and throughput** are two interrelated functions/properties of CAN networks which +are typically abstracted in virtual interfaces. In all four interfaces, an unlimited amount +of messages can be sent per unit of time (given the computational power of the machines and +networks that are involved). In a real CAN/CAN FD networks, however, throughput is usually much +more restricted and prioritization of arbitration IDs is thus an important feature once the bus +is starting to get saturated. None of the interfaces presented above support any sort of throttling +or ID arbitration under high loads. + diff --git a/examples/print_notifier.py b/examples/print_notifier.py new file mode 100755 index 000000000..bbead7d15 --- /dev/null +++ b/examples/print_notifier.py @@ -0,0 +1,19 @@ +import time +import can + + +def main(): + + with can.Bus(receive_own_messages=True) as bus: + print_listener = can.Printer() + can.Notifier(bus, [print_listener]) + + bus.send(can.Message(arbitration_id=1, is_extended_id=True)) + bus.send(can.Message(arbitration_id=2, is_extended_id=True)) + bus.send(can.Message(arbitration_id=1, is_extended_id=False)) + + time.sleep(1.0) + + +if __name__ == "__main__": + main() diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index a43fbe821..d48a9d8cb 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -16,7 +16,7 @@ def main(): can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}] bus.set_filters(can_filters) - # print all incoming messages, wich includes the ones sent, + # print all incoming messages, which includes the ones sent, # since we set receive_own_messages to True # assign to some variable so it does not garbage collected notifier = can.Notifier(bus, [can.Printer()]) # pylint: disable=unused-variable From 71ba9dd24ccf179b8ff0f474c1bd1ddc0ed85a04 Mon Sep 17 00:00:00 2001 From: szlegp Date: Mon, 21 Nov 2022 23:33:43 +0100 Subject: [PATCH 0944/1235] Comment about bus.shutdown call in README.rst --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index cd2731696..a558957de 100644 --- a/README.rst +++ b/README.rst @@ -89,7 +89,8 @@ Example usage # import the library import can - # create a bus instance + # create a bus instance using 'with' statement, + # this will cause bus.shutdown() to be called on the block exit; # many other interfaces are supported as well (see documentation) with can.Bus(interface='socketcan', channel='vcan0', From 50b1709e77b7b50a92a5f565fe8891918acd3bd9 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:46:57 +0100 Subject: [PATCH 0945/1235] Use furo html theme for documentation (#1443) --- doc/conf.py | 3 +-- doc/doc-requirements.txt | 2 +- doc/interfaces/vector.rst | 4 +++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index c1409b8c2..de08fc300 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,7 +49,6 @@ "sphinx.ext.graphviz", "sphinxcontrib.programoutput", "sphinx_inline_tabs", - "sphinx_rtd_theme", ] # Now, you can use the alias name as a new role, e.g. :issue:`123`. @@ -139,7 +138,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "sphinx_rtd_theme" +html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt index b1c2da632..c61b55683 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,4 +1,4 @@ sphinx>=5.2.3 sphinxcontrib-programoutput -sphinx_rtd_theme sphinx-inline-tabs +furo diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index 56961e7e2..d3e2bed45 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -15,7 +15,9 @@ the bus or in a config file. Channel should be given as a list of channels starting at 0. Here is an example configuration file connecting to CAN 1 and CAN 2 for an -application named "python-can":: +application named "python-can": + +:: [default] interface = vector From 7ca2aad4d09d73a34a72266f620005c10a2d1fee Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 15 Nov 2022 19:13:03 +1300 Subject: [PATCH 0946/1235] Editing changelog for 4.1.0 --- CHANGELOG.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ffb9b57..fc0fb84b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ Version 4.1.0 ==== +Breaking Changes +---------------- + +* ``windows-curses`` was moved to optional dependencies (#1395). + Use ``pip install python-can[viewer]`` if you are using the ``can.viewer`` + script on Windows. +* The attributes of ``can.interfaces.vector.VectorChannelConfig`` were renamed + from camelCase to snake_case (#1422). + + Features -------- @@ -14,6 +24,7 @@ Features Currently only the blf-, canutils- and csv-formats are supported. * All CLI ``extra_args`` are passed to the bus, logger and player initialisation (#1366). +* Initial support for TRC files (#1217) ### Type Annotations * python-can now includes the ``py.typed`` marker to support type checking @@ -63,15 +74,7 @@ Miscellaneous * Exclude repository-configuration from git-archive (#1343) * Improve documentation (#1397, #1401, #1405, #1420, #1421) * Officially support Python 3.11 (#1423) - -Breaking Changes ----------------- - -* ``windows-curses`` was moved to optional dependencies (#1395). - Use ``pip install python-can[viewer]`` if you are using the ``can.viewer`` - script on Windows. -* The attributes of ``can.interfaces.vector.VectorChannelConfig`` were renamed - from camelCase to snake_case (#1422). +* Migrate code coverage reporting from Codecov to Coveralls (#1430) Version 4.0.0 ==== From 488454561a03bea952fef7da7f93f9cbbce912aa Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 15 Nov 2022 19:24:34 +1300 Subject: [PATCH 0947/1235] Bump version for alpha release of 4.1.0 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 8af42009a..68a309e31 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.1.0.dev0" +__version__ = "4.1.0a0" log = logging.getLogger("can") From e07142d423b1c97fdc050cd6912da8a7a07d367d Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 15 Nov 2022 20:28:54 +1300 Subject: [PATCH 0948/1235] Remove codecov config file --- .codecov.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index b9b9b52b6..000000000 --- a/.codecov.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Validate with curl --data-binary @.codecov.yml https://codecov.io/validate -codecov: - archive: - uploads: yes - -coverage: - precision: 2 - round: down - range: 50...100 - status: - project: - default: - # coverage may fall by <1.0% and still be considered "passing" - threshold: 1.0% - patch: - default: - # coverage may fall by <1.0% and still be considered "passing" - threshold: 1.0% - -comment: - layout: "header, diff, changes" From 3ea6eee5089289dcae97d3f49ab5e6924f87ebc4 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 16 Nov 2022 23:06:31 +1300 Subject: [PATCH 0949/1235] Get github actions to publish releases to pypi --- .github/workflows/{build.yml => ci.yml} | 22 ++++++++++++++++++++++ .travis.yml | 24 ------------------------ requirements-lint.txt | 6 +++--- 3 files changed, 25 insertions(+), 27 deletions(-) rename .github/workflows/{build.yml => ci.yml} (86%) diff --git a/.github/workflows/build.yml b/.github/workflows/ci.yml similarity index 86% rename from .github/workflows/build.yml rename to .github/workflows/ci.yml index 2ed1742c6..0162b4d95 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/ci.yml @@ -143,3 +143,25 @@ jobs: run: pipx run build - name: Check build artifacts run: pipx run twine check --strict dist/* + - name: Save artifacts + uses: actions/upload-artifact@v3 + with: + name: python-can-dist + path: ./dist + + upload_pypi: + needs: [build] + runs-on: ubuntu-latest + + # upload to PyPI only on release + if: github.event.release && github.event.action == 'published' + steps: + - uses: actions/download-artifact@v3 + with: + name: python-can-dist + path: dist + + - uses: pypa/gh-action-pypi-publish@v1.4.2 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.travis.yml b/.travis.yml index fb24f8e9a..9bd1920e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,27 +63,3 @@ jobs: travis_retry sudo pip install tox # Run the tests sudo tox -e travis - - - stage: documentation - name: "Sphinx Build" - python: "3.9" - before_install: - - travis_retry pip install -r doc/doc-requirements.txt - script: - # Build the docs with Sphinx - # -a Write all files - # -n nitpicky - - python -m sphinx -an doc build - - stage: deploy - name: "PyPi Deployment" - python: "3.9" - deploy: - provider: pypi - distributions: "sdist bdist_wheel" - user: hardbyte - password: - secure: oQ9XpEkcilkZgKp+rKvPb2J1GrZe2ZvtOq/IjzCpiA8NeWixl/ai3BkPrLbd8t1wNIFoGwx7IQ7zxWL79aPYeG6XrljEomv3g45NR6dkQewUH+dQFlnT75Rm96Ycxvme0w1+71vM4PqxIuzyXUrF2n7JjC0XCCxHdTuYmPGbxVO1fOsE5R5b9inAbpEUtJuWz5AIrDEZ0OgoQpLSC8fLwbymTThX3JZ5GBLpRScVvLazjIYfRkZxvCqQ4mp1UNTdoMzekxsvxOOcEW6+j3fQO+Q/8uvMksKP0RgT8HE69oeYOeVic4Q4wGqORw+ur4A56NvBqVKtizVLCzzEG9ZfoSDy7ryvGWGZykkh8HX0PFQAEykC3iYihHK8ZFz5bEqRMegTmuRYZwPsel61wVd5posxnQkGm0syIoJNKuuRc5sUK+E3GviYcT8NntdR+4WBrvpQAYa1ZHpVrfnQXyaDmGzOjwCRGPoIDJweEqGVmLycEC5aT8rX3/W9tie9iPnjmFJh4CwNMxDgVQRo80m6Gtlf/DQpA3mH39IvWGqd5fHdTPxYPs32EQSCsaYLJV5pM8xBNv6M2S/KriGnGZU0xT7MEr46da0LstKsK/U8O0yamjyugMvQoC3zQcKLrDzWFSBsT7/vG+AuV5SK8yzfEHugo7jkPQQ+NTw29xzk4dY= - on: - # Have travis deploy tagged commits to PyPi - tags: true - skip_cleanup: true diff --git a/requirements-lint.txt b/requirements-lint.txt index f62eeb189..92af74fa0 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,5 +1,5 @@ -pylint==2.12.2 -black~=22.3.0 -mypy==0.931 +pylint==2.15.5 +black~=22.10.0 +mypy==0.991 mypy-extensions==0.4.3 types-setuptools From f697eb5bdd3ee77464b44b15d21a84e7321f1c31 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 16 Nov 2022 23:08:54 +1300 Subject: [PATCH 0950/1235] Run the CI on releases, pull requests and every push to develop --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0162b4d95..b97d05332 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,11 @@ name: Tests -on: [push, pull_request] - +on: + release: + types: [ published ] + pull_request: + push: + branches: [ develop, main ] env: PY_COLORS: "1" From 717be7e31a6dbdb8c33fc0a95da26429531e86ff Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 16 Nov 2022 23:09:57 +1300 Subject: [PATCH 0951/1235] Bump version to 4.1.0a1 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 68a309e31..2398f4fdc 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.1.0a0" +__version__ = "4.1.0a1" log = logging.getLogger("can") From 786bdbfce940167cc5448a6ee59168cb60f10ce4 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 16 Nov 2022 23:18:40 +1300 Subject: [PATCH 0952/1235] Fix mypy's complaints --- can/interfaces/vector/canlib.py | 2 +- can/io/player.py | 2 +- can/viewer.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 8f699f3f7..6abb26d40 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -28,7 +28,7 @@ INFINITE: Optional[int] try: # Try builtin Python 3 Windows API - from _winapi import WaitForSingleObject, INFINITE + from _winapi import WaitForSingleObject, INFINITE # type: ignore HAS_EVENTS = True except ImportError: diff --git a/can/io/player.py b/can/io/player.py index 82f851502..21d1964bb 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -106,7 +106,7 @@ def decompress( return real_suffix, gzip.open(filename, mode) def __iter__(self) -> typing.Generator[Message, None, None]: - pass + raise NotImplementedError() class MessageSync: # pylint: disable=too-few-public-methods diff --git a/can/viewer.py b/can/viewer.py index 6773a0acd..202f8d546 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -390,7 +390,7 @@ def _fill_text(self, text, width, indent): return super()._fill_text(text, width, indent) -def parse_args(args): +def parse_args(args: List[str]) -> Tuple: # Parse command line arguments parser = argparse.ArgumentParser( "python -m can.viewer", From a963fccb17db04a4ee0150a50feee3f337abfd13 Mon Sep 17 00:00:00 2001 From: hardbyte Date: Wed, 16 Nov 2022 10:19:13 +0000 Subject: [PATCH 0953/1235] Format code with black --- can/interfaces/vector/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 6abb26d40..fea23fb54 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -28,7 +28,7 @@ INFINITE: Optional[int] try: # Try builtin Python 3 Windows API - from _winapi import WaitForSingleObject, INFINITE # type: ignore + from _winapi import WaitForSingleObject, INFINITE # type: ignore HAS_EVENTS = True except ImportError: From 16ab05042937fd1cab12c58f917155b3ec8af75e Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 16 Nov 2022 23:27:54 +1300 Subject: [PATCH 0954/1235] Remove pylint unrecognized option --- .pylintrc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.pylintrc b/.pylintrc index 8144d5d8f..a42935e6a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -395,12 +395,6 @@ max-line-length=100 # Maximum number of lines in a module. max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator # Allow the body of a class to be on the same line as the declaration if body # contains single statement. From a2cb2adec70b5e433c2660e1dad7f6485f695904 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 16 Nov 2022 23:31:33 +1300 Subject: [PATCH 0955/1235] Don't install in local virtual env before running pylint --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b97d05332..5ea3076f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . pip install -r requirements-lint.txt - name: mypy 3.7 run: | From 1a1b119637d2d8d4bcf984ab0718b32fd6491509 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 16 Nov 2022 23:58:35 +1300 Subject: [PATCH 0956/1235] Revert pylint version bump --- requirements-lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-lint.txt b/requirements-lint.txt index 92af74fa0..2952103c3 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ -pylint==2.15.5 +pylint==2.12.2 black~=22.10.0 mypy==0.991 mypy-extensions==0.4.3 From 9f68b7af921a92b4f9af244842e2e1a41cc3bced Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 18 Nov 2022 10:26:26 +1300 Subject: [PATCH 0957/1235] Update examples to use default configured Bus where it makes sense --- examples/cyclic.py | 4 +++- examples/receive_all.py | 11 +++++++---- examples/{virtual_can_demo.py => send_multiple.py} | 5 ++++- 3 files changed, 14 insertions(+), 6 deletions(-) rename examples/{virtual_can_demo.py => send_multiple.py} (80%) diff --git a/examples/cyclic.py b/examples/cyclic.py index 573465d78..bdd69eef2 100755 --- a/examples/cyclic.py +++ b/examples/cyclic.py @@ -114,7 +114,9 @@ def main(): arbitration_id=0x00, data=[0, 0, 0, 0, 0, 0], is_extended_id=False ) - with can.Bus(interface="virtual") as bus: + # this uses the default configuration (for example from environment variables, or a + # config file) see https://python-can.readthedocs.io/en/stable/configuration.html + with can.Bus() as bus: bus.send(reset_msg) simple_periodic_send(bus) diff --git a/examples/receive_all.py b/examples/receive_all.py index 7b94d526f..d8d8714fc 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -11,12 +11,15 @@ def receive_all(): """Receives all messages and prints them to the console until Ctrl+C is pressed.""" - with can.Bus(interface="pcan", channel="PCAN_USBBUS1", bitrate=250000) as bus: - # bus = can.Bus(interface='ixxat', channel=0, bitrate=250000) - # bus = can.Bus(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000) + # this uses the default configuration (for example from environment variables, or a + # config file) see https://python-can.readthedocs.io/en/stable/configuration.html + with can.Bus() as bus: # set to read-only, only supported on some interfaces - bus.state = BusState.PASSIVE + try: + bus.state = BusState.PASSIVE + except NotImplementedError: + pass try: while True: diff --git a/examples/virtual_can_demo.py b/examples/send_multiple.py similarity index 80% rename from examples/virtual_can_demo.py rename to examples/send_multiple.py index af50a87a7..240b3d1cf 100755 --- a/examples/virtual_can_demo.py +++ b/examples/send_multiple.py @@ -16,7 +16,10 @@ def producer(thread_id: int, message_count: int = 16) -> None: :param thread_id: the id of the thread/process :param message_count: the number of messages that shall be sent """ - with can.Bus(interface="socketcan", channel="vcan0") as bus: # type: ignore + + # this uses the default configuration (for example from environment variables, or a + # config file) see https://python-can.readthedocs.io/en/stable/configuration.html + with can.Bus() as bus: # type: ignore for i in range(message_count): msg = can.Message( arbitration_id=0x0CF02200 + thread_id, From 3af52709d59dc0933e791f18211361d30bce0d6c Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 18 Nov 2022 19:28:00 +1300 Subject: [PATCH 0958/1235] Allow restarting of transmission tasks for socketcan (#1440) * Allow restarting of transmission tasks for socketcan * Update can/interfaces/socketcan/socketcan.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/socketcan/socketcan.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 549998dc8..f7b9a9ae4 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -349,7 +349,9 @@ def __init__( self.task_id = task_id self._tx_setup(self.messages) - def _tx_setup(self, messages: Sequence[Message]) -> None: + def _tx_setup( + self, messages: Sequence[Message], raise_if_task_exists: bool = True + ) -> None: # Create a low level packed frame to pass to the kernel body = bytearray() self.flags = CAN_FD_FRAME if messages[0].is_fd else 0 @@ -363,7 +365,8 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: ival1 = 0.0 ival2 = self.period - self._check_bcm_task() + if raise_if_task_exists: + self._check_bcm_task() header = build_bcm_transmit_header( self.task_id, count, ival1, ival2, self.flags, nframes=len(messages) @@ -375,7 +378,7 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: def _check_bcm_task(self) -> None: # Do a TX_READ on a task ID, and check if we get EINVAL. If so, - # then we are referring to a CAN message with the existing ID + # then we are referring to a CAN message with an existing ID check_header = build_bcm_header( opcode=CAN_BCM_TX_READ, flags=0, @@ -387,12 +390,19 @@ def _check_bcm_task(self) -> None: can_id=self.task_id, nframes=0, ) + log.debug( + f"Reading properties of (cyclic) transmission task id={self.task_id}", + ) try: self.bcm_socket.send(check_header) except OSError as error: if error.errno != errno.EINVAL: raise can.CanOperationError("failed to check", error.errno) from error + else: + log.debug("Invalid argument - transmission task not known to kernel") else: + # No exception raised - transmission task with this ID exists in kernel. + # Existence of an existing transmission task might not be a problem! raise can.CanOperationError( f"A periodic task for task ID {self.task_id} is already in progress " "by the SocketCAN Linux layer" @@ -438,7 +448,7 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: send_bcm(self.bcm_socket, header + body) def start(self) -> None: - """Start a periodic task by sending TX_SETUP message to Linux kernel. + """Restart a periodic task by sending TX_SETUP message to Linux kernel. It verifies presence of the particular BCM task through sending TX_READ message to Linux kernel prior to scheduling. @@ -446,7 +456,7 @@ def start(self) -> None: :raises ValueError: If the task referenced by ``task_id`` is already running. """ - self._tx_setup(self.messages) + self._tx_setup(self.messages, raise_if_task_exists=False) class MultiRateCyclicSendTask(CyclicSendTask): From e44833442819411ff725e434682007eb7cf7b18d Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 18 Nov 2022 19:34:07 +1300 Subject: [PATCH 0959/1235] 4.1.0-a2 version bump --- can/__init__.py | 2 +- can/logger.py | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 2398f4fdc..edaf7278b 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.1.0a1" +__version__ = "4.1.0a2" log = logging.getLogger("can") diff --git a/can/logger.py b/can/logger.py index 8cb201987..f13b78bfc 100644 --- a/can/logger.py +++ b/can/logger.py @@ -1,18 +1,3 @@ -""" -logger.py logs CAN traffic to the terminal and to a file on disk. - - logger.py can0 - -See candump in the can-utils package for a C implementation. -Efficient filtering has been implemented for the socketcan backend. -For example the command - - logger.py can0 F03000:FFF000 - -Will filter for can frames with a can_id containing XXF03XXX. - -Dynamic Controls 2010 -""" import re import sys import argparse From 881606150316f3fce37b5605ea2cbdd4620a811f Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 21 Nov 2022 10:26:46 +1300 Subject: [PATCH 0960/1235] Update changelog and set version to 4.1.0 --- CHANGELOG.md | 4 +++- can/__init__.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc0fb84b3..c6ff2bbbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Bug Fixes * Fix file name for compressed files in SizedRotatingLogger (#1382, #1683). * Fix memory leak in neoVI bus where message_receipts grows with no limit (#1427). * Raise ValueError if gzip is used with incompatible log formats (#1429). +* Allow restarting of transmission tasks for socketcan (#1440) Miscellaneous ------------- @@ -72,9 +73,10 @@ Miscellaneous * Allow ICSApiError to be pickled and un-pickled (#1341) * Sort interface names in CLI API to make documentation reproducible (#1342) * Exclude repository-configuration from git-archive (#1343) -* Improve documentation (#1397, #1401, #1405, #1420, #1421) +* Improve documentation (#1397, #1401, #1405, #1420, #1421, #1434) * Officially support Python 3.11 (#1423) * Migrate code coverage reporting from Codecov to Coveralls (#1430) +* Migrate building docs and publishing releases to PyPi from Travis-CI to GitHub Actions (#1433) Version 4.0.0 ==== diff --git a/can/__init__.py b/can/__init__.py index edaf7278b..773e94022 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Dict, Any -__version__ = "4.1.0a2" +__version__ = "4.1.0" log = logging.getLogger("can") From 9f5af3fafd8c8b80e82ad5d6e390c62ac7682784 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 24 Nov 2022 21:00:52 +0100 Subject: [PATCH 0961/1235] Run pyupgrade --py37-plus (#1447) * Run pyupgrade --py37-plus * Format code with black * Fix useless dict comprehension * Add missing type hint Co-authored-by: felixdivo --- can/interfaces/ics_neovi/neovi_bus.py | 14 +++++++------- can/interfaces/ixxat/canlib_vcinpl.py | 6 +++--- can/interfaces/ixxat/canlib_vcinpl2.py | 4 ++-- can/interfaces/ixxat/structures.py | 2 +- can/interfaces/kvaser/canlib.py | 4 ++-- can/interfaces/nixnet.py | 2 +- can/interfaces/pcan/basic.py | 2 +- can/interfaces/pcan/pcan.py | 6 +++--- can/interfaces/robotell.py | 4 ++-- can/interfaces/socketcan/socketcan.py | 4 ++-- can/interfaces/socketcan/utils.py | 4 ++-- can/interfaces/socketcand/socketcand.py | 2 +- can/interfaces/systec/ucanbus.py | 2 +- can/interfaces/vector/canlib.py | 2 +- can/io/canutils.py | 2 +- can/io/trc.py | 6 ++---- can/typechecking.py | 19 +++++++++++++------ can/util.py | 4 ++-- can/viewer.py | 2 +- setup.py | 4 ++-- test/config.py | 4 ++-- test/listener_test.py | 4 ++-- test/logformats_test.py | 2 +- test/message_helper.py | 8 ++++---- test/test_interface_virtual.py | 1 - test/test_load_config.py | 4 ++-- test/test_load_file_config.py | 4 ++-- test/test_message_class.py | 4 ++-- test/test_message_sync.py | 2 +- test/test_viewer.py | 2 +- 30 files changed, 67 insertions(+), 63 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index bd4fc5445..4972ed479 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -171,8 +171,8 @@ def __init__(self, channel, can_filters=None, **kwargs): super().__init__(channel=channel, can_filters=can_filters, **kwargs) - logger.info("CAN Filters: {}".format(can_filters)) - logger.info("Got configuration of: {}".format(kwargs)) + logger.info(f"CAN Filters: {can_filters}") + logger.info(f"Got configuration of: {kwargs}") if "override_library_name" in kwargs: ics.override_library_name(kwargs.get("override_library_name")) @@ -215,12 +215,12 @@ def __init__(self, channel, can_filters=None, **kwargs): self._use_system_timestamp = bool(kwargs.get("use_system_timestamp", False)) self._receive_own_messages = kwargs.get("receive_own_messages", True) - self.channel_info = "%s %s CH:%s" % ( + self.channel_info = "{} {} CH:{}".format( self.dev.Name, self.get_serial_number(self.dev), self.channels, ) - logger.info("Using device: {}".format(self.channel_info)) + logger.info(f"Using device: {self.channel_info}") self.rx_buffer = deque() self.message_receipts = defaultdict(Event) @@ -230,7 +230,7 @@ def channel_to_netid(channel_name_or_id): try: channel = int(channel_name_or_id) except ValueError: - netid = "NETID_{}".format(channel_name_or_id.upper()) + netid = f"NETID_{channel_name_or_id.upper()}" if hasattr(ics, netid): channel = getattr(ics, netid) else: @@ -298,9 +298,9 @@ def _find_device(self, type_filter=None, serial=None): msg = ["No device"] if type_filter is not None: - msg.append("with type {}".format(type_filter)) + msg.append(f"with type {type_filter}") if serial is not None: - msg.append("with serial {}".format(serial)) + msg.append(f"with serial {serial}") msg.append("found.") raise CanInitializationError(" ".join(msg)) diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index fa88e5f90..d74da2539 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -119,7 +119,7 @@ def __check_status(result, function, args): result = ctypes.c_ulong(result).value if result == constants.VCI_E_TIMEOUT: - raise VCITimeout("Function {} timed out".format(function._name)) + raise VCITimeout(f"Function {function._name} timed out") elif result == constants.VCI_E_RXQUEUE_EMPTY: raise VCIRxQueueEmptyError() elif result == constants.VCI_E_NO_MORE_ITEMS: @@ -469,7 +469,7 @@ def __init__( channel = int(channel) if bitrate not in self.CHANNEL_BITRATES[0]: - raise ValueError("Invalid bitrate {}".format(bitrate)) + raise ValueError(f"Invalid bitrate {bitrate}") if rx_fifo_size <= 0: raise ValueError("rx_fifo_size must be > 0") @@ -887,7 +887,7 @@ def _format_can_status(status_flags: int): status_flags &= ~flag if status_flags: - states.append("unknown state 0x{:02x}".format(status_flags)) + states.append(f"unknown state 0x{status_flags:02x}") if states: return "CAN status message: {}".format(", ".join(states)) diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 108ad2c02..2e3125e9b 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -117,7 +117,7 @@ def __check_status(result, function, args): :class:VCIError """ if result == constants.VCI_E_TIMEOUT: - raise VCITimeout("Function {} timed out".format(function._name)) + raise VCITimeout(f"Function {function._name} timed out") elif result == constants.VCI_E_RXQUEUE_EMPTY: raise VCIRxQueueEmptyError() elif result == constants.VCI_E_NO_MORE_ITEMS: @@ -1011,7 +1011,7 @@ def _format_can_status(status_flags: int): status_flags &= ~flag if status_flags: - states.append("unknown state 0x{:02x}".format(status_flags)) + states.append(f"unknown state 0x{status_flags:02x}") if states: return "CAN status message: {}".format(", ".join(states)) diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index b784437e0..419a52973 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -162,7 +162,7 @@ class CANMSG(ctypes.Structure): ] def __str__(self) -> str: - return """ID: 0x{0:04x}{1} DLC: {2:02d} DATA: {3}""".format( + return """ID: 0x{:04x}{} DLC: {:02d} DATA: {}""".format( self.dwMsgId, "[RTR]" if self.uMsgInfo.Bits.rtr else "", self.uMsgInfo.Bits.dlc, diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index f60a43bc5..a8bb7bac7 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -410,8 +410,8 @@ def __init__(self, channel, can_filters=None, **kwargs): """ - log.info("CAN Filters: {}".format(can_filters)) - log.info("Got configuration of: {}".format(kwargs)) + log.info(f"CAN Filters: {can_filters}") + log.info(f"Got configuration of: {kwargs}") bitrate = kwargs.get("bitrate", 500000) tseg1 = kwargs.get("tseg1", 0) tseg2 = kwargs.get("tseg2", 0) diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index f1def62ad..1eba09b31 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -124,7 +124,7 @@ def __init__( ) from None self._is_filtered = False - super(NiXNETcanBus, self).__init__( + super().__init__( channel=channel, can_filters=can_filters, bitrate=bitrate, diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 38ded44f6..743fb55ee 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -663,7 +663,7 @@ def __init__(self): try: aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers") winreg.CloseKey(aKey) - except WindowsError: + except OSError: logger.error("Exception: The PEAK-driver couldn't be found!") finally: winreg.CloseKey(aReg) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index c60b9e6c9..cd0349c99 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -218,7 +218,7 @@ def __init__( channel = self._find_channel_by_dev_id(device_id) if channel is None: - err_msg = "Cannot find a channel with ID {:08x}".format(device_id) + err_msg = f"Cannot find a channel with ID {device_id:08x}" raise ValueError(err_msg) self.channel_info = str(channel) @@ -249,7 +249,7 @@ def __init__( f_clock = "{}={}".format("f_clock", kwargs.get("f_clock", None)) fd_parameters_values = [f_clock] + [ - "{}={}".format(key, kwargs.get(key, None)) + f"{key}={kwargs.get(key, None)}" for key in PCAN_FD_PARAMETER_LIST if kwargs.get(key, None) is not None ] @@ -350,7 +350,7 @@ def bits(n): for b in bits(error): stsReturn = self.m_objPCANBasic.GetErrorText(b, 0x9) if stsReturn[0] != PCAN_ERROR_OK: - text = "An error occurred. Error-code's text ({0:X}h) couldn't be retrieved".format( + text = "An error occurred. Error-code's text ({:X}h) couldn't be retrieved".format( error ) else: diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 0d3ad1b77..4d82c1922 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -396,7 +396,7 @@ def get_serial_number(self, timeout: Optional[int]) -> Optional[str]: serial = "" for idx in range(0, 8, 2): - serial += "{:02X}{:02X}-".format(sn1[idx], sn1[idx + 1]) + serial += f"{sn1[idx]:02X}{sn1[idx + 1]:02X}-" for idx in range(0, 4, 2): - serial += "{:02X}{:02X}-".format(sn2[idx], sn2[idx + 1]) + serial += f"{sn2[idx]:02X}{sn2[idx + 1]:02X}-" return serial[:-1] diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index f7b9a9ae4..f0545f7df 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -68,7 +68,7 @@ def bcm_header_factory( # requirements of this field, then we must add padding bytes until we # are aligned while curr_stride % field_alignment != 0: - results.append(("pad_{}".format(pad_index), ctypes.c_uint8)) + results.append((f"pad_{pad_index}", ctypes.c_uint8)) pad_index += 1 curr_stride += 1 @@ -84,7 +84,7 @@ def bcm_header_factory( # Add trailing padding to align to a multiple of the largest scalar member # in the structure while curr_stride % alignment != 0: - results.append(("pad_{}".format(pad_index), ctypes.c_uint8)) + results.append((f"pad_{pad_index}", ctypes.c_uint8)) pad_index += 1 curr_stride += 1 diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 55e7eb392..ecc870ca4 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -21,7 +21,7 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes # Pass all messages can_filters = [{"can_id": 0, "can_mask": 0}] - can_filter_fmt = "={}I".format(2 * len(can_filters)) + can_filter_fmt = f"={2 * len(can_filters)}I" filter_data = [] for can_filter in can_filters: can_id = can_filter["can_id"] @@ -50,7 +50,7 @@ def find_available_interfaces() -> Iterable[str]: try: # adding "type vcan" would exclude physical can devices command = ["ip", "-o", "link", "list", "up"] - output = subprocess.check_output(command, universal_newlines=True) + output = subprocess.check_output(command, text=True) except Exception as e: # subprocess.CalledProcessError is too specific log.error("failed to fetch opened can devices: %s", e) diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 327df9e73..28f0c700f 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -43,7 +43,7 @@ def convert_can_message_to_ascii_message(can_message: can.Message) -> str: # Note: seems like we cannot add CANFD_BRS (bitrate_switch) and CANFD_ESI (error_state_indicator) flags data = can_message.data length = can_message.dlc - bytes_string = " ".join("{:x}".format(x) for x in data[0:length]) + bytes_string = " ".join(f"{x:x}" for x in data[0:length]) return f"< send {can_id:X} {length:X} {bytes_string} >" diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index fee110b08..7d8b6133a 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -134,7 +134,7 @@ def __init__(self, channel, can_filters=None, **kwargs): self._ucan.init_hardware(device_number=device_number) self._ucan.init_can(self.channel, **self._params) hw_info_ex, _, _ = self._ucan.get_hardware_info() - self.channel_info = "%s, S/N %s, CH %s, BTR %s" % ( + self.channel_info = "{}, S/N {}, CH {}, BTR {}".format( self._ucan.get_product_code_message(hw_info_ex.product_code), hw_info_ex.serial, self.channel, diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index fea23fb54..ff72d262a 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -171,7 +171,7 @@ def __init__( ) self._app_name = app_name.encode() if app_name is not None else b"" - self.channel_info = "Application %s: %s" % ( + self.channel_info = "Application {}: {}".format( app_name, ", ".join(f"CAN {ch + 1}" for ch in self.channels), ) diff --git a/can/io/canutils.py b/can/io/canutils.py index f63df3f6b..e159ecdf4 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -170,7 +170,7 @@ def on_message_received(self, msg): if isinstance(channel, int) or isinstance(channel, str) and channel.isdigit(): channel = f"can{channel}" - framestr = "(%f) %s" % (timestamp, channel) + framestr = f"({timestamp:f}) {channel}" if msg.is_error_frame: framestr += " %08X#" % (CAN_ERR_FLAG | CAN_ERR_BUSERROR) diff --git a/can/io/trc.py b/can/io/trc.py index 25ba0f5c6..0a07f01c9 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ Reader and writer for can logging files in peak trc format @@ -51,7 +49,7 @@ def __init__( If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super(TRCReader, self).__init__(file, mode="r") + super().__init__(file, mode="r") self.file_version = TRCFileVersion.UNKNOWN if not self.file: @@ -211,7 +209,7 @@ def __init__( :param channel: a default channel to use when the message does not have a channel set """ - super(TRCWriter, self).__init__(file, mode="w") + super().__init__(file, mode="w") self.channel = channel if type(file) is str: self.filepath = os.path.abspath(file) diff --git a/can/typechecking.py b/can/typechecking.py index b3a513a3a..31a298995 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -11,9 +11,14 @@ CanFilter: typing_extensions = typing_extensions.TypedDict( "CanFilter", {"can_id": int, "can_mask": int} ) -CanFilterExtended = typing_extensions.TypedDict( - "CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool} -) + + +class CanFilterExtended(typing_extensions.TypedDict): + can_id: int + can_mask: int + extended: bool + + CanFilters = typing.Sequence[typing.Union[CanFilter, CanFilterExtended]] # TODO: Once buffer protocol support lands in typing, we should switch to that, @@ -35,8 +40,10 @@ BusConfig = typing.NewType("BusConfig", typing.Dict[str, typing.Any]) -AutoDetectedConfig = typing_extensions.TypedDict( - "AutoDetectedConfig", {"interface": str, "channel": Channel} -) + +class AutoDetectedConfig(typing_extensions.TypedDict): + interface: str + channel: Channel + ReadableBytesLike = typing.Union[bytes, bytearray, memoryview] diff --git a/can/util.py b/can/util.py index e64eb13b5..41467542d 100644 --- a/can/util.py +++ b/can/util.py @@ -61,10 +61,10 @@ def load_file_config( else: config.read(path) - _config = {} + _config: Dict[str, str] = {} if config.has_section(section): - _config.update(dict((key, val) for key, val in config.items(section))) + _config.update(config.items(section)) return _config diff --git a/can/viewer.py b/can/viewer.py index 202f8d546..e87684485 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -515,7 +515,7 @@ def parse_args(args: List[str]) -> Tuple: ] = {} if parsed_args.decode: if os.path.isfile(parsed_args.decode[0]): - with open(parsed_args.decode[0], "r", encoding="utf-8") as f: + with open(parsed_args.decode[0], encoding="utf-8") as f: structs = f.readlines() else: structs = parsed_args.decode diff --git a/setup.py b/setup.py index a32471844..b3d99f612 100644 --- a/setup.py +++ b/setup.py @@ -15,12 +15,12 @@ logging.basicConfig(level=logging.WARNING) -with open("can/__init__.py", "r", encoding="utf-8") as fd: +with open("can/__init__.py", encoding="utf-8") as fd: version = re.search( r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE ).group(1) -with open("README.rst", "r", encoding="utf-8") as f: +with open("README.rst", encoding="utf-8") as f: long_description = f.read() # Dependencies diff --git a/test/config.py b/test/config.py index fc1bca6c2..d308a8cc8 100644 --- a/test/config.py +++ b/test/config.py @@ -27,7 +27,7 @@ def env(name: str) -> bool: IS_CI = IS_TRAVIS or IS_GITHUB_ACTIONS or env("CI") or env("CONTINUOUS_INTEGRATION") if IS_TRAVIS and IS_GITHUB_ACTIONS: - raise EnvironmentError( + raise OSError( f"only one of IS_TRAVIS ({IS_TRAVIS}) and IS_GITHUB_ACTIONS ({IS_GITHUB_ACTIONS}) may be True at the " "same time" ) @@ -43,7 +43,7 @@ def env(name: str) -> bool: del _sys if (IS_WINDOWS and IS_LINUX) or (IS_LINUX and IS_OSX) or (IS_WINDOWS and IS_OSX): - raise EnvironmentError( + raise OSError( f"only one of IS_WINDOWS ({IS_WINDOWS}), IS_LINUX ({IS_LINUX}) and IS_OSX ({IS_OSX}) " f'can be True at the same time (platform.system() == "{platform.system()}")' ) diff --git a/test/listener_test.py b/test/listener_test.py index 0e64a266a..9b2e9e93b 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -88,7 +88,7 @@ def testRemoveListenerFromNotifier(self): def testPlayerTypeResolution(self): def test_filetype_to_instance(extension, klass): - print("testing: {}".format(extension)) + print(f"testing: {extension}") try: if extension == ".blf": delete = False @@ -123,7 +123,7 @@ def testPlayerTypeResolutionUnsupportedFileTypes(self): def testLoggerTypeResolution(self): def test_filetype_to_instance(extension, klass): - print("testing: {}".format(extension)) + print(f"testing: {extension}") try: with tempfile.NamedTemporaryFile( suffix=extension, delete=False diff --git a/test/logformats_test.py b/test/logformats_test.py index 435b651b6..05c8b986f 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -141,7 +141,7 @@ def _setup_instance_helper( ] assert ( "log_event" in attrs - ), "cannot check comments with this writer: {}".format(writer_constructor) + ), f"cannot check comments with this writer: {writer_constructor}" # get all test comments self.original_comments = TEST_COMMENTS if check_comments else () diff --git a/test/message_helper.py b/test/message_helper.py index 1e7989156..fe193097b 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -31,8 +31,8 @@ def assertMessageEqual(self, message_1, message_2): if message_1.equals(message_2, timestamp_delta=self.allowed_timestamp_delta): return elif self.preserves_channel: - print("Comparing: message 1: {!r}".format(message_1)) - print(" message 2: {!r}".format(message_2)) + print(f"Comparing: message 1: {message_1!r}") + print(f" message 2: {message_2!r}") self.fail( "messages are unequal with allowed timestamp delta {}".format( self.allowed_timestamp_delta @@ -46,8 +46,8 @@ def assertMessageEqual(self, message_1, message_2): ): return else: - print("Comparing: message 1: {!r}".format(message_1)) - print(" message 2: {!r}".format(message_2)) + print(f"Comparing: message 1: {message_1!r}") + print(f" message 2: {message_2!r}") self.fail( "messages are unequal with allowed timestamp delta {} even when ignoring channels".format( self.allowed_timestamp_delta diff --git a/test/test_interface_virtual.py b/test/test_interface_virtual.py index 009722779..94833fdcb 100644 --- a/test/test_interface_virtual.py +++ b/test/test_interface_virtual.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 """ This module tests :meth:`can.interface.virtual`. diff --git a/test/test_load_config.py b/test/test_load_config.py index a2969b0a5..1bfba450a 100644 --- a/test/test_load_config.py +++ b/test/test_load_config.py @@ -30,9 +30,9 @@ def _gen_configration_file(self, sections): ) as tmp_config_file: content = [] for section in sections: - content.append("[{}]".format(section)) + content.append(f"[{section}]") for k, v in self.configuration[section].items(): - content.append("{} = {}".format(k, v)) + content.append(f"{k} = {v}") tmp_config_file.write("\n".join(content)) return tmp_config_file.name diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py index 6b1d5a382..afedf58d4 100644 --- a/test/test_load_file_config.py +++ b/test/test_load_file_config.py @@ -30,9 +30,9 @@ def _gen_configration_file(self, sections): ) as tmp_config_file: content = [] for section in sections: - content.append("[{}]".format(section)) + content.append(f"[{section}]") for k, v in self.configuration[section].items(): - content.append("{} = {}".format(k, v)) + content.append(f"{k} = {v}") tmp_config_file.write("\n".join(content)) return tmp_config_file.name diff --git a/test/test_message_class.py b/test/test_message_class.py index 9fae7262e..4840402ff 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -87,8 +87,8 @@ def test_methods(self, **kwargs): if is_valid: self.assertEqual(len(message), kwargs["dlc"]) self.assertTrue(bool(message)) - self.assertGreater(len("{}".format(message)), 0) - _ = "{}".format(message) + self.assertGreater(len(f"{message}"), 0) + _ = f"{message}" with self.assertRaises(Exception): _ = "{somespec}".format( message diff --git a/test/test_message_sync.py b/test/test_message_sync.py index 8750dd416..7552915e7 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -87,7 +87,7 @@ def test_skip(self): # the handling of the messages itself also takes some time: # ~0.001 s/message on a ThinkPad T560 laptop (Ubuntu 18.04, i5-6200U) - assert 0 < took < inc(len(messages) * (0.005 + 0.003)), "took: {}s".format(took) + assert 0 < took < inc(len(messages) * (0.005 + 0.003)), f"took: {took}s" self.assertMessagesEqual(messages, collected) diff --git a/test/test_viewer.py b/test/test_viewer.py index 5633a3bc1..baef10bda 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -302,7 +302,7 @@ def pack_data( return struct_t.pack(*data) else: - raise ValueError("Unknown command: 0x{:02X}".format(cmd)) + raise ValueError(f"Unknown command: 0x{cmd:02X}") def test_pack_unpack(self): CANOPEN_TPDO1 = 0x180 From cc7f81e8b7d07d58ee44fcd0f1641b0fbaeb97f6 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 26 Nov 2022 23:47:31 +0100 Subject: [PATCH 0962/1235] Use high resolution timer on Windows (#1449) * use high resolution timer * enable CI for feature branches --- .github/workflows/ci.yml | 2 +- can/broadcastmanager.py | 10 +++++++++- setup.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ea3076f2..577ccd97d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: types: [ published ] pull_request: push: - branches: [ develop, main ] + env: PY_COLORS: "1" diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 239d1d7d5..cfa4c658d 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -248,7 +248,15 @@ def __init__( if HAS_EVENTS: self.period_ms = int(round(period * 1000, 0)) - self.event = win32event.CreateWaitableTimer(None, False, None) + try: + self.event = win32event.CreateWaitableTimerEx( + None, + None, + win32event.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + win32event.TIMER_ALL_ACCESS, + ) + except (AttributeError, OSError): + self.event = win32event.CreateWaitableTimer(None, False, None) self.start() diff --git a/setup.py b/setup.py index b3d99f612..dc4c7e11f 100644 --- a/setup.py +++ b/setup.py @@ -92,7 +92,7 @@ "setuptools", "wrapt~=1.10", "typing_extensions>=3.10.0.0", - 'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"', + 'pywin32>=305;platform_system=="Windows" and platform_python_implementation=="CPython"', 'msgpack~=1.0.0;platform_system!="Windows"', "packaging", ], From 486dafb3fd01f428d030ec2c4bf5d944fd77a146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?uml=C3=A4ute?= Date: Tue, 29 Nov 2022 22:51:16 +0100 Subject: [PATCH 0963/1235] executable examples (#1452) * Fix permissions of executable scripts * Add shebang to examples/print_notifier.py - it's executable - it has a __name__=='__main__' section * dos2unix * make sure all python-files have a consistent line-ending * make sure all ASC-files have a consistent line-ending --- can/exceptions.py | 244 +++++++++++++++--------------- examples/crc.py | 168 ++++++++++---------- examples/cyclic_multiple.py | 0 examples/print_notifier.py | 2 + test/data/logfile.asc | 78 +++++----- test/data/logfile_errorframes.asc | 42 ++--- test/test_interface_ixxat.py | 128 ++++++++-------- 7 files changed, 332 insertions(+), 330 deletions(-) mode change 100644 => 100755 examples/crc.py mode change 100644 => 100755 examples/cyclic_multiple.py diff --git a/can/exceptions.py b/can/exceptions.py index dc08be3b8..7496c6c0e 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -1,122 +1,122 @@ -""" -There are several specific :class:`Exception` classes to allow user -code to react to specific scenarios related to CAN busses:: - - Exception (Python standard library) - +-- ... - +-- CanError (python-can) - +-- CanInterfaceNotImplementedError - +-- CanInitializationError - +-- CanOperationError - +-- CanTimeoutError - -Keep in mind that some functions and methods may raise different exceptions. -For example, validating typical arguments and parameters might result in a -:class:`ValueError`. This should always be documented for the function at hand. -""" - -import sys -from contextlib import contextmanager - -from typing import Optional -from typing import Type - -if sys.version_info >= (3, 9): - from collections.abc import Generator -else: - from typing import Generator - - -class CanError(Exception): - """Base class for all CAN related exceptions. - - If specified, the error code is automatically appended to the message: - - >>> # With an error code (it also works with a specific error): - >>> error = CanOperationError(message="Failed to do the thing", error_code=42) - >>> str(error) - 'Failed to do the thing [Error Code 42]' - >>> - >>> # Missing the error code: - >>> plain_error = CanError(message="Something went wrong ...") - >>> str(plain_error) - 'Something went wrong ...' - - :param error_code: - An optional error code to narrow down the cause of the fault - - :arg error_code: - An optional error code to narrow down the cause of the fault - """ - - def __init__( - self, - message: str = "", - error_code: Optional[int] = None, - ) -> None: - self.error_code = error_code - super().__init__( - message if error_code is None else f"{message} [Error Code {error_code}]" - ) - - -class CanInterfaceNotImplementedError(CanError, NotImplementedError): - """Indicates that the interface is not supported on the current platform. - - Example scenarios: - - No interface with that name exists - - The interface is unsupported on the current operating system or interpreter - - The driver could not be found or has the wrong version - """ - - -class CanInitializationError(CanError): - """Indicates an error the occurred while initializing a :class:`can.BusABC`. - - If initialization fails due to a driver or platform missing/being unsupported, - a :exc:`~can.exceptions.CanInterfaceNotImplementedError` is raised instead. - If initialization fails due to a value being out of range, a :class:`ValueError` - is raised. - - Example scenarios: - - Try to open a non-existent device and/or channel - - Try to use an invalid setting, which is ok by value, but not ok for the interface - - The device or other resources are already used - """ - - -class CanOperationError(CanError): - """Indicates an error while in operation. - - Example scenarios: - - A call to a library function results in an unexpected return value - - An invalid message was received - - The driver rejected a message that was meant to be sent - - Cyclic redundancy check (CRC) failed - - A message remained unacknowledged - - A buffer is full - """ - - -class CanTimeoutError(CanError, TimeoutError): - """Indicates the timeout of an operation. - - Example scenarios: - - Some message could not be sent after the timeout elapsed - - No message was read within the given time - """ - - -@contextmanager -def error_check( - error_message: Optional[str] = None, - exception_type: Type[CanError] = CanOperationError, -) -> Generator[None, None, None]: - """Catches any exceptions and turns them into the new type while preserving the stack trace.""" - try: - yield - except Exception as error: # pylint: disable=broad-except - if error_message is None: - raise exception_type(str(error)) from error - else: - raise exception_type(error_message) from error +""" +There are several specific :class:`Exception` classes to allow user +code to react to specific scenarios related to CAN busses:: + + Exception (Python standard library) + +-- ... + +-- CanError (python-can) + +-- CanInterfaceNotImplementedError + +-- CanInitializationError + +-- CanOperationError + +-- CanTimeoutError + +Keep in mind that some functions and methods may raise different exceptions. +For example, validating typical arguments and parameters might result in a +:class:`ValueError`. This should always be documented for the function at hand. +""" + +import sys +from contextlib import contextmanager + +from typing import Optional +from typing import Type + +if sys.version_info >= (3, 9): + from collections.abc import Generator +else: + from typing import Generator + + +class CanError(Exception): + """Base class for all CAN related exceptions. + + If specified, the error code is automatically appended to the message: + + >>> # With an error code (it also works with a specific error): + >>> error = CanOperationError(message="Failed to do the thing", error_code=42) + >>> str(error) + 'Failed to do the thing [Error Code 42]' + >>> + >>> # Missing the error code: + >>> plain_error = CanError(message="Something went wrong ...") + >>> str(plain_error) + 'Something went wrong ...' + + :param error_code: + An optional error code to narrow down the cause of the fault + + :arg error_code: + An optional error code to narrow down the cause of the fault + """ + + def __init__( + self, + message: str = "", + error_code: Optional[int] = None, + ) -> None: + self.error_code = error_code + super().__init__( + message if error_code is None else f"{message} [Error Code {error_code}]" + ) + + +class CanInterfaceNotImplementedError(CanError, NotImplementedError): + """Indicates that the interface is not supported on the current platform. + + Example scenarios: + - No interface with that name exists + - The interface is unsupported on the current operating system or interpreter + - The driver could not be found or has the wrong version + """ + + +class CanInitializationError(CanError): + """Indicates an error the occurred while initializing a :class:`can.BusABC`. + + If initialization fails due to a driver or platform missing/being unsupported, + a :exc:`~can.exceptions.CanInterfaceNotImplementedError` is raised instead. + If initialization fails due to a value being out of range, a :class:`ValueError` + is raised. + + Example scenarios: + - Try to open a non-existent device and/or channel + - Try to use an invalid setting, which is ok by value, but not ok for the interface + - The device or other resources are already used + """ + + +class CanOperationError(CanError): + """Indicates an error while in operation. + + Example scenarios: + - A call to a library function results in an unexpected return value + - An invalid message was received + - The driver rejected a message that was meant to be sent + - Cyclic redundancy check (CRC) failed + - A message remained unacknowledged + - A buffer is full + """ + + +class CanTimeoutError(CanError, TimeoutError): + """Indicates the timeout of an operation. + + Example scenarios: + - Some message could not be sent after the timeout elapsed + - No message was read within the given time + """ + + +@contextmanager +def error_check( + error_message: Optional[str] = None, + exception_type: Type[CanError] = CanOperationError, +) -> Generator[None, None, None]: + """Catches any exceptions and turns them into the new type while preserving the stack trace.""" + try: + yield + except Exception as error: # pylint: disable=broad-except + if error_message is None: + raise exception_type(str(error)) from error + else: + raise exception_type(error_message) from error diff --git a/examples/crc.py b/examples/crc.py old mode 100644 new mode 100755 index 4344fb2ea..18d22681a --- a/examples/crc.py +++ b/examples/crc.py @@ -1,84 +1,84 @@ -#!/usr/bin/env python - -""" -This example exercises the periodic task's multiple message sending capabilities -to send a message containing a counter and a checksum. - -Expects a vcan0 interface: - - python3 -m examples.crc - -""" - -import logging -import time - -import can - -logging.basicConfig(level=logging.INFO) - - -def crc_send(bus): - """ - Sends periodic messages every 1 s with no explicit timeout. Modifies messages - after 8 seconds, sends for 10 more seconds, then stops. - """ - msg = can.Message(arbitration_id=0x12345678, data=[1, 2, 3, 4, 5, 6, 7, 0]) - messages = build_crc_msgs(msg) - - print( - "Starting to send a message with updating counter and checksum every 1 s for 8 s" - ) - task = bus.send_periodic(messages, 1) - assert isinstance(task, can.CyclicSendTaskABC) - time.sleep(8) - - msg = can.Message(arbitration_id=0x12345678, data=[8, 9, 10, 11, 12, 13, 14, 0]) - messages = build_crc_msgs(msg) - - print("Sending modified message data every 1 s for 10 s") - task.modify_data(messages) - time.sleep(10) - task.stop() - print("stopped cyclic send") - - -def build_crc_msgs(msg): - """ - Using the input message as base, create 16 messages with SAE J1939 SPN 3189 counters - and SPN 3188 checksums placed in the final byte. - """ - messages = [] - - for counter in range(16): - checksum = compute_xbr_checksum(msg, counter) - msg.data[7] = counter + (checksum << 4) - messages.append( - can.Message(arbitration_id=msg.arbitration_id, data=msg.data[:]) - ) - - return messages - - -def compute_xbr_checksum(message, counter): - """ - Computes an XBR checksum per SAE J1939 SPN 3188. - """ - checksum = sum(message.data[:7]) - checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder="big")) - checksum += counter & 0x0F - xbr_checksum = ((checksum >> 4) + checksum) & 0x0F - - return xbr_checksum - - -if __name__ == "__main__": - for interface, channel in [("socketcan", "vcan0")]: - print(f"Carrying out crc test with {interface} interface") - - with can.Bus( # type: ignore - interface=interface, channel=channel, bitrate=500000 - ) as BUS: - crc_send(BUS) - - time.sleep(2) +#!/usr/bin/env python + +""" +This example exercises the periodic task's multiple message sending capabilities +to send a message containing a counter and a checksum. + +Expects a vcan0 interface: + + python3 -m examples.crc + +""" + +import logging +import time + +import can + +logging.basicConfig(level=logging.INFO) + + +def crc_send(bus): + """ + Sends periodic messages every 1 s with no explicit timeout. Modifies messages + after 8 seconds, sends for 10 more seconds, then stops. + """ + msg = can.Message(arbitration_id=0x12345678, data=[1, 2, 3, 4, 5, 6, 7, 0]) + messages = build_crc_msgs(msg) + + print( + "Starting to send a message with updating counter and checksum every 1 s for 8 s" + ) + task = bus.send_periodic(messages, 1) + assert isinstance(task, can.CyclicSendTaskABC) + time.sleep(8) + + msg = can.Message(arbitration_id=0x12345678, data=[8, 9, 10, 11, 12, 13, 14, 0]) + messages = build_crc_msgs(msg) + + print("Sending modified message data every 1 s for 10 s") + task.modify_data(messages) + time.sleep(10) + task.stop() + print("stopped cyclic send") + + +def build_crc_msgs(msg): + """ + Using the input message as base, create 16 messages with SAE J1939 SPN 3189 counters + and SPN 3188 checksums placed in the final byte. + """ + messages = [] + + for counter in range(16): + checksum = compute_xbr_checksum(msg, counter) + msg.data[7] = counter + (checksum << 4) + messages.append( + can.Message(arbitration_id=msg.arbitration_id, data=msg.data[:]) + ) + + return messages + + +def compute_xbr_checksum(message, counter): + """ + Computes an XBR checksum per SAE J1939 SPN 3188. + """ + checksum = sum(message.data[:7]) + checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder="big")) + checksum += counter & 0x0F + xbr_checksum = ((checksum >> 4) + checksum) & 0x0F + + return xbr_checksum + + +if __name__ == "__main__": + for interface, channel in [("socketcan", "vcan0")]: + print(f"Carrying out crc test with {interface} interface") + + with can.Bus( # type: ignore + interface=interface, channel=channel, bitrate=500000 + ) as BUS: + crc_send(BUS) + + time.sleep(2) diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py old mode 100644 new mode 100755 diff --git a/examples/print_notifier.py b/examples/print_notifier.py index bbead7d15..b6554ccd2 100755 --- a/examples/print_notifier.py +++ b/examples/print_notifier.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import time import can diff --git a/test/data/logfile.asc b/test/data/logfile.asc index 8e6ac0464..13274a4c4 100644 --- a/test/data/logfile.asc +++ b/test/data/logfile.asc @@ -1,39 +1,39 @@ -date Sam Sep 30 15:06:13.191 2017 -base hex timestamps absolute -internal events logged -// version 9.0.0 -//0.000000 previous log file: logfile_errorframes.asc -Begin Triggerblock Sam Sep 30 15:06:13.191 2017 - 0.000000 Start of measurement - 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0 - 0.015991 CAN 2 Status:chip status error active - 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% - 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% - 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% - 2.501000 1 ErrorFrame - 2.501010 1 ErrorFrame ECC: 10100010 - 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300 - 2.510001 2 100 Tx r - 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x - 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x - 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x - 3.148421 1 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 271910 BitCount = 140 ID = 418119424x - 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x - 3.248765 1 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283910 BitCount = 146 ID = 418119424x - 3.297743 1 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF - 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0 - 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 - 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 - 18.015997 1 Statistic: D 2 R 0 XD 0 XR 0 E 0 O 0 B 0.04% - 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x - 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x - 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x - 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x - 20.305233 2 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF - 30.005071 CANFD 2 Rx 300 Generic_Name_12 1 0 8 8 01 02 03 04 05 06 07 08 102203 133 303000 e0006659 46500250 4b140250 20011736 2001040d - 30.300981 CANFD 3 Tx 50005x 0 0 5 0 140000 73 200050 7a60 46500250 460a0250 20011736 20010205 - 30.506898 CANFD 4 Rx 4EE 0 0 f 64 01 02 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 1331984 11 0 46500250 460a0250 20011736 20010205 - 30.806898 CANFD 5 Tx ErrorFrame Not Acknowledge error, dominant error flag fffe c7 31ca Arb. 556 44 0 0 f 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1331984 11 0 46500250 460a0250 20011736 20010205 - 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% - 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% -End TriggerBlock +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +//0.000000 previous log file: logfile_errorframes.asc +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0 + 0.015991 CAN 2 Status:chip status error active + 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 2.501000 1 ErrorFrame + 2.501010 1 ErrorFrame ECC: 10100010 + 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300 + 2.510001 2 100 Tx r + 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x + 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x + 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x + 3.148421 1 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 271910 BitCount = 140 ID = 418119424x + 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 3.248765 1 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283910 BitCount = 146 ID = 418119424x + 3.297743 1 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF + 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0 + 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 + 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 + 18.015997 1 Statistic: D 2 R 0 XD 0 XR 0 E 0 O 0 B 0.04% + 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x + 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x + 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x + 20.305233 2 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF + 30.005071 CANFD 2 Rx 300 Generic_Name_12 1 0 8 8 01 02 03 04 05 06 07 08 102203 133 303000 e0006659 46500250 4b140250 20011736 2001040d + 30.300981 CANFD 3 Tx 50005x 0 0 5 0 140000 73 200050 7a60 46500250 460a0250 20011736 20010205 + 30.506898 CANFD 4 Rx 4EE 0 0 f 64 01 02 03 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 1331984 11 0 46500250 460a0250 20011736 20010205 + 30.806898 CANFD 5 Tx ErrorFrame Not Acknowledge error, dominant error flag fffe c7 31ca Arb. 556 44 0 0 f 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1331984 11 0 46500250 460a0250 20011736 20010205 + 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% +End TriggerBlock diff --git a/test/data/logfile_errorframes.asc b/test/data/logfile_errorframes.asc index bcb5584a7..6f751bfac 100644 --- a/test/data/logfile_errorframes.asc +++ b/test/data/logfile_errorframes.asc @@ -1,21 +1,21 @@ -date Sam Sep 30 15:06:13.191 2017 -base hex timestamps absolute -internal events logged -// version 9.0.0 -Begin Triggerblock Sam Sep 30 15:06:13.191 2017 - 0.000000 Start of measurement - 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0 - 0.015991 CAN 2 Status:chip status error active - 2.501000 1 ErrorFrame - 2.501010 1 ErrorFrame ECC: 10100010 - 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300 - 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x - 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x - 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x - 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x - 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 - 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x - 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x - 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x - 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x -End TriggerBlock +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0 + 0.015991 CAN 2 Status:chip status error active + 2.501000 1 ErrorFrame + 2.501010 1 ErrorFrame ECC: 10100010 + 2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300 + 2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x + 2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x + 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x + 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 + 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x + 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x + 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x +End TriggerBlock diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 76285e422..484a88b58 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -1,64 +1,64 @@ -#!/usr/bin/env python - -""" -Unittest for ixxat interface. - -Run only this test: -python setup.py test --addopts "--verbose -s test/test_interface_ixxat.py" -""" - -import unittest -import can - - -class SoftwareTestCase(unittest.TestCase): - """ - Test cases that test the software only and do not rely on an existing/connected hardware. - """ - - def setUp(self): - try: - bus = can.Bus(interface="ixxat", channel=0) - bus.shutdown() - except can.CanInterfaceNotImplementedError: - raise unittest.SkipTest("not available on this platform") - - def test_bus_creation(self): - # channel must be >= 0 - with self.assertRaises(ValueError): - can.Bus(interface="ixxat", channel=-1) - - # rx_fifo_size must be > 0 - with self.assertRaises(ValueError): - can.Bus(interface="ixxat", channel=0, rx_fifo_size=0) - - # tx_fifo_size must be > 0 - with self.assertRaises(ValueError): - can.Bus(interface="ixxat", channel=0, tx_fifo_size=0) - - -class HardwareTestCase(unittest.TestCase): - """ - Test cases that rely on an existing/connected hardware. - """ - - def setUp(self): - try: - bus = can.Bus(interface="ixxat", channel=0) - bus.shutdown() - except can.CanInterfaceNotImplementedError: - raise unittest.SkipTest("not available on this platform") - - def test_bus_creation(self): - # non-existent channel -> use arbitrary high value - with self.assertRaises(can.CanInitializationError): - can.Bus(interface="ixxat", channel=0xFFFF) - - def test_send_after_shutdown(self): - with can.Bus(interface="ixxat", channel=0) as bus: - with self.assertRaises(can.CanOperationError): - bus.send(can.Message(arbitration_id=0x3FF, dlc=0)) - - -if __name__ == "__main__": - unittest.main() +#!/usr/bin/env python + +""" +Unittest for ixxat interface. + +Run only this test: +python setup.py test --addopts "--verbose -s test/test_interface_ixxat.py" +""" + +import unittest +import can + + +class SoftwareTestCase(unittest.TestCase): + """ + Test cases that test the software only and do not rely on an existing/connected hardware. + """ + + def setUp(self): + try: + bus = can.Bus(interface="ixxat", channel=0) + bus.shutdown() + except can.CanInterfaceNotImplementedError: + raise unittest.SkipTest("not available on this platform") + + def test_bus_creation(self): + # channel must be >= 0 + with self.assertRaises(ValueError): + can.Bus(interface="ixxat", channel=-1) + + # rx_fifo_size must be > 0 + with self.assertRaises(ValueError): + can.Bus(interface="ixxat", channel=0, rx_fifo_size=0) + + # tx_fifo_size must be > 0 + with self.assertRaises(ValueError): + can.Bus(interface="ixxat", channel=0, tx_fifo_size=0) + + +class HardwareTestCase(unittest.TestCase): + """ + Test cases that rely on an existing/connected hardware. + """ + + def setUp(self): + try: + bus = can.Bus(interface="ixxat", channel=0) + bus.shutdown() + except can.CanInterfaceNotImplementedError: + raise unittest.SkipTest("not available on this platform") + + def test_bus_creation(self): + # non-existent channel -> use arbitrary high value + with self.assertRaises(can.CanInitializationError): + can.Bus(interface="ixxat", channel=0xFFFF) + + def test_send_after_shutdown(self): + with can.Bus(interface="ixxat", channel=0) as bus: + with self.assertRaises(can.CanOperationError): + bus.send(can.Message(arbitration_id=0x3FF, dlc=0)) + + +if __name__ == "__main__": + unittest.main() From 25d30b529dcc28cbe75320d8315b57233fd10cc1 Mon Sep 17 00:00:00 2001 From: Hugo SERRAT Date: Wed, 7 Dec 2022 16:11:09 +0100 Subject: [PATCH 0964/1235] fix: canfilter typing (#1456) Co-authored-by: hugo --- can/typechecking.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/can/typechecking.py b/can/typechecking.py index 31a298995..7aa4f7e5c 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -8,9 +8,10 @@ import typing_extensions -CanFilter: typing_extensions = typing_extensions.TypedDict( - "CanFilter", {"can_id": int, "can_mask": int} -) + +class CanFilter(typing_extensions.TypedDict): + can_id: int + can_mask: int class CanFilterExtended(typing_extensions.TypedDict): From f3136fb5e85f11ecb9ba83744a955a706f9dbde2 Mon Sep 17 00:00:00 2001 From: MattWoodhead Date: Mon, 19 Dec 2022 12:39:19 +0000 Subject: [PATCH 0965/1235] Update documentation with plugins, related tools etc. (#1457) * Update interfaces.rst Add a note regarding the ability to use plugins or installing external modules to extend the functionality of python-can. * Update plugin-interface.rst Add examples of modules using the plugin api * Update index.rst * Create other-tools.rst * Update other-tools.rst * Add optional deps to setup.py * Format code with black * Update other-tools.rst * Update plugin-interface.rst * Update doc/other-tools.rst fix note block Co-authored-by: Brian Thorne * Update doc/other-tools.rst Co-authored-by: Brian Thorne * Address sphinx warnings * Update doc/interfaces.rst Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update doc/interfaces.rst Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update doc/other-tools.rst Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update doc/plugin-interface.rst Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update doc/interfaces.rst Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update doc/plugin-interface.rst Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Remove extra installation tag out of scope of PR * Fix numbering & spelling Fix numbering of lists. Correct careless spelling errors. Co-authored-by: MattWoodhead Co-authored-by: Brian Thorne Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- doc/index.rst | 1 + doc/interfaces.rst | 4 +- doc/other-tools.rst | 79 ++++++++++++++++++++++++++++++++++++++++ doc/plugin-interface.rst | 23 ++++++++++++ setup.py | 3 ++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 doc/other-tools.rst diff --git a/doc/index.rst b/doc/index.rst index 505c8b87b..c55108d97 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -45,6 +45,7 @@ Contents: interfaces virtual-interfaces plugin-interface + other-tools scripts development history diff --git a/doc/interfaces.rst b/doc/interfaces.rst index cc686d2d5..6645ec338 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -12,7 +12,7 @@ documentation. The *Interface Names* are listed in :doc:`configuration`. -The available hardware interfaces are: +The following hardware interfaces are included in python-can: .. toctree:: :maxdepth: 1 @@ -39,3 +39,5 @@ The available hardware interfaces are: interfaces/usb2can interfaces/vector + +Additional interface types can be added via the :ref:`plugin interface`, or by installing a third party package that utilises the :ref:`plugin interface`. diff --git a/doc/other-tools.rst b/doc/other-tools.rst new file mode 100644 index 000000000..56ec21083 --- /dev/null +++ b/doc/other-tools.rst @@ -0,0 +1,79 @@ +Other CAN bus tools +=================== + +In order to keep the project maintainable, the scope of the package is limited to providing common +abstractions to different hardware devices, and a basic suite of utilities for sending and +receiving messages on a CAN bus. Other tools are available that either extend the functionality +of python-can, or provide complementary features that python-can users might find useful. + +Some of these tools are listed below for convenience. + +CAN Message protocols (implemented in Python) +--------------------------------------------- + +#. SAE J1939 Message Protocol + * The `can-j1939`_ module provides an implementation of the CAN SAE J1939 standard for Python, + including J1939-22. `can-j1939`_ uses python-can to provide support for multiple hardware + interfaces. +#. CIA CANopen + * The `canopen`_ module provides an implementation of the CIA CANopen protocol, aiming to be + used for automation and testing purposes +#. ISO 15765-2 (ISO TP) + * The `can-isotp`_ module provides an implementation of the ISO TP CAN protocol for sending + data packets via a CAN transport layer. + +#. UDS + * The `python-uds`_ module is a communication protocol agnostic implementation of the Unified + Diagnostic Services (UDS) protocol defined in ISO 14229-1, although it does have extensions + for performing UDS over CAN utilising the ISO TP protocol. This module has not been updated + for some time. + * The `uds`_ module is another tool that implements the UDS protocol, although it does have + extensions for performing UDS over CAN utilising the ISO TP protocol. This module has not + been updated for some time. +#. XCP + * The `pyxcp`_ module implements the Universal Measurement and Calibration Protocol (XCP). + The purpose of XCP is to adjust parameters and acquire current values of internal + variables in an ECU. + +.. _can-j1939: https://github.com/juergenH87/python-can-j1939 +.. _canopen: https://canopen.readthedocs.io/en/latest/ +.. _can-isotp: https://can-isotp.readthedocs.io/en/latest/ +.. _python-uds: https://python-uds.readthedocs.io/en/latest/index.html +.. _uds: https://uds.readthedocs.io/en/latest/ +.. _pyxcp: https://pyxcp.readthedocs.io/en/latest/ + +CAN Frame Parsing tools etc. (implemented in Python) +---------------------------------------------------- + +#. CAN Message / Database scripting + * The `cantools`_ package provides multiple methods for interacting with can message database + files, and using these files to monitor live busses with a command line monitor tool. +#. CAN Message / Log Decoding + * The `canmatrix`_ module provides methods for converting between multiple popular message + frame definition file formats (e.g. .DBC files, .KCD files, .ARXML files etc.). + * The `pretty_j1939`_ module can be used to post-process CAN logs of J1939 traffic into human + readable terminal prints or into a JSON file for consumption elsewhere in your scripts. + +.. _cantools: https://cantools.readthedocs.io/en/latest/ +.. _canmatrix: https://canmatrix.readthedocs.io/en/latest/ +.. _pretty_j1939: https://github.com/nmfta-repo/pretty_j1939 + +Other CAN related tools, programs etc. +-------------------------------------- + +#. Micropython CAN class + * A `CAN class`_ is available for the original micropython pyboard, with much of the same + functionality as is available with python-can (but with a different API!). +#. ASAM MDF Files + * The `asammdf`_ module provides many methods for processing ASAM (Association for + Standardization of Automation and Measuring Systems) MDF (Measurement Data Format) files. + +.. _`CAN class`: https://docs.micropython.org/en/latest/library/pyb.CAN.html +.. _`asammdf`: https://asammdf.readthedocs.io/en/master/ + +| +| + +.. note:: + See also the available plugins for python-can in :ref:`plugin interface`. + diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index 14c3f51d5..bab8c85a9 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -52,3 +52,26 @@ create an instance of the bus in the **python-can** API: bus = can.Bus(interface="interface_name", channel=0) + + +Example Interface Plugins +------------------------- + +The table below lists interface drivers that can be added by installing additional packages that utilise the plugin API. These modules are optional dependencies of python-can. + +.. note:: + The packages listed below are maintained by other authors. Any issues should be reported in their corresponding repository and **not** in the python-can repository. + ++----------------------------+-------------------------------------------------------+ +| Name | Description | ++============================+=======================================================+ +| `python-can-cvector`_ | Cython based version of the 'VectorBus' | ++----------------------------+-------------------------------------------------------+ +| `python-can-remote`_ | CAN over network bridge | ++----------------------------+-------------------------------------------------------+ +| `python-can-sontheim`_ | CAN Driver for Sontheim CAN interfaces (e.g. CANfox) | ++----------------------------+-------------------------------------------------------+ + +.. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector +.. _python-can-remote: https://github.com/christiansandberg/python-can-remote +.. _python-can-sontheim: https://github.com/MattWoodhead/python-can-sontheim diff --git a/setup.py b/setup.py index dc4c7e11f..bada45b77 100644 --- a/setup.py +++ b/setup.py @@ -30,9 +30,12 @@ "neovi": ["filelock", "python-ics>=2.12"], "canalystii": ["canalystii>=0.1.0"], "cantact": ["cantact>=0.0.7"], + "cvector": ["python-can-cvector"], "gs_usb": ["gs_usb>=0.2.1"], "nixnet": ["nixnet>=0.3.1"], "pcan": ["uptime~=3.0.1"], + "remote": ["python-can-remote"], + "sontheim": ["python-can-sontheim>=0.1.2"], "viewer": [ 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"' ], From 4136f37c115e59d3c6cd7f6f2c9dbba706b51056 Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Wed, 21 Dec 2022 01:33:54 +0100 Subject: [PATCH 0966/1235] Fix Bus.__new__ for PEAK CAN-FD interfaces (#1460) * Add failing unit test to verify that issue #1485 is fixed https://github.com/hardbyte/python-can/issues/1458 * Remove unused generic BitTiming creation in Bus.__new__ The BitTiming class is an attempt at unifying the various timing parameters of the individual interfaces. The idea is that instead of manually supplying multiple parameters that make up the timing definition of the interface, one can instead supply a single instance of the BitTiming class, which will also automatically calculate derivative values from its input. At the moment, this class is only used by two interfaces: CANtact and CANanalystii. Both either accept a single bitrate or a BitTiming instance. The latter will overrule the former. The code that is removed with this commit is part of the generic Bus.__new__ constructor. The removed code searches the set of kwargs parameters for timing-related values. If it finds at least one such value, it creates a BitTiming class instance and adds it to the list of parameters. However, it breaks compatibility with the PEAK interface, see issue #1458. Additionally, the code in question is generic and applies to all interfaces. Instantiating a class here is prone to issues since it must be generic enough to fit all use cases. A better approach would be to simply forward the parameters as was done previously and leave it up to the individual interfaces to handle things properly. * Format code with black Co-authored-by: lumagi --- can/util.py | 19 ------------------- test/test_pcan.py | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/can/util.py b/can/util.py index 41467542d..aa4a28d15 100644 --- a/can/util.py +++ b/can/util.py @@ -233,25 +233,6 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: if "data_bitrate" in config: config["data_bitrate"] = int(config["data_bitrate"]) - # Create bit timing configuration if given - timing_conf = {} - for key in ( - "f_clock", - "brp", - "tseg1", - "tseg2", - "sjw", - "nof_samples", - "btr0", - "btr1", - ): - if key in config: - timing_conf[key] = int(str(config[key]), base=0) - del config[key] - if timing_conf: - timing_conf["bitrate"] = config["bitrate"] - config["timing"] = can.BitTiming(**timing_conf) - return cast(typechecking.BusConfig, config) diff --git a/test/test_pcan.py b/test/test_pcan.py index 0a680fea0..ab03bf0a1 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -373,6 +373,26 @@ def test_auto_reset_init_fault(self): with self.assertRaises(CanInitializationError): self.bus = can.Bus(bustype="pcan", auto_reset=True) + def test_peak_fd_bus_constructor_regression(self): + # Tests that the following issue has been fixed: + # https://github.com/hardbyte/python-can/issues/1458 + params = { + "interface": "pcan", + "fd": True, + "f_clock": 80000000, + "nom_brp": 1, + "nom_tseg1": 129, + "nom_tseg2": 30, + "nom_sjw": 1, + "data_brp": 1, + "data_tseg1": 9, + "data_tseg2": 6, + "data_sjw": 1, + "channel": "PCAN_USBBUS1", + } + + can.Bus(**params) + if __name__ == "__main__": unittest.main() From 1046c8c50cb388e6550a93de8f5aeb053f0703e0 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:20:16 +0100 Subject: [PATCH 0967/1235] Tiny type narrowing --- can/interfaces/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 755e8675c..bb206e72b 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -6,7 +6,7 @@ from typing import Dict, Tuple # interface_name => (module, classname) -BACKENDS: Dict[str, Tuple[str, ...]] = { +BACKENDS: Dict[str, Tuple[str, str]] = { "kvaser": ("can.interfaces.kvaser", "KvaserBus"), "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), "serial": ("can.interfaces.serial.serial_can", "SerialBus"), From be29fc97070fa2ba80f5f047aa58bf08fce86aaa Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:28:44 +0100 Subject: [PATCH 0968/1235] Fix "DeprecationWarning: SelectableGroups dict interface is deprecated. Use select." Previously, the change line issued the above deprecation warning. This code fixes it. I also tested it locally. To reproduce the error before the change, simply run `python -W error -c 'import can.interfaces'`. --- can/interfaces/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 755e8675c..8f74a114c 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -35,7 +35,7 @@ if sys.version_info >= (3, 8): from importlib.metadata import entry_points - entries = entry_points().get("can.interface", ()) + entries = entry_points(group="can.interface") BACKENDS.update( {interface.name: tuple(interface.value.split(":")) for interface in entries} ) From 6cb1af1d05561ccce3e7dab03b4fb66472e6883a Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:37:29 +0100 Subject: [PATCH 0969/1235] Add required cast Apparently, this is required for mypy to accept it .... --- can/interfaces/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index bb206e72b..f686f7633 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -6,7 +6,7 @@ from typing import Dict, Tuple # interface_name => (module, classname) -BACKENDS: Dict[str, Tuple[str, str]] = { +BACKENDS: Dict[str, Tuple[str, str]] = cast(Dict[str, Tuple[str, str]], { "kvaser": ("can.interfaces.kvaser", "KvaserBus"), "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), "serial": ("can.interfaces.serial.serial_can", "SerialBus"), @@ -30,7 +30,7 @@ "neousys": ("can.interfaces.neousys", "NeousysBus"), "etas": ("can.interfaces.etas", "EtasBus"), "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), -} +}) if sys.version_info >= (3, 8): from importlib.metadata import entry_points From e2071431ab544a36c19f3c12b35033e87da8b68d Mon Sep 17 00:00:00 2001 From: felixdivo Date: Wed, 21 Dec 2022 15:38:08 +0000 Subject: [PATCH 0970/1235] Format code with black --- can/interfaces/__init__.py | 53 ++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index f686f7633..5162360b9 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -6,31 +6,34 @@ from typing import Dict, Tuple # interface_name => (module, classname) -BACKENDS: Dict[str, Tuple[str, str]] = cast(Dict[str, Tuple[str, str]], { - "kvaser": ("can.interfaces.kvaser", "KvaserBus"), - "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), - "serial": ("can.interfaces.serial.serial_can", "SerialBus"), - "pcan": ("can.interfaces.pcan", "PcanBus"), - "usb2can": ("can.interfaces.usb2can", "Usb2canBus"), - "ixxat": ("can.interfaces.ixxat", "IXXATBus"), - "nican": ("can.interfaces.nican", "NicanBus"), - "iscan": ("can.interfaces.iscan", "IscanBus"), - "virtual": ("can.interfaces.virtual", "VirtualBus"), - "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"), - "neovi": ("can.interfaces.ics_neovi", "NeoViBus"), - "vector": ("can.interfaces.vector", "VectorBus"), - "slcan": ("can.interfaces.slcan", "slcanBus"), - "robotell": ("can.interfaces.robotell", "robotellBus"), - "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), - "systec": ("can.interfaces.systec", "UcanBus"), - "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), - "cantact": ("can.interfaces.cantact", "CantactBus"), - "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), - "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"), - "neousys": ("can.interfaces.neousys", "NeousysBus"), - "etas": ("can.interfaces.etas", "EtasBus"), - "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), -}) +BACKENDS: Dict[str, Tuple[str, str]] = cast( + Dict[str, Tuple[str, str]], + { + "kvaser": ("can.interfaces.kvaser", "KvaserBus"), + "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), + "serial": ("can.interfaces.serial.serial_can", "SerialBus"), + "pcan": ("can.interfaces.pcan", "PcanBus"), + "usb2can": ("can.interfaces.usb2can", "Usb2canBus"), + "ixxat": ("can.interfaces.ixxat", "IXXATBus"), + "nican": ("can.interfaces.nican", "NicanBus"), + "iscan": ("can.interfaces.iscan", "IscanBus"), + "virtual": ("can.interfaces.virtual", "VirtualBus"), + "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"), + "neovi": ("can.interfaces.ics_neovi", "NeoViBus"), + "vector": ("can.interfaces.vector", "VectorBus"), + "slcan": ("can.interfaces.slcan", "slcanBus"), + "robotell": ("can.interfaces.robotell", "robotellBus"), + "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), + "systec": ("can.interfaces.systec", "UcanBus"), + "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), + "cantact": ("can.interfaces.cantact", "CantactBus"), + "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), + "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"), + "neousys": ("can.interfaces.neousys", "NeousysBus"), + "etas": ("can.interfaces.etas", "EtasBus"), + "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), + }, +) if sys.version_info >= (3, 8): from importlib.metadata import entry_points From 05e3283c08d0c2a1426c4a9a04c6c0fecba8eba9 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:41:15 +0100 Subject: [PATCH 0971/1235] Restore compatibility with Python version < 3.10 --- can/interfaces/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 8f74a114c..f3a6e94cf 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -35,7 +35,8 @@ if sys.version_info >= (3, 8): from importlib.metadata import entry_points - entries = entry_points(group="can.interface") + # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note". + entries = entry_points(group="can.interface") if sys.version_info >= (3, 10) else entry_points().get("can.interface", ()) BACKENDS.update( {interface.name: tuple(interface.value.split(":")) for interface in entries} ) From 6885ca41d8fcaed8a711f5773fa6d847ec6145f4 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:42:43 +0100 Subject: [PATCH 0972/1235] Add missing import --- can/interfaces/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 5162360b9..edb58b228 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -3,7 +3,7 @@ """ import sys -from typing import Dict, Tuple +from typing import cast, Dict, Tuple # interface_name => (module, classname) BACKENDS: Dict[str, Tuple[str, str]] = cast( From 41d8c0e0d2680e9f018279d0ba87262aa17732ae Mon Sep 17 00:00:00 2001 From: felixdivo Date: Wed, 21 Dec 2022 15:46:01 +0000 Subject: [PATCH 0973/1235] Format code with black --- can/interfaces/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index f3a6e94cf..1f115b5ad 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -36,7 +36,11 @@ from importlib.metadata import entry_points # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note". - entries = entry_points(group="can.interface") if sys.version_info >= (3, 10) else entry_points().get("can.interface", ()) + entries = ( + entry_points(group="can.interface") + if sys.version_info >= (3, 10) + else entry_points().get("can.interface", ()) + ) BACKENDS.update( {interface.name: tuple(interface.value.split(":")) for interface in entries} ) From ab58780b4b776b7738a624a407d52fe08bebb221 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:55:48 +0100 Subject: [PATCH 0974/1235] Try to fix typing --- can/interfaces/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 1f115b5ad..093c21d81 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -3,7 +3,7 @@ """ import sys -from typing import Dict, Tuple +from typing import Dict, Iterable, Tuple # interface_name => (module, classname) BACKENDS: Dict[str, Tuple[str, ...]] = { @@ -33,13 +33,13 @@ } if sys.version_info >= (3, 8): - from importlib.metadata import entry_points + from importlib.metadata import entry_points, EntryPoint # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note". - entries = ( + entries: Iterable[EntryPoint] = ( entry_points(group="can.interface") if sys.version_info >= (3, 10) - else entry_points().get("can.interface", ()) + else entry_points().get("can.interface", []) ) BACKENDS.update( {interface.name: tuple(interface.value.split(":")) for interface in entries} From 41a958d7fad9f6f76cd4e5868d34a5b7322b5487 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:04:41 +0100 Subject: [PATCH 0975/1235] Update __init__.py --- can/interfaces/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index edb58b228..9e03b126c 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -40,7 +40,7 @@ entries = entry_points().get("can.interface", ()) BACKENDS.update( - {interface.name: tuple(interface.value.split(":")) for interface in entries} + {interface.name: (interface.module, interface.attr) for interface in entries} ) else: from pkg_resources import iter_entry_points From 551e1c391bbb5b4a1334bd9edc1807b693ec3dee Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:19:24 +0100 Subject: [PATCH 0976/1235] Try to make mypy happy --- can/interfaces/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 093c21d81..2c6973b12 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -36,7 +36,7 @@ from importlib.metadata import entry_points, EntryPoint # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note". - entries: Iterable[EntryPoint] = ( + entries: Iterable[EntryPoint] = ( # type: ignore entry_points(group="can.interface") if sys.version_info >= (3, 10) else entry_points().get("can.interface", []) From f5df2733791659f2b7acd6b9ca336960e187a69a Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:29:22 +0100 Subject: [PATCH 0977/1235] Update __init__.py --- can/interfaces/__init__.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index eec099294..f9aee6112 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -39,14 +39,15 @@ from importlib.metadata import entry_points, EntryPoint # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note". - entries: Iterable[EntryPoint] = ( # type: ignore - entry_points(group="can.interface") - if sys.version_info >= (3, 10) - else entry_points().get("can.interface", []) - ) - BACKENDS.update( - {interface.name: (interface.module, interface.attr) for interface in entries} - ) + # The second variant causes a deprecation warning on Python >= 3.10. + if sys.version_info >= (3, 10): + BACKENDS.update( + {interface.name: (interface.module, interface.attr) for interface in entry_points(group="can.interface")} + ) + else: + BACKENDS.update( + {interface.name: tuple(interface.value.split(":")) for interface in entry_points().get("can.interface", [])} + ) else: from pkg_resources import iter_entry_points From 66a5d2a1466b060a32ce04909fd6057ad26facfd Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:43:05 +0100 Subject: [PATCH 0978/1235] Add deprecation warning for 'bustype' parameter (#1462) * deprecate bustype parameter * Add comment Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/interface.py | 3 +- doc/bus.rst | 2 +- doc/configuration.rst | 2 +- doc/interfaces/gs_usb.rst | 4 +-- doc/interfaces/seeedstudio.rst | 2 +- doc/interfaces/socketcan.rst | 4 +-- doc/interfaces/socketcand.rst | 2 +- doc/interfaces/udp_multicast.rst | 2 +- doc/interfaces/virtual.rst | 8 ++--- examples/asyncio_demo.py | 2 +- examples/crc.py | 4 +-- examples/cyclic_multiple.py | 4 +-- examples/send_multiple.py | 2 +- test/back2back_test.py | 16 ++++----- test/simplecyclic_test.py | 12 +++---- test/test_cantact.py | 12 +++---- test/test_interface_virtual.py | 4 +-- test/test_kvaser.py | 8 ++--- test/test_message_filtering.py | 2 +- test/test_neousys.py | 4 +-- test/test_pcan.py | 52 +++++++++++++-------------- test/test_robotell.py | 2 +- test/test_slcan.py | 2 +- test/test_systec.py | 18 +++++----- test/test_vector.py | 61 +++++++++++++++++--------------- test/zero_dlc_test.py | 10 +++--- 26 files changed, 122 insertions(+), 122 deletions(-) diff --git a/can/interface.py b/can/interface.py index 527f84d20..76d0dd1a5 100644 --- a/can/interface.py +++ b/can/interface.py @@ -9,7 +9,7 @@ from typing import Any, cast, Iterable, Type, Optional, Union, List from .bus import BusABC -from .util import load_config +from .util import load_config, deprecated_args_alias from .interfaces import BACKENDS from .exceptions import CanInterfaceNotImplementedError from .typechecking import AutoDetectedConfig, Channel @@ -88,6 +88,7 @@ class Bus(BusABC): # pylint: disable=abstract-method """ @staticmethod + @deprecated_args_alias(bustype="interface") # Deprecated since python-can 4.2 def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg cls: Any, channel: Optional[Channel] = None, diff --git a/doc/bus.rst b/doc/bus.rst index 06a740829..51ed0220b 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -69,7 +69,7 @@ Example defining two filters, one to pass 11-bit ID ``0x451``, the other to pass {"can_id": 0x451, "can_mask": 0x7FF, "extended": False}, {"can_id": 0xA0000, "can_mask": 0x1FFFFFFF, "extended": True}, ] - bus = can.interface.Bus(channel="can0", bustype="socketcan", can_filters=filters) + bus = can.interface.Bus(channel="can0", interface="socketcan", can_filters=filters) See :meth:`~can.BusABC.set_filters` for the implementation. diff --git a/doc/configuration.rst b/doc/configuration.rst index d92d6164f..494351350 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -30,7 +30,7 @@ You can also specify the interface and channel for each Bus instance:: import can - bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=500000) + bus = can.interface.Bus(interface='socketcan', channel='vcan0', bitrate=500000) Configuration File diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index af69581be..3a869911c 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -14,7 +14,7 @@ Usage: pass device ``index`` (starting from 0) if using automatic device detecti import can - bus = can.Bus(bustype="gs_usb", channel=dev.product, index=0, bitrate=250000) + bus = can.Bus(interface="gs_usb", channel=dev.product, index=0, bitrate=250000) Alternatively, pass ``bus`` and ``address`` to open a specific device. The parameters can be got by ``pyusb`` as shown below: @@ -25,7 +25,7 @@ Alternatively, pass ``bus`` and ``address`` to open a specific device. The param dev = usb.core.find(idVendor=0x1D50, idProduct=0x606F) bus = can.Bus( - bustype="gs_usb", + interface="gs_usb", channel=dev.product, bus=dev.bus, address=dev.address, diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst index 98ea352c5..ae07b0545 100644 --- a/doc/interfaces/seeedstudio.rst +++ b/doc/interfaces/seeedstudio.rst @@ -31,7 +31,7 @@ Interface A bus example:: - bus = can.interface.Bus(bustype='seeedstudio', channel='/dev/ttyUSB0', bitrate=500000) + bus = can.interface.Bus(interface='seeedstudio', channel='/dev/ttyUSB0', bitrate=500000) diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index b07f4f78e..ddc965678 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -177,12 +177,12 @@ To spam a bus: import time import can - bustype = 'socketcan' + interface = 'socketcan' channel = 'vcan0' def producer(id): """:param id: Spam the bus with messages including the data id.""" - bus = can.Bus(channel=channel, interface=bustype) + bus = can.Bus(channel=channel, interface=interface) for i in range(10): msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], is_extended_id=False) bus.send(msg) diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst index 2f313470c..0214f094a 100644 --- a/doc/interfaces/socketcand.rst +++ b/doc/interfaces/socketcand.rst @@ -18,7 +18,7 @@ daemon running on a remote Raspberry Pi: import can - bus = can.interface.Bus(bustype='socketcand', host="10.0.16.15", port=29536, channel="can0") + bus = can.interface.Bus(interface='socketcand', host="10.0.16.15", port=29536, channel="can0") # loop until Ctrl-C try: diff --git a/doc/interfaces/udp_multicast.rst b/doc/interfaces/udp_multicast.rst index b15354ed5..4f9745615 100644 --- a/doc/interfaces/udp_multicast.rst +++ b/doc/interfaces/udp_multicast.rst @@ -40,7 +40,7 @@ from ``bus_1`` to ``bus_2``: from can.interfaces.udp_multicast import UdpMulticastBus # The bus can be created using the can.Bus wrapper class or using UdpMulticastBus directly - with can.Bus(channel=UdpMulticastBus.DEFAULT_GROUP_IPv6, bustype='udp_multicast') as bus_1, \ + with can.Bus(channel=UdpMulticastBus.DEFAULT_GROUP_IPv6, interface='udp_multicast') as bus_1, \ UdpMulticastBus(channel=UdpMulticastBus.DEFAULT_GROUP_IPv6) as bus_2: # register a callback on the second bus that prints messages to the standard out diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst index 7569ffeb9..bdadcb08d 100644 --- a/doc/interfaces/virtual.rst +++ b/doc/interfaces/virtual.rst @@ -18,8 +18,8 @@ Example import can - bus1 = can.interface.Bus('test', bustype='virtual') - bus2 = can.interface.Bus('test', bustype='virtual') + bus1 = can.interface.Bus('test', interface='virtual') + bus2 = can.interface.Bus('test', interface='virtual') msg1 = can.Message(arbitration_id=0xabcde, data=[1,2,3]) bus1.send(msg1) @@ -34,8 +34,8 @@ Example import can - bus1 = can.interface.Bus('test', bustype='virtual', preserve_timestamps=True) - bus2 = can.interface.Bus('test', bustype='virtual') + bus1 = can.interface.Bus('test', interface='virtual', preserve_timestamps=True) + bus2 = can.interface.Bus('test', interface='virtual') msg1 = can.Message(timestamp=1639740470.051948, arbitration_id=0xabcde, data=[1,2,3]) diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index 0f37d6573..d29f03bc5 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -19,7 +19,7 @@ def print_message(msg: can.Message) -> None: async def main() -> None: """The main function that runs in the loop.""" - with can.Bus( # type: ignore + with can.Bus( interface="virtual", channel="my_channel_0", receive_own_messages=True ) as bus: reader = can.AsyncBufferedReader() diff --git a/examples/crc.py b/examples/crc.py index 18d22681a..fff3dce25 100755 --- a/examples/crc.py +++ b/examples/crc.py @@ -76,9 +76,7 @@ def compute_xbr_checksum(message, counter): for interface, channel in [("socketcan", "vcan0")]: print(f"Carrying out crc test with {interface} interface") - with can.Bus( # type: ignore - interface=interface, channel=channel, bitrate=500000 - ) as BUS: + with can.Bus(interface=interface, channel=channel, bitrate=500000) as BUS: crc_send(BUS) time.sleep(2) diff --git a/examples/cyclic_multiple.py b/examples/cyclic_multiple.py index 64f0862d7..43dc0cd17 100755 --- a/examples/cyclic_multiple.py +++ b/examples/cyclic_multiple.py @@ -133,9 +133,7 @@ def cyclic_multiple_send_modify(bus): for interface, channel in [("socketcan", "vcan0")]: print(f"Carrying out cyclic multiple tests with {interface} interface") - with can.Bus( # type: ignore - interface=interface, channel=channel, bitrate=500000 - ) as BUS: + with can.Bus(interface=interface, channel=channel, bitrate=500000) as BUS: cyclic_multiple_send(BUS) cyclic_multiple_send_modify(BUS) diff --git a/examples/send_multiple.py b/examples/send_multiple.py index 240b3d1cf..fdcaa5b59 100755 --- a/examples/send_multiple.py +++ b/examples/send_multiple.py @@ -19,7 +19,7 @@ def producer(thread_id: int, message_count: int = 16) -> None: # this uses the default configuration (for example from environment variables, or a # config file) see https://python-can.readthedocs.io/en/stable/configuration.html - with can.Bus() as bus: # type: ignore + with can.Bus() as bus: for i in range(message_count): msg = can.Message( arbitration_id=0x0CF02200 + thread_id, diff --git a/test/back2back_test.py b/test/back2back_test.py index ab4d57dc1..54d619878 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -43,14 +43,14 @@ class Back2BackTestCase(unittest.TestCase): def setUp(self): self.bus1 = can.Bus( channel=self.CHANNEL_1, - bustype=self.INTERFACE_1, + interface=self.INTERFACE_1, bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True, ) self.bus2 = can.Bus( channel=self.CHANNEL_2, - bustype=self.INTERFACE_2, + interface=self.INTERFACE_2, bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True, @@ -166,7 +166,7 @@ def test_message_is_rx_receive_own_messages(self): """The same as `test_message_direction` but testing with `receive_own_messages=True`.""" bus3 = can.Bus( channel=self.CHANNEL_2, - bustype=self.INTERFACE_2, + interface=self.INTERFACE_2, bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True, @@ -188,7 +188,7 @@ def test_unique_message_instances(self): """ bus3 = can.Bus( channel=self.CHANNEL_2, - bustype=self.INTERFACE_2, + interface=self.INTERFACE_2, bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True, @@ -347,8 +347,8 @@ def test_unique_message_instances(self): @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class SocketCanBroadcastChannel(unittest.TestCase): def setUp(self): - self.broadcast_bus = can.Bus(channel="", bustype="socketcan") - self.regular_bus = can.Bus(channel="vcan0", bustype="socketcan") + self.broadcast_bus = can.Bus(channel="", interface="socketcan") + self.regular_bus = can.Bus(channel="vcan0", interface="socketcan") def tearDown(self): self.broadcast_bus.shutdown() @@ -370,14 +370,14 @@ class TestThreadSafeBus(Back2BackTestCase): def setUp(self): self.bus1 = can.ThreadSafeBus( channel=self.CHANNEL_1, - bustype=self.INTERFACE_1, + interface=self.INTERFACE_1, bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True, ) self.bus2 = can.ThreadSafeBus( channel=self.CHANNEL_2, - bustype=self.INTERFACE_2, + interface=self.INTERFACE_2, bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True, diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 4b9ded43f..639694bfa 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -31,8 +31,8 @@ def test_cycle_time(self): is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] ) - with can.interface.Bus(bustype="virtual") as bus1: - with can.interface.Bus(bustype="virtual") as bus2: + with can.interface.Bus(interface="virtual") as bus1: + with can.interface.Bus(interface="virtual") as bus2: # disabling the garbage collector makes the time readings more reliable gc.disable() @@ -67,7 +67,7 @@ def test_cycle_time(self): self.assertMessageEqual(msg, last_msg) def test_removing_bus_tasks(self): - bus = can.interface.Bus(bustype="virtual") + bus = can.interface.Bus(interface="virtual") tasks = [] for task_i in range(10): msg = can.Message( @@ -90,7 +90,7 @@ def test_removing_bus_tasks(self): bus.shutdown() def test_managed_tasks(self): - bus = can.interface.Bus(bustype="virtual", receive_own_messages=True) + bus = can.interface.Bus(interface="virtual", receive_own_messages=True) tasks = [] for task_i in range(3): msg = can.Message( @@ -120,7 +120,7 @@ def test_managed_tasks(self): bus.shutdown() def test_stopping_perodic_tasks(self): - bus = can.interface.Bus(bustype="virtual") + bus = can.interface.Bus(interface="virtual") tasks = [] for task_i in range(10): msg = can.Message( @@ -153,7 +153,7 @@ def test_stopping_perodic_tasks(self): @unittest.skipIf(IS_CI, "fails randomly when run on CI server") def test_thread_based_cyclic_send_task(self): - bus = can.ThreadSafeBus(bustype="virtual") + bus = can.ThreadSafeBus(interface="virtual") msg = can.Message( is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] ) diff --git a/test/test_cantact.py b/test/test_cantact.py index e361ad1ad..4383fab37 100644 --- a/test/test_cantact.py +++ b/test/test_cantact.py @@ -12,7 +12,7 @@ class CantactTest(unittest.TestCase): def test_bus_creation(self): - bus = can.Bus(channel=0, bustype="cantact", _testing=True) + bus = can.Bus(channel=0, interface="cantact", _testing=True) self.assertIsInstance(bus, cantact.CantactBus) cantact.MockInterface.set_bitrate.assert_called() cantact.MockInterface.set_bit_timing.assert_not_called() @@ -24,7 +24,7 @@ def test_bus_creation_bittiming(self): cantact.MockInterface.set_bitrate.reset_mock() bt = can.BitTiming(tseg1=13, tseg2=2, brp=6, sjw=1) - bus = can.Bus(channel=0, bustype="cantact", bit_timing=bt, _testing=True) + bus = can.Bus(channel=0, interface="cantact", bit_timing=bt, _testing=True) self.assertIsInstance(bus, cantact.CantactBus) cantact.MockInterface.set_bitrate.assert_not_called() cantact.MockInterface.set_bit_timing.assert_called() @@ -33,7 +33,7 @@ def test_bus_creation_bittiming(self): cantact.MockInterface.start.assert_called() def test_transmit(self): - bus = can.Bus(channel=0, bustype="cantact", _testing=True) + bus = can.Bus(channel=0, interface="cantact", _testing=True) msg = can.Message( arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True ) @@ -41,18 +41,18 @@ def test_transmit(self): cantact.MockInterface.send.assert_called() def test_recv(self): - bus = can.Bus(channel=0, bustype="cantact", _testing=True) + bus = can.Bus(channel=0, interface="cantact", _testing=True) frame = bus.recv(timeout=0.5) cantact.MockInterface.recv.assert_called() self.assertIsInstance(frame, can.Message) def test_recv_timeout(self): - bus = can.Bus(channel=0, bustype="cantact", _testing=True) + bus = can.Bus(channel=0, interface="cantact", _testing=True) frame = bus.recv(timeout=0.0) cantact.MockInterface.recv.assert_called() self.assertIsNone(frame) def test_shutdown(self): - bus = can.Bus(channel=0, bustype="cantact", _testing=True) + bus = can.Bus(channel=0, interface="cantact", _testing=True) bus.shutdown() cantact.MockInterface.stop.assert_called() diff --git a/test/test_interface_virtual.py b/test/test_interface_virtual.py index 94833fdcb..c1d842180 100644 --- a/test/test_interface_virtual.py +++ b/test/test_interface_virtual.py @@ -13,8 +13,8 @@ class TestMessageFiltering(unittest.TestCase): def setUp(self): - self.node1 = Bus("test", bustype="virtual", preserve_timestamps=True) - self.node2 = Bus("test", bustype="virtual") + self.node1 = Bus("test", interface="virtual", preserve_timestamps=True) + self.node2 = Bus("test", interface="virtual") def tearDown(self): self.node1.shutdown() diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 0efdfd643..fda8b8316 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -37,7 +37,7 @@ def setUp(self): self.msg = {} self.msg_in_cue = None - self.bus = can.Bus(channel=0, bustype="kvaser") + self.bus = can.Bus(channel=0, interface="kvaser") def tearDown(self): if self.bus: @@ -149,7 +149,7 @@ def test_available_configs(self): def test_canfd_default_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() - can.Bus(channel=0, bustype="kvaser", fd=True) + can.Bus(channel=0, interface="kvaser", fd=True) canlib.canSetBusParams.assert_called_once_with( 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 ) @@ -161,7 +161,7 @@ def test_canfd_nondefault_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() data_bitrate = 2000000 - can.Bus(channel=0, bustype="kvaser", fd=True, data_bitrate=data_bitrate) + can.Bus(channel=0, interface="kvaser", fd=True, data_bitrate=data_bitrate) bitrate_constant = canlib.BITRATE_FD[data_bitrate] canlib.canSetBusParams.assert_called_once_with( 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 @@ -172,7 +172,7 @@ def test_canfd_custom_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() data_bitrate = 123456 - can.Bus(channel=0, bustype="kvaser", fd=True, data_bitrate=data_bitrate) + can.Bus(channel=0, interface="kvaser", fd=True, data_bitrate=data_bitrate) canlib.canSetBusParams.assert_called_once_with( 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 ) diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index addea13fd..e6fe16d46 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -21,7 +21,7 @@ class TestMessageFiltering(unittest.TestCase): def setUp(self): - self.bus = Bus(bustype="virtual", channel="testy") + self.bus = Bus(interface="virtual", channel="testy") def tearDown(self): self.bus.shutdown() diff --git a/test/test_neousys.py b/test/test_neousys.py index f61c37655..26a220048 100644 --- a/test/test_neousys.py +++ b/test/test_neousys.py @@ -33,7 +33,7 @@ def setUp(self) -> None: can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Start = Mock(return_value=1) can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Send = Mock(return_value=1) can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Stop = Mock(return_value=1) - self.bus = can.Bus(channel=0, bustype="neousys") + self.bus = can.Bus(channel=0, interface="neousys") def tearDown(self) -> None: if self.bus: @@ -66,7 +66,7 @@ def test_bus_creation(self) -> None: ) def test_bus_creation_bitrate(self) -> None: - self.bus = can.Bus(channel=0, bustype="neousys", bitrate=200000) + self.bus = can.Bus(channel=0, interface="neousys", bitrate=200000) self.assertIsInstance(self.bus, neousys.NeousysBus) CAN_Start_args = ( can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup.call_args[0] diff --git a/test/test_pcan.py b/test/test_pcan.py index ab03bf0a1..7e8e27cf6 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -49,7 +49,7 @@ def _mockGetValue(self, channel, parameter): ) def test_bus_creation(self) -> None: - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.assertIsInstance(self.bus, PcanBus) self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_called_once() @@ -57,10 +57,10 @@ def test_bus_creation(self) -> None: def test_bus_creation_state_error(self) -> None: with self.assertRaises(ValueError): - can.Bus(bustype="pcan", state=BusState.ERROR) + can.Bus(interface="pcan", state=BusState.ERROR) def test_bus_creation_fd(self) -> None: - self.bus = can.Bus(bustype="pcan", fd=True) + self.bus = can.Bus(interface="pcan", fd=True) self.assertIsInstance(self.bus, PcanBus) self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_not_called() @@ -69,7 +69,7 @@ def test_bus_creation_fd(self) -> None: def test_api_version_low(self) -> None: self.PCAN_API_VERSION_SIM = "1.0" with self.assertLogs("can.pcan", level="WARNING") as cm: - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") found_version_warning = False for i in cm.output: if "version" in i and "pcan" in i: @@ -82,7 +82,7 @@ def test_api_version_low(self) -> None: def test_api_version_read_fail(self) -> None: self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_ILLOPERATION, None)) with self.assertRaises(CanInitializationError): - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") @parameterized.expand( [ @@ -98,7 +98,7 @@ def test_api_version_read_fail(self) -> None: ) def test_get_formatted_error(self, name, status1, status2, expected_result: str): with self.subTest(name): - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.mock_pcan.GetErrorText = Mock( side_effect=[ (status1, expected_result.encode("utf-8", errors="replace")), @@ -111,7 +111,7 @@ def test_get_formatted_error(self, name, status1, status2, expected_result: str) self.assertEqual(complete_text, expected_result) def test_status(self) -> None: - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.bus.status() self.mock_pcan.GetStatus.assert_called_once_with(PCAN_USBBUS1) @@ -121,7 +121,7 @@ def test_status(self) -> None: def test_status_is_ok(self, name, status, expected_result) -> None: with self.subTest(name): self.mock_pcan.GetStatus = Mock(return_value=status) - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.assertEqual(self.bus.status_is_ok(), expected_result) self.mock_pcan.GetStatus.assert_called_once_with(PCAN_USBBUS1) @@ -131,7 +131,7 @@ def test_status_is_ok(self, name, status, expected_result) -> None: def test_reset(self, name, status, expected_result) -> None: with self.subTest(name): self.mock_pcan.Reset = Mock(return_value=status) - self.bus = can.Bus(bustype="pcan", fd=True) + self.bus = can.Bus(interface="pcan", fd=True) self.assertEqual(self.bus.reset(), expected_result) self.mock_pcan.Reset.assert_called_once_with(PCAN_USBBUS1) @@ -140,7 +140,7 @@ def test_reset(self, name, status, expected_result) -> None: ) def test_get_device_number(self, name, status, expected_result) -> None: with self.subTest(name): - self.bus = can.Bus(bustype="pcan", fd=True) + self.bus = can.Bus(interface="pcan", fd=True) # Mock GetValue after creation of bus to use first mock of # GetValue in constructor self.mock_pcan.GetValue = Mock(return_value=(status, 1)) @@ -155,7 +155,7 @@ def test_get_device_number(self, name, status, expected_result) -> None: ) def test_set_device_number(self, name, status, expected_result) -> None: with self.subTest(name): - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.mock_pcan.SetValue = Mock(return_value=status) self.assertEqual(self.bus.set_device_number(3), expected_result) # check last SetValue call @@ -170,7 +170,7 @@ def test_recv(self): timestamp = TPCANTimestamp() self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_OK, msg, timestamp)) - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") recv_msg = self.bus.recv() self.assertEqual(recv_msg.arbitration_id, msg.ID) @@ -193,7 +193,7 @@ def test_recv_fd(self): self.mock_pcan.ReadFD = Mock(return_value=(PCAN_ERROR_OK, msg, timestamp)) - self.bus = can.Bus(bustype="pcan", fd=True) + self.bus = can.Bus(interface="pcan", fd=True) recv_msg = self.bus.recv() self.assertEqual(recv_msg.arbitration_id, msg.ID) @@ -206,12 +206,12 @@ def test_recv_fd(self): @pytest.mark.timeout(3.0) def test_recv_no_message(self): self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_QRCVEMPTY, None, None)) - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.assertEqual(self.bus.recv(timeout=0.5), None) def test_send(self) -> None: self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_OK) - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") msg = can.Message( arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True ) @@ -221,7 +221,7 @@ def test_send(self) -> None: def test_send_fd(self) -> None: self.mock_pcan.WriteFD = Mock(return_value=PCAN_ERROR_OK) - self.bus = can.Bus(bustype="pcan", fd=True) + self.bus = can.Bus(interface="pcan", fd=True) msg = can.Message( arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True ) @@ -269,7 +269,7 @@ def test_send_type(self, name, msg_type, expected_value) -> None: self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_OK) - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") msg = can.Message( arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], @@ -287,7 +287,7 @@ def test_send_type(self, name, msg_type, expected_value) -> None: def test_send_error(self) -> None: self.mock_pcan.Write = Mock(return_value=PCAN_ERROR_BUSHEAVY) - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") msg = can.Message( arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True ) @@ -298,7 +298,7 @@ def test_send_error(self) -> None: @parameterized.expand([("on", True), ("off", False)]) def test_flash(self, name, flash) -> None: with self.subTest(name): - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.bus.flash(flash) call_list = self.mock_pcan.SetValue.call_args_list last_call_args_list = call_list[-1][0] @@ -307,7 +307,7 @@ def test_flash(self, name, flash) -> None: ) def test_shutdown(self) -> None: - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.bus.shutdown() self.mock_pcan.Uninitialize.assert_called_once_with(PCAN_USBBUS1) @@ -319,7 +319,7 @@ def test_shutdown(self) -> None: ) def test_state(self, name, bus_state: BusState, expected_parameter) -> None: with self.subTest(name): - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.bus.state = bus_state call_list = self.mock_pcan.SetValue.call_args_list @@ -339,7 +339,7 @@ def test_detect_available_configs(self) -> None: @parameterized.expand([("valid", PCAN_ERROR_OK, "OK"), ("invalid", 0x00005, None)]) def test_status_string(self, name, status, expected_result) -> None: with self.subTest(name): - self.bus = can.Bus(bustype="pcan") + self.bus = can.Bus(interface="pcan") self.mock_pcan.GetStatus = Mock(return_value=status) self.assertEqual(self.bus.status_string(), expected_result) self.mock_pcan.GetStatus.assert_called() @@ -358,20 +358,20 @@ def get_value_side_effect(handle, param): self.mock_pcan.GetValue = Mock(side_effect=get_value_side_effect) if expected_result == "error": - self.assertRaises(ValueError, can.Bus, bustype="pcan", device_id=dev_id) + self.assertRaises(ValueError, can.Bus, interface="pcan", device_id=dev_id) else: - self.bus = can.Bus(bustype="pcan", device_id=dev_id) + self.bus = can.Bus(interface="pcan", device_id=dev_id) self.assertEqual(expected_result, self.bus.channel_info) def test_bus_creation_auto_reset(self): - self.bus = can.Bus(bustype="pcan", auto_reset=True) + self.bus = can.Bus(interface="pcan", auto_reset=True) self.assertIsInstance(self.bus, PcanBus) self.MockPCANBasic.assert_called_once() def test_auto_reset_init_fault(self): self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_INITIALIZE) with self.assertRaises(CanInitializationError): - self.bus = can.Bus(bustype="pcan", auto_reset=True) + self.bus = can.Bus(interface="pcan", auto_reset=True) def test_peak_fd_bus_constructor_regression(self): # Tests that the following issue has been fixed: diff --git a/test/test_robotell.py b/test/test_robotell.py index 8250b7ada..64f4acaf1 100644 --- a/test/test_robotell.py +++ b/test/test_robotell.py @@ -7,7 +7,7 @@ class robotellTestCase(unittest.TestCase): def setUp(self): # will log timeout messages since we are not feeding ack messages to the serial port at this stage - self.bus = can.Bus("loop://", bustype="robotell") + self.bus = can.Bus("loop://", interface="robotell") self.serial = self.bus.serialPortOrig self.serial.read(self.serial.in_waiting) diff --git a/test/test_slcan.py b/test/test_slcan.py index 1e6282d41..aa97e518b 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -6,7 +6,7 @@ class slcanTestCase(unittest.TestCase): def setUp(self): - self.bus = can.Bus("loop://", bustype="slcan", sleep_after_open=0) + self.bus = can.Bus("loop://", interface="slcan", sleep_after_open=0) self.serial = self.bus.serialPortOrig self.serial.read(self.serial.in_waiting) diff --git a/test/test_systec.py b/test/test_systec.py index 5e8b30dcf..7495f75eb 100644 --- a/test/test_systec.py +++ b/test/test_systec.py @@ -32,7 +32,7 @@ def setUp(self): ucan.UcanWriteCanMsgEx = Mock() ucan.UcanResetCanEx = Mock() ucan._UCAN_INITIALIZED = True # Fake this - self.bus = can.Bus(bustype="systec", channel=0, bitrate=125000) + self.bus = can.Bus(interface="systec", channel=0, bitrate=125000) def test_bus_creation(self): self.assertIsInstance(self.bus, ucanbus.UcanBus) @@ -136,7 +136,7 @@ def test_recv_standard(self, mock_read_can_msg, mock_get_msg_pending): @staticmethod def test_bus_defaults(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype="systec", channel=0) + bus = can.Bus(interface="systec", channel=0) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -155,7 +155,7 @@ def test_bus_defaults(): @staticmethod def test_bus_channel(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype="systec", channel=1) + bus = can.Bus(interface="systec", channel=1) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 1, @@ -174,7 +174,7 @@ def test_bus_channel(): @staticmethod def test_bus_bitrate(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype="systec", channel=0, bitrate=125000) + bus = can.Bus(interface="systec", channel=0, bitrate=125000) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -192,12 +192,12 @@ def test_bus_bitrate(): def test_bus_custom_bitrate(self): with self.assertRaises(ValueError): - can.Bus(bustype="systec", channel=0, bitrate=123456) + can.Bus(interface="systec", channel=0, bitrate=123456) @staticmethod def test_receive_own_messages(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype="systec", channel=0, receive_own_messages=True) + bus = can.Bus(interface="systec", channel=0, receive_own_messages=True) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -216,7 +216,7 @@ def test_receive_own_messages(): @staticmethod def test_bus_passive_state(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype="systec", channel=0, state=can.BusState.PASSIVE) + bus = can.Bus(interface="systec", channel=0, state=can.BusState.PASSIVE) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -235,7 +235,7 @@ def test_bus_passive_state(): @staticmethod def test_rx_buffer_entries(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype="systec", channel=0, rx_buffer_entries=1024) + bus = can.Bus(interface="systec", channel=0, rx_buffer_entries=1024) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, @@ -254,7 +254,7 @@ def test_rx_buffer_entries(): @staticmethod def test_tx_buffer_entries(): ucan.UcanInitCanEx2.reset_mock() - bus = can.Bus(bustype="systec", channel=0, tx_buffer_entries=1024) + bus = can.Bus(interface="systec", channel=0, tx_buffer_entries=1024) ucan.UcanInitCanEx2.assert_called_once_with( bus._ucan._handle, 0, diff --git a/test/test_vector.py b/test/test_vector.py index b0f305821..21125cc18 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -77,7 +77,7 @@ def mock_xldriver() -> None: def test_bus_creation_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus = can.Bus(channel=0, interface="vector", _testing=True) assert isinstance(bus, canlib.VectorBus) can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() @@ -93,7 +93,7 @@ def test_bus_creation_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_bus_creation() -> None: - bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector") assert isinstance(bus, canlib.VectorBus) bus.shutdown() @@ -112,7 +112,7 @@ def test_bus_creation() -> None: def test_bus_creation_bitrate_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", bitrate=200_000, _testing=True) + bus = can.Bus(channel=0, interface="vector", bitrate=200_000, _testing=True) assert isinstance(bus, canlib.VectorBus) can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() @@ -133,7 +133,10 @@ def test_bus_creation_bitrate_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_bus_creation_bitrate() -> None: bus = can.Bus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector", bitrate=200_000 + channel=0, + serial=_find_virtual_can_serial(), + interface="vector", + bitrate=200_000, ) assert isinstance(bus, canlib.VectorBus) @@ -146,7 +149,7 @@ def test_bus_creation_bitrate() -> None: def test_bus_creation_fd_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True) assert isinstance(bus, canlib.VectorBus) can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() @@ -165,7 +168,7 @@ def test_bus_creation_fd_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_bus_creation_fd() -> None: bus = can.Bus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector", fd=True + channel=0, serial=_find_virtual_can_serial(), interface="vector", fd=True ) assert isinstance(bus, canlib.VectorBus) @@ -186,7 +189,7 @@ def test_bus_creation_fd() -> None: def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None: bus = can.Bus( channel=0, - bustype="vector", + interface="vector", fd=True, bitrate=500_000, data_bitrate=2_000_000, @@ -232,7 +235,7 @@ def test_bus_creation_fd_bitrate_timings() -> None: bus = can.Bus( channel=0, serial=_find_virtual_can_serial(), - bustype="vector", + interface="vector", fd=True, bitrate=500_000, data_bitrate=2_000_000, @@ -268,7 +271,7 @@ def test_bus_creation_fd_bitrate_timings() -> None: def test_send_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus = can.Bus(channel=0, interface="vector", _testing=True) msg = can.Message( arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True ) @@ -278,7 +281,7 @@ def test_send_mocked(mock_xldriver) -> None: def test_send_fd_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True) msg = can.Message( arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True ) @@ -289,7 +292,7 @@ def test_send_fd_mocked(mock_xldriver) -> None: def test_receive_mocked(mock_xldriver) -> None: can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive) - bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus = can.Bus(channel=0, interface="vector", _testing=True) bus.recv(timeout=0.05) can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() @@ -297,7 +300,7 @@ def test_receive_mocked(mock_xldriver) -> None: def test_receive_fd_mocked(mock_xldriver) -> None: can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(side_effect=xlCanReceive) - bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True) bus.recv(timeout=0.05) can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() @@ -305,8 +308,8 @@ def test_receive_fd_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_send_and_receive() -> None: - bus1 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") - bus2 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + bus1 = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector") + bus2 = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector") msg_std = can.Message( channel=0, arbitration_id=0xFF, data=list(range(8)), is_extended_id=False @@ -330,10 +333,10 @@ def test_send_and_receive() -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_send_and_receive_fd() -> None: bus1 = can.Bus( - channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" + channel=0, serial=_find_virtual_can_serial(), fd=True, interface="vector" ) bus2 = can.Bus( - channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" + channel=0, serial=_find_virtual_can_serial(), fd=True, interface="vector" ) msg_std = can.Message( @@ -367,7 +370,7 @@ def test_receive_non_msg_event_mocked(mock_xldriver) -> None: can.interfaces.vector.canlib.xldriver.xlReceive = Mock( side_effect=xlReceive_chipstate ) - bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus = can.Bus(channel=0, interface="vector", _testing=True) bus.handle_can_event = Mock() bus.recv(timeout=0.05) can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() @@ -378,7 +381,7 @@ def test_receive_non_msg_event_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_receive_non_msg_event() -> None: bus = canlib.VectorBus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector" + channel=0, serial=_find_virtual_can_serial(), interface="vector" ) bus.handle_can_event = Mock() bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0]) @@ -391,7 +394,7 @@ def test_receive_fd_non_msg_event_mocked(mock_xldriver) -> None: can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( side_effect=xlCanReceive_chipstate ) - bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True) bus.handle_canfd_event = Mock() bus.recv(timeout=0.05) can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() @@ -402,7 +405,7 @@ def test_receive_fd_non_msg_event_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_receive_fd_non_msg_event() -> None: bus = canlib.VectorBus( - channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" + channel=0, serial=_find_virtual_can_serial(), fd=True, interface="vector" ) bus.handle_canfd_event = Mock() bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0]) @@ -412,20 +415,20 @@ def test_receive_fd_non_msg_event() -> None: def test_flush_tx_buffer_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus = can.Bus(channel=0, interface="vector", _testing=True) bus.flush_tx_buffer() can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called() @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_flush_tx_buffer() -> None: - bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector") bus.flush_tx_buffer() bus.shutdown() def test_shutdown_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", _testing=True) + bus = can.Bus(channel=0, interface="vector", _testing=True) bus.shutdown() can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called() @@ -434,7 +437,7 @@ def test_shutdown_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_shutdown() -> None: - bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") + bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector") xl_channel_config = _find_xl_channel_config( serial=_find_virtual_can_serial(), channel=0 @@ -449,7 +452,7 @@ def test_shutdown() -> None: def test_reset_mocked(mock_xldriver) -> None: - bus = canlib.VectorBus(channel=0, bustype="vector", _testing=True) + bus = canlib.VectorBus(channel=0, interface="vector", _testing=True) bus.reset() can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called() @@ -458,7 +461,7 @@ def test_reset_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_reset_mocked() -> None: bus = canlib.VectorBus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector" + channel=0, serial=_find_virtual_can_serial(), interface="vector" ) bus.reset() bus.shutdown() @@ -520,7 +523,7 @@ def test_set_and_get_application_config() -> None: def test_set_timer_mocked(mock_xldriver) -> None: canlib.xldriver.xlSetTimerRate = Mock() - bus = canlib.VectorBus(channel=0, bustype="vector", fd=True, _testing=True) + bus = canlib.VectorBus(channel=0, interface="vector", fd=True, _testing=True) bus.set_timer_rate(timer_rate_ms=1) assert canlib.xldriver.xlSetTimerRate.called @@ -528,7 +531,7 @@ def test_set_timer_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_set_timer() -> None: bus = canlib.VectorBus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector" + channel=0, serial=_find_virtual_can_serial(), interface="vector" ) bus.handle_can_event = Mock() bus.set_timer_rate(timer_rate_ms=1) @@ -546,7 +549,7 @@ def test_called_without_testing_argument() -> None: """This tests if an exception is thrown when we are not running on Windows.""" with pytest.raises(can.CanInterfaceNotImplementedError): # do not set the _testing argument, since it would suppress the exception - can.Bus(channel=0, bustype="vector") + can.Bus(channel=0, interface="vector") def test_vector_error_pickle() -> None: diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py index dd7c0dd49..cd5e7895e 100644 --- a/test/zero_dlc_test.py +++ b/test/zero_dlc_test.py @@ -14,8 +14,8 @@ class ZeroDLCTest(unittest.TestCase): def test_recv_non_zero_dlc(self): - bus_send = can.interface.Bus(bustype="virtual") - bus_recv = can.interface.Bus(bustype="virtual") + bus_send = can.interface.Bus(interface="virtual") + bus_recv = can.interface.Bus(interface="virtual") data = [0, 1, 2, 3, 4, 5, 6, 7] msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data) @@ -26,7 +26,7 @@ def test_recv_non_zero_dlc(self): self.assertTrue(msg_recv) def test_recv_none(self): - bus_recv = can.interface.Bus(bustype="virtual") + bus_recv = can.interface.Bus(interface="virtual") msg_recv = bus_recv.recv(timeout=0) @@ -34,8 +34,8 @@ def test_recv_none(self): self.assertFalse(msg_recv) def test_recv_zero_dlc(self): - bus_send = can.interface.Bus(bustype="virtual") - bus_recv = can.interface.Bus(bustype="virtual") + bus_send = can.interface.Bus(interface="virtual") + bus_recv = can.interface.Bus(interface="virtual") msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=[]) bus_send.send(msg_send) From 11f983aae4b4975bcdbc1a85ad077adf12f68b63 Mon Sep 17 00:00:00 2001 From: felixdivo Date: Wed, 21 Dec 2022 17:00:18 +0000 Subject: [PATCH 0979/1235] Format code with black --- can/interfaces/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index f9aee6112..626a85f8b 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -42,11 +42,17 @@ # The second variant causes a deprecation warning on Python >= 3.10. if sys.version_info >= (3, 10): BACKENDS.update( - {interface.name: (interface.module, interface.attr) for interface in entry_points(group="can.interface")} + { + interface.name: (interface.module, interface.attr) + for interface in entry_points(group="can.interface") + } ) else: BACKENDS.update( - {interface.name: tuple(interface.value.split(":")) for interface in entry_points().get("can.interface", [])} + { + interface.name: tuple(interface.value.split(":")) + for interface in entry_points().get("can.interface", []) + } ) else: from pkg_resources import iter_entry_points From 5a202c9d1d87273f6535a347dd683daa49aa8b22 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 21 Dec 2022 18:27:39 +0100 Subject: [PATCH 0980/1235] Finally fix typing --- can/interfaces/__init__.py | 58 ++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 626a85f8b..24d592abb 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -6,37 +6,34 @@ from typing import cast, Dict, Iterable, Tuple # interface_name => (module, classname) -BACKENDS: Dict[str, Tuple[str, str]] = cast( - Dict[str, Tuple[str, str]], - { - "kvaser": ("can.interfaces.kvaser", "KvaserBus"), - "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), - "serial": ("can.interfaces.serial.serial_can", "SerialBus"), - "pcan": ("can.interfaces.pcan", "PcanBus"), - "usb2can": ("can.interfaces.usb2can", "Usb2canBus"), - "ixxat": ("can.interfaces.ixxat", "IXXATBus"), - "nican": ("can.interfaces.nican", "NicanBus"), - "iscan": ("can.interfaces.iscan", "IscanBus"), - "virtual": ("can.interfaces.virtual", "VirtualBus"), - "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"), - "neovi": ("can.interfaces.ics_neovi", "NeoViBus"), - "vector": ("can.interfaces.vector", "VectorBus"), - "slcan": ("can.interfaces.slcan", "slcanBus"), - "robotell": ("can.interfaces.robotell", "robotellBus"), - "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), - "systec": ("can.interfaces.systec", "UcanBus"), - "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), - "cantact": ("can.interfaces.cantact", "CantactBus"), - "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), - "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"), - "neousys": ("can.interfaces.neousys", "NeousysBus"), - "etas": ("can.interfaces.etas", "EtasBus"), - "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), - }, -) +BACKENDS: Dict[str, Tuple[str, str]] = { + "kvaser": ("can.interfaces.kvaser", "KvaserBus"), + "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), + "serial": ("can.interfaces.serial.serial_can", "SerialBus"), + "pcan": ("can.interfaces.pcan", "PcanBus"), + "usb2can": ("can.interfaces.usb2can", "Usb2canBus"), + "ixxat": ("can.interfaces.ixxat", "IXXATBus"), + "nican": ("can.interfaces.nican", "NicanBus"), + "iscan": ("can.interfaces.iscan", "IscanBus"), + "virtual": ("can.interfaces.virtual", "VirtualBus"), + "udp_multicast": ("can.interfaces.udp_multicast", "UdpMulticastBus"), + "neovi": ("can.interfaces.ics_neovi", "NeoViBus"), + "vector": ("can.interfaces.vector", "VectorBus"), + "slcan": ("can.interfaces.slcan", "slcanBus"), + "robotell": ("can.interfaces.robotell", "robotellBus"), + "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), + "systec": ("can.interfaces.systec", "UcanBus"), + "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), + "cantact": ("can.interfaces.cantact", "CantactBus"), + "gs_usb": ("can.interfaces.gs_usb", "GsUsbBus"), + "nixnet": ("can.interfaces.nixnet", "NiXNETcanBus"), + "neousys": ("can.interfaces.neousys", "NeousysBus"), + "etas": ("can.interfaces.etas", "EtasBus"), + "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), +} if sys.version_info >= (3, 8): - from importlib.metadata import entry_points, EntryPoint + from importlib.metadata import entry_points # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note". # The second variant causes a deprecation warning on Python >= 3.10. @@ -50,7 +47,8 @@ else: BACKENDS.update( { - interface.name: tuple(interface.value.split(":")) + # This cast in wrong if interface.value is formatted badly, but we just fail later + interface.name: cast(Tuple[str, str], tuple(interface.value.split(":"))) for interface in entry_points().get("can.interface", []) } ) From 18b86bab92dd03e1f8d775e9cd5cacdad5fe2be5 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Wed, 21 Dec 2022 18:34:28 +0100 Subject: [PATCH 0981/1235] Update can/interfaces/__init__.py Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 24d592abb..8754666a0 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -48,7 +48,7 @@ BACKENDS.update( { # This cast in wrong if interface.value is formatted badly, but we just fail later - interface.name: cast(Tuple[str, str], tuple(interface.value.split(":"))) + interface.name: cast(Tuple[str, str], tuple(interface.value.split(":", maxsplit=1))) for interface in entry_points().get("can.interface", []) } ) From 057b67770b8b786aff886ee143f3946b6fbc092f Mon Sep 17 00:00:00 2001 From: felixdivo Date: Wed, 21 Dec 2022 17:35:06 +0000 Subject: [PATCH 0982/1235] Format code with black --- can/interfaces/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 8754666a0..5936819e0 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -48,7 +48,9 @@ BACKENDS.update( { # This cast in wrong if interface.value is formatted badly, but we just fail later - interface.name: cast(Tuple[str, str], tuple(interface.value.split(":", maxsplit=1))) + interface.name: cast( + Tuple[str, str], tuple(interface.value.split(":", maxsplit=1)) + ) for interface in entry_points().get("can.interface", []) } ) From e968bcbb506555349839c4e1ed299aa0211450b2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 21 Dec 2022 18:41:09 +0100 Subject: [PATCH 0983/1235] Cleanup --- can/interfaces/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 5936819e0..8c7d016bc 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -3,7 +3,7 @@ """ import sys -from typing import cast, Dict, Iterable, Tuple +from typing import cast, Dict, Tuple # interface_name => (module, classname) BACKENDS: Dict[str, Tuple[str, str]] = { @@ -36,7 +36,6 @@ from importlib.metadata import entry_points # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note". - # The second variant causes a deprecation warning on Python >= 3.10. if sys.version_info >= (3, 10): BACKENDS.update( { @@ -45,6 +44,7 @@ } ) else: + # The entry_points().get(...) causes a deprecation warning on Python >= 3.10. BACKENDS.update( { # This cast in wrong if interface.value is formatted badly, but we just fail later @@ -57,11 +57,10 @@ else: from pkg_resources import iter_entry_points - entries = iter_entry_points("can.interface") BACKENDS.update( { interface.name: (interface.module_name, interface.attrs[0]) - for interface in entries + for interface in iter_entry_points("can.interface") } ) From e50490c8029bda301bdbf54d28c03ed95968a5a2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 21 Dec 2022 18:45:14 +0100 Subject: [PATCH 0984/1235] Cleanup --- can/interfaces/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 8c7d016bc..3065e9bfd 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -47,7 +47,6 @@ # The entry_points().get(...) causes a deprecation warning on Python >= 3.10. BACKENDS.update( { - # This cast in wrong if interface.value is formatted badly, but we just fail later interface.name: cast( Tuple[str, str], tuple(interface.value.split(":", maxsplit=1)) ) From e9252de77f8dceccf9d8249ff86088396ace8d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schwaiger?= Date: Wed, 21 Dec 2022 19:22:58 +0100 Subject: [PATCH 0985/1235] PCAN: Fix detection of lib on Windows on ARM (#1463) Before this commit detection of the PCAN DLL would fail on Windows on ARM, regardless if you used the native Python version, or the x64 version of Python. After this fix detection should work properly as long as the PCAN library for your version of Python is listed first in the `PATH` variable. The default: 1. `C:\Program Files\PEAK-System\PEAK-Drivers 4\APIs\ARM64\` before 2. `C:\Program Files\PEAK-System\PEAK-Drivers 4\APIs\x64\` should work if you use the (native) ARM version of Python. If you reorder these paths, then loading the library works in the x64 version of Python. This commit closes #1461. --- can/interfaces/pcan/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 743fb55ee..171fae96d 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -658,7 +658,7 @@ def __init__(self): # if platform.system() == "Windows": # Loads the API on Windows - self.__m_dllBasic = windll.LoadLibrary("PCANBasic") + self.__m_dllBasic = windll.LoadLibrary(find_library("PCANBasic")) aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) try: aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers") From b77527865bc2ca1da415111d5c4e7d6ae6bf5a5b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 21 Dec 2022 19:50:03 +0100 Subject: [PATCH 0986/1235] fix TypeError (#1466) --- can/interfaces/pcan/basic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 171fae96d..e9fc5029a 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -658,7 +658,8 @@ def __init__(self): # if platform.system() == "Windows": # Loads the API on Windows - self.__m_dllBasic = windll.LoadLibrary(find_library("PCANBasic")) + _dll_path = find_library("PCANBasic") + self.__m_dllBasic = windll.LoadLibrary(_dll_path) if _dll_path else None aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) try: aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers") From 3f314dc6582572446cbc33458a53c8eb47860d0b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 21 Dec 2022 21:47:01 +0100 Subject: [PATCH 0987/1235] Add conda badge and fix GHA badge (#1467) --- README.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index ba32607d4..6e65e505c 100644 --- a/README.rst +++ b/README.rst @@ -1,14 +1,18 @@ python-can ========== -|release| |python_implementation| |downloads| |downloads_monthly| |formatter| +|pypi| |conda| |python_implementation| |downloads| |downloads_monthly| -|docs| |github-actions| |build_travis| |coverage| |mergify| +|docs| |github-actions| |build_travis| |coverage| |mergify| |formatter| -.. |release| image:: https://img.shields.io/pypi/v/python-can.svg +.. |pypi| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ :alt: Latest Version on PyPi +.. |conda| image:: https://img.shields.io/conda/v/conda-forge/python-can + :target: https://github.com/conda-forge/python-can-feedstock + :alt: Latest Version on conda-forge + .. |python_implementation| image:: https://img.shields.io/pypi/implementation/python-can :target: https://pypi.python.org/pypi/python-can/ :alt: Supported Python implementations @@ -29,8 +33,8 @@ python-can :target: https://python-can.readthedocs.io/en/stable/ :alt: Documentation -.. |github-actions| image:: https://github.com/hardbyte/python-can/actions/workflows/build.yml/badge.svg?branch=develop - :target: https://github.com/hardbyte/python-can/actions/workflows/build.yml +.. |github-actions| image:: https://github.com/hardbyte/python-can/actions/workflows/ci.yml/badge.svg + :target: https://github.com/hardbyte/python-can/actions/workflows/ci.yml :alt: Github Actions workflow status .. |build_travis| image:: https://img.shields.io/travis/hardbyte/python-can/develop.svg?label=Travis%20CI From ddeef2e9672065673e55805faf1d4c3bfabb344d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 21 Dec 2022 22:44:26 +0100 Subject: [PATCH 0988/1235] fix indentation --- doc/other-tools.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/other-tools.rst b/doc/other-tools.rst index 56ec21083..eab3c4f43 100644 --- a/doc/other-tools.rst +++ b/doc/other-tools.rst @@ -28,8 +28,8 @@ CAN Message protocols (implemented in Python) for performing UDS over CAN utilising the ISO TP protocol. This module has not been updated for some time. * The `uds`_ module is another tool that implements the UDS protocol, although it does have - extensions for performing UDS over CAN utilising the ISO TP protocol. This module has not - been updated for some time. + extensions for performing UDS over CAN utilising the ISO TP protocol. This module has not + been updated for some time. #. XCP * The `pyxcp`_ module implements the Universal Measurement and Calibration Protocol (XCP). The purpose of XCP is to adjust parameters and acquire current values of internal From 9977c71b5ec143d0883170fc7df2201ae03b4843 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 28 Dec 2022 06:40:20 +0100 Subject: [PATCH 0989/1235] Turn sphinx warnings into errors (#1472) * fix nixnet sphinx warnings * treat sphinx warnings as errors * fix mypy --- .github/workflows/ci.yml | 2 +- can/interfaces/nixnet.py | 90 ++++++++++++++++++++------------------- doc/interfaces/nixnet.rst | 7 +-- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 577ccd97d..8bcc273ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,7 +127,7 @@ jobs: pip install -r doc/doc-requirements.txt - name: Build documentation run: | - python -m sphinx -an doc build + python -m sphinx -Wan doc build - uses: actions/upload-artifact@v3 with: name: sphinx-out diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index 1eba09b31..e304fbc1f 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -9,31 +9,24 @@ """ import logging -import sys -import time -import struct +import os +from types import ModuleType +from typing import Optional from can import BusABC, Message -from ..exceptions import CanInitializationError, CanOperationError - +from ..exceptions import ( + CanInitializationError, + CanOperationError, + CanInterfaceNotImplementedError, +) logger = logging.getLogger(__name__) -if sys.platform == "win32": - try: - from nixnet import ( - session, - types, - constants, - errors, - system, - database, - XnetError, - ) - except ImportError as error: - raise ImportError("NIXNET python module cannot be loaded") from error -else: - raise NotImplementedError("NiXNET only supported on Win32 platforms") +nixnet: Optional[ModuleType] = None +try: + import nixnet # type: ignore +except Exception as exc: + logger.warning("Could not import nixnet: %s", exc) class NiXNETcanBus(BusABC): @@ -68,9 +61,18 @@ def __init__( ``is_error_frame`` set to True and ``arbitration_id`` will identify the error (default True) - :raises can.exceptions.CanInitializationError: + :raises ~can.exceptions.CanInitializationError: If starting communication fails """ + if os.name != "nt" and not kwargs.get("_testing", False): + raise CanInterfaceNotImplementedError( + f"The NI-XNET interface is only supported on Windows, " + f'but you are running "{os.name}"' + ) + + if nixnet is None: + raise CanInterfaceNotImplementedError("The NI-XNET API has not been loaded") + self._rx_queue = [] self.channel = channel self.channel_info = "NI-XNET: " + channel @@ -88,10 +90,10 @@ def __init__( # We need two sessions for this application, one to send frames and another to receive them - self.__session_send = session.FrameOutStreamSession( + self.__session_send = nixnet.session.FrameOutStreamSession( channel, database_name=database_name ) - self.__session_receive = session.FrameInStreamSession( + self.__session_receive = nixnet.session.FrameInStreamSession( channel, database_name=database_name ) @@ -110,15 +112,15 @@ def __init__( self.__session_receive.intf.can_fd_baud_rate = fd_bitrate if can_termination: - self.__session_send.intf.can_term = constants.CanTerm.ON - self.__session_receive.intf.can_term = constants.CanTerm.ON + self.__session_send.intf.can_term = nixnet.constants.CanTerm.ON + self.__session_receive.intf.can_term = nixnet.constants.CanTerm.ON self.__session_receive.queue_size = 512 # Once that all the parameters have been restarted, we start the sessions self.__session_send.start() self.__session_receive.start() - except errors.XnetError as error: + except nixnet.errors.XnetError as error: raise CanInitializationError( f"{error.args[0]} ({error.error_type})", error.error_code ) from None @@ -145,13 +147,15 @@ def _recv_internal(self, timeout): msg = Message( timestamp=can_frame.timestamp / 10000000.0 - 11644473600, channel=self.channel, - is_remote_frame=can_frame.type == constants.FrameType.CAN_REMOTE, - is_error_frame=can_frame.type == constants.FrameType.CAN_BUS_ERROR, + is_remote_frame=can_frame.type == nixnet.constants.FrameType.CAN_REMOTE, + is_error_frame=can_frame.type + == nixnet.constants.FrameType.CAN_BUS_ERROR, is_fd=( - can_frame.type == constants.FrameType.CANFD_DATA - or can_frame.type == constants.FrameType.CANFDBRS_DATA + can_frame.type == nixnet.constants.FrameType.CANFD_DATA + or can_frame.type == nixnet.constants.FrameType.CANFDBRS_DATA ), - bitrate_switch=can_frame.type == constants.FrameType.CANFDBRS_DATA, + bitrate_switch=can_frame.type + == nixnet.constants.FrameType.CANFDBRS_DATA, is_extended_id=can_frame.identifier.extended, # Get identifier from CanIdentifier structure arbitration_id=can_frame.identifier.identifier, @@ -178,29 +182,29 @@ def send(self, msg, timeout=None): It does not wait for message to be ACKed currently. """ if timeout is None: - timeout = constants.TIMEOUT_INFINITE + timeout = nixnet.constants.TIMEOUT_INFINITE if msg.is_remote_frame: - type_message = constants.FrameType.CAN_REMOTE + type_message = nixnet.constants.FrameType.CAN_REMOTE elif msg.is_error_frame: - type_message = constants.FrameType.CAN_BUS_ERROR + type_message = nixnet.constants.FrameType.CAN_BUS_ERROR elif msg.is_fd: if msg.bitrate_switch: - type_message = constants.FrameType.CANFDBRS_DATA + type_message = nixnet.constants.FrameType.CANFDBRS_DATA else: - type_message = constants.FrameType.CANFD_DATA + type_message = nixnet.constants.FrameType.CANFD_DATA else: - type_message = constants.FrameType.CAN_DATA + type_message = nixnet.constants.FrameType.CAN_DATA - can_frame = types.CanFrame( - types.CanIdentifier(msg.arbitration_id, msg.is_extended_id), + can_frame = nixnet.types.CanFrame( + nixnet.types.CanIdentifier(msg.arbitration_id, msg.is_extended_id), type=type_message, payload=msg.data, ) try: self.__session_send.frames.write([can_frame], timeout) - except errors.XnetError as error: + except nixnet.errors.XnetError as error: raise CanOperationError( f"{error.args[0]} ({error.error_type})", error.error_code ) from None @@ -237,7 +241,7 @@ def _detect_available_configs(): configs = [] try: - with system.System() as nixnet_system: + with nixnet.system.System() as nixnet_system: for interface in nixnet_system.intf_refs_can: cahnnel = str(interface) logger.debug( @@ -248,10 +252,10 @@ def _detect_available_configs(): "interface": "nixnet", "channel": cahnnel, "can_term_available": interface.can_term_cap - == constants.CanTermCap.YES, + == nixnet.constants.CanTermCap.YES, } ) - except XnetError as error: + except Exception as error: logger.debug("An error occured while searching for configs: %s", str(error)) return configs diff --git a/doc/interfaces/nixnet.rst b/doc/interfaces/nixnet.rst index 8cf2ee72d..5a17e7e8d 100644 --- a/doc/interfaces/nixnet.rst +++ b/doc/interfaces/nixnet.rst @@ -12,9 +12,10 @@ This interface adds support for NI-XNET CAN controllers by `National Instruments Bus --- -.. autoclass:: can.interfaces.nican.NiXNETcanBus - -.. autoexception:: can.interfaces.nican.NiXNETError +.. autoclass:: can.interfaces.nixnet.NiXNETcanBus + :show-inheritance: + :member-order: bysource + :members: .. _National Instruments: http://www.ni.com/can/ From eb0331d140afd9ee940d57a5a5f324e6da9fbb15 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 31 Dec 2022 19:42:57 +0100 Subject: [PATCH 0990/1235] Update pylint (#1471) * update pylint version * add coverage.lcov to .gitignore * check can/io with pylint * check can/interfaces/socketcan with pylint * address review comments * replace wildcard import * fix TRCWriter line endings Co-authored-by: zariiii9003 --- .github/workflows/ci.yml | 6 +- .gitignore | 1 + .pylintrc | 3 +- can/bus.py | 4 +- can/interfaces/socketcan/socketcan.py | 94 +++++++++-------- can/interfaces/socketcan/utils.py | 3 +- can/io/asc.py | 13 +-- can/io/blf.py | 2 - can/io/canutils.py | 10 +- can/io/csv.py | 2 - can/io/generic.py | 25 +++-- can/io/logger.py | 28 +++-- can/io/player.py | 5 +- can/io/printer.py | 1 - can/io/sqlite.py | 2 - can/io/trc.py | 142 ++++++++++++-------------- can/listener.py | 10 +- can/logconvert.py | 2 +- can/logger.py | 2 +- can/message.py | 4 +- requirements-lint.txt | 2 +- 21 files changed, 170 insertions(+), 191 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8bcc273ab..c6a5c4253 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,10 +91,12 @@ jobs: run: | pylint --rcfile=.pylintrc \ can/**.py \ + can/io \ setup.py \ - doc.conf \ + doc/conf.py \ scripts/**.py \ - examples/**.py + examples/**.py \ + can/interfaces/socketcan format: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 258ca73ea..03775bd7c 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ htmlcov/ .cache nosetests.xml coverage.xml +coverage.lcov *,cover .hypothesis/ test.* diff --git a/.pylintrc b/.pylintrc index a42935e6a..cc4c50d88 100644 --- a/.pylintrc +++ b/.pylintrc @@ -81,7 +81,8 @@ disable=invalid-name, # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member +enable=c-extension-no-member, + useless-suppression, [REPORTS] diff --git a/can/bus.py b/can/bus.py index c0793c5f7..d0192fc2d 100644 --- a/can/bus.py +++ b/can/bus.py @@ -464,7 +464,5 @@ class _SelfRemovingCyclicTask(CyclicSendTaskABC, ABC): Only needed for typing :meth:`Bus._periodic_tasks`. Do not instantiate. """ - def stop( # pylint: disable=arguments-differ - self, remove_task: bool = True - ) -> None: + def stop(self, remove_task: bool = True) -> None: raise NotImplementedError() diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index f0545f7df..74fbe8197 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -21,12 +21,6 @@ log_tx = log.getChild("tx") log_rx = log.getChild("rx") -try: - import fcntl -except ImportError: - log.error("fcntl not available on this platform") - - try: from socket import CMSG_SPACE @@ -44,7 +38,7 @@ LimitedDurationCyclicSendTaskABC, ) from can.typechecking import CanFilters -from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG +from can.interfaces.socketcan import constants from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces @@ -177,9 +171,9 @@ def build_can_frame(msg: Message) -> bytes: can_id = _compose_arbitration_id(msg) flags = 0 if msg.bitrate_switch: - flags |= CANFD_BRS + flags |= constants.CANFD_BRS if msg.error_state_indicator: - flags |= CANFD_ESI + flags |= constants.CANFD_ESI max_len = 64 if msg.is_fd else 8 data = bytes(msg.data).ljust(max_len, b"\x00") return CAN_FRAME_HEADER_STRUCT.pack(can_id, msg.dlc, flags) + data @@ -211,7 +205,7 @@ def build_bcm_header( def build_bcm_tx_delete_header(can_id: int, flags: int) -> bytes: - opcode = CAN_BCM_TX_DELETE + opcode = constants.CAN_BCM_TX_DELETE return build_bcm_header(opcode, flags, 0, 0, 0, 0, 0, can_id, 1) @@ -223,13 +217,13 @@ def build_bcm_transmit_header( msg_flags: int, nframes: int = 1, ) -> bytes: - opcode = CAN_BCM_TX_SETUP + opcode = constants.CAN_BCM_TX_SETUP - flags = msg_flags | SETTIMER | STARTTIMER + flags = msg_flags | constants.SETTIMER | constants.STARTTIMER if initial_period > 0: # Note `TX_COUNTEVT` creates the message TX_EXPIRED when count expires - flags |= TX_COUNTEVT + flags |= constants.TX_COUNTEVT def split_time(value: float) -> Tuple[int, int]: """Given seconds as a float, return whole seconds and microseconds""" @@ -254,12 +248,14 @@ def split_time(value: float) -> Tuple[int, int]: def build_bcm_update_header(can_id: int, msg_flags: int, nframes: int = 1) -> bytes: - return build_bcm_header(CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, nframes) + return build_bcm_header( + constants.CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, nframes + ) def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]: can_id, can_dlc, flags = CAN_FRAME_HEADER_STRUCT.unpack_from(frame) - if len(frame) != CANFD_MTU: + if len(frame) != constants.CANFD_MTU: # Flags not valid in non-FD frames flags = 0 return can_id, can_dlc, flags, frame[8 : 8 + can_dlc] @@ -267,7 +263,7 @@ def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]: def create_bcm_socket(channel: str) -> socket.socket: """create a broadcast manager socket and connect to the given interface""" - s = socket.socket(PF_CAN, socket.SOCK_DGRAM, CAN_BCM) + s = socket.socket(constants.PF_CAN, socket.SOCK_DGRAM, constants.CAN_BCM) s.connect((channel,)) return s @@ -297,13 +293,13 @@ def _compose_arbitration_id(message: Message) -> int: can_id = message.arbitration_id if message.is_extended_id: log.debug("sending an extended id type message") - can_id |= CAN_EFF_FLAG + can_id |= constants.CAN_EFF_FLAG if message.is_remote_frame: log.debug("requesting a remote frame") - can_id |= CAN_RTR_FLAG + can_id |= constants.CAN_RTR_FLAG if message.is_error_frame: log.debug("sending error frame") - can_id |= CAN_ERR_FLAG + can_id |= constants.CAN_ERR_FLAG return can_id @@ -354,7 +350,7 @@ def _tx_setup( ) -> None: # Create a low level packed frame to pass to the kernel body = bytearray() - self.flags = CAN_FD_FRAME if messages[0].is_fd else 0 + self.flags = constants.CAN_FD_FRAME if messages[0].is_fd else 0 if self.duration: count = int(self.duration / self.period) @@ -380,7 +376,7 @@ def _check_bcm_task(self) -> None: # Do a TX_READ on a task ID, and check if we get EINVAL. If so, # then we are referring to a CAN message with an existing ID check_header = build_bcm_header( - opcode=CAN_BCM_TX_READ, + opcode=constants.CAN_BCM_TX_READ, flags=0, count=0, ival1_seconds=0, @@ -391,7 +387,7 @@ def _check_bcm_task(self) -> None: nframes=0, ) log.debug( - f"Reading properties of (cyclic) transmission task id={self.task_id}", + "Reading properties of (cyclic) transmission task id=%d", self.task_id ) try: self.bcm_socket.send(check_header) @@ -495,7 +491,7 @@ def create_socket() -> socket.socket: """Creates a raw CAN socket. The socket will be returned unbound to any interface. """ - sock = socket.socket(PF_CAN, socket.SOCK_RAW, CAN_RAW) + sock = socket.socket(constants.PF_CAN, socket.SOCK_RAW, constants.CAN_RAW) log.info("Created a socket") @@ -534,7 +530,7 @@ def capture_message( # Fetching the Arb ID, DLC and Data try: cf, ancillary_data, msg_flags, addr = sock.recvmsg( - CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE + constants.CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE ) if get_channel: channel = addr[0] if isinstance(addr, tuple) else addr @@ -549,7 +545,7 @@ def capture_message( assert len(ancillary_data) == 1, "only requested a single extra field" cmsg_level, cmsg_type, cmsg_data = ancillary_data[0] assert ( - cmsg_level == socket.SOL_SOCKET and cmsg_type == SO_TIMESTAMPNS + cmsg_level == socket.SOL_SOCKET and cmsg_type == constants.SO_TIMESTAMPNS ), "received control message type that was not requested" # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data) @@ -564,12 +560,12 @@ def capture_message( # #define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */ # #define CAN_RTR_FLAG 0x40000000U /* remote transmission request */ # #define CAN_ERR_FLAG 0x20000000U /* error frame */ - is_extended_frame_format = bool(can_id & CAN_EFF_FLAG) - is_remote_transmission_request = bool(can_id & CAN_RTR_FLAG) - is_error_frame = bool(can_id & CAN_ERR_FLAG) - is_fd = len(cf) == CANFD_MTU - bitrate_switch = bool(flags & CANFD_BRS) - error_state_indicator = bool(flags & CANFD_ESI) + is_extended_frame_format = bool(can_id & constants.CAN_EFF_FLAG) + is_remote_transmission_request = bool(can_id & constants.CAN_RTR_FLAG) + is_error_frame = bool(can_id & constants.CAN_ERR_FLAG) + is_fd = len(cf) == constants.CANFD_MTU + bitrate_switch = bool(flags & constants.CANFD_BRS) + error_state_indicator = bool(flags & constants.CANFD_ESI) # Section 4.7.1: MSG_DONTROUTE: set when the received frame was created on the local host. is_rx = not bool(msg_flags & socket.MSG_DONTROUTE) @@ -625,8 +621,8 @@ def __init__( ) -> None: """Creates a new socketcan bus. - If setting some socket options fails, an error will be printed but no exception will be thrown. - This includes enabling: + If setting some socket options fails, an error will be printed + but no exception will be thrown. This includes enabling: - that own messages should be received, - CAN-FD frames and @@ -656,7 +652,7 @@ def __init__( """ self.socket = create_socket() self.channel = channel - self.channel_info = "socketcan channel '%s'" % channel + self.channel_info = f"socketcan channel '{channel}'" self._bcm_sockets: Dict[str, socket.socket] = {} self._is_filtered = False self._task_id = 0 @@ -665,7 +661,9 @@ def __init__( # set the local_loopback parameter try: self.socket.setsockopt( - SOL_CAN_RAW, CAN_RAW_LOOPBACK, 1 if local_loopback else 0 + constants.SOL_CAN_RAW, + constants.CAN_RAW_LOOPBACK, + 1 if local_loopback else 0, ) except OSError as error: log.error("Could not set local loopback flag(%s)", error) @@ -673,7 +671,9 @@ def __init__( # set the receive_own_messages parameter try: self.socket.setsockopt( - SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 1 if receive_own_messages else 0 + constants.SOL_CAN_RAW, + constants.CAN_RAW_RECV_OWN_MSGS, + 1 if receive_own_messages else 0, ) except OSError as error: log.error("Could not receive own messages (%s)", error) @@ -681,23 +681,27 @@ def __init__( # enable CAN-FD frames if desired if fd: try: - self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FD_FRAMES, 1) + self.socket.setsockopt( + constants.SOL_CAN_RAW, constants.CAN_RAW_FD_FRAMES, 1 + ) except OSError as error: log.error("Could not enable CAN-FD frames (%s)", error) if not ignore_rx_error_frames: # enable error frames try: - self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_ERR_FILTER, 0x1FFFFFFF) + self.socket.setsockopt( + constants.SOL_CAN_RAW, constants.CAN_RAW_ERR_FILTER, 0x1FFFFFFF + ) except OSError as error: log.error("Could not enable error frames (%s)", error) # enable nanosecond resolution timestamping # we can always do this since - # 1) is is guaranteed to be at least as precise as without + # 1) it is guaranteed to be at least as precise as without # 2) it is available since Linux 2.6.22, and CAN support was only added afterward # so this is always supported by the kernel - self.socket.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) + self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1) bind_socket(self.socket, channel) kwargs.update( @@ -830,7 +834,9 @@ def _send_periodic_internal( general the message will be sent at the given rate until at least *duration* seconds. """ - msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages(msgs) + msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages( # pylint: disable=protected-access + msgs + ) msgs_channel = str(msgs[0].channel) if msgs[0].channel else None bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) @@ -850,7 +856,9 @@ def _get_bcm_socket(self, channel: str) -> socket.socket: def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: try: - self.socket.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, pack_filters(filters)) + self.socket.setsockopt( + constants.SOL_CAN_RAW, constants.CAN_RAW_FILTER, pack_filters(filters) + ) except OSError as error: # fall back to "software filtering" (= not in kernel) self._is_filtered = False @@ -899,8 +907,6 @@ def sender(event: threading.Event) -> None: sender_socket.send(build_can_frame(msg)) print("Sender sent a message.") - import threading - e = threading.Event() threading.Thread(target=receiver, args=(e,)).start() threading.Thread(target=sender, args=(e,)).start() diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index ecc870ca4..25f04617f 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -52,7 +52,8 @@ def find_available_interfaces() -> Iterable[str]: command = ["ip", "-o", "link", "list", "up"] output = subprocess.check_output(command, text=True) - except Exception as e: # subprocess.CalledProcessError is too specific + except Exception as e: # pylint: disable=broad-except + # subprocess.CalledProcessError is too specific log.error("failed to fetch opened can devices: %s", e) return [] diff --git a/can/io/asc.py b/can/io/asc.py index 7cefa5b76..eb59c0471 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -2,7 +2,7 @@ Contains handling of ASC logging files. Example .asc files: - - https://bitbucket.org/tobylorenz/vector_asc/src/47556e1a6d32c859224ca62d075e1efcc67fa690/src/Vector/ASC/tests/unittests/data/CAN_Log_Trigger_3_2.asc?at=master&fileviewer=file-view-default + - https://bitbucket.org/tobylorenz/vector_asc/src/master/src/Vector/ASC/tests/unittests/data/ - under `test/data/logfile.asc` """ import re @@ -39,7 +39,6 @@ def __init__( file: Union[StringPathLike, TextIO], base: str = "hex", relative_timestamp: bool = True, - *args: Any, **kwargs: Any, ) -> None: """ @@ -93,7 +92,7 @@ def _extract_header(self) -> None: ) continue - elif base_match: + if base_match: base = base_match.group("base") timestamp_format = base_match.group("timestamp_format") self.base = base @@ -101,15 +100,14 @@ def _extract_header(self) -> None: self.timestamps_format = timestamp_format or "absolute" continue - elif comment_match: + if comment_match: continue - elif events_match: + if events_match: self.internal_events_logged = events_match.group("no_events") is None break - else: - break + break @staticmethod def _datetime_to_timestamp(datetime_string: str) -> float: @@ -354,7 +352,6 @@ def __init__( self, file: Union[StringPathLike, TextIO], channel: int = 1, - *args: Any, **kwargs: Any, ) -> None: """ diff --git a/can/io/blf.py b/can/io/blf.py index 93fa54ca2..8d5ade8c8 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -146,7 +146,6 @@ class BLFReader(MessageReader): def __init__( self, file: Union[StringPathLike, BinaryIO], - *args: Any, **kwargs: Any, ) -> None: """ @@ -375,7 +374,6 @@ def __init__( append: bool = False, channel: int = 1, compression_level: int = -1, - *args: Any, **kwargs: Any, ) -> None: """ diff --git a/can/io/canutils.py b/can/io/canutils.py index e159ecdf4..c57a6ca97 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -37,7 +37,6 @@ class CanutilsLogReader(MessageReader): def __init__( self, file: Union[StringPathLike, TextIO], - *args: Any, **kwargs: Any, ) -> None: """ @@ -137,7 +136,6 @@ def __init__( file: Union[StringPathLike, TextIO], channel: str = "vcan0", append: bool = False, - *args: Any, **kwargs: Any, ): """ @@ -173,11 +171,11 @@ def on_message_received(self, msg): framestr = f"({timestamp:f}) {channel}" if msg.is_error_frame: - framestr += " %08X#" % (CAN_ERR_FLAG | CAN_ERR_BUSERROR) + framestr += f" {CAN_ERR_FLAG | CAN_ERR_BUSERROR:08X}#" elif msg.is_extended_id: - framestr += " %08X#" % (msg.arbitration_id) + framestr += f" {msg.arbitration_id:08X}#" else: - framestr += " %03X#" % (msg.arbitration_id) + framestr += f" {msg.arbitration_id:03X}#" if msg.is_error_frame: eol = "\n" @@ -193,7 +191,7 @@ def on_message_received(self, msg): fd_flags |= CANFD_BRS if msg.error_state_indicator: fd_flags |= CANFD_ESI - framestr += "#%X" % fd_flags + framestr += f"#{fd_flags:X}" framestr += f"{msg.data.hex().upper()}{eol}" self.file.write(framestr) diff --git a/can/io/csv.py b/can/io/csv.py index 2e2f46699..ecfc5de35 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -31,7 +31,6 @@ class CSVReader(MessageReader): def __init__( self, file: Union[StringPathLike, TextIO], - *args: Any, **kwargs: Any, ) -> None: """ @@ -95,7 +94,6 @@ def __init__( self, file: Union[StringPathLike, TextIO], append: bool = False, - *args: Any, **kwargs: Any, ) -> None: """ diff --git a/can/io/generic.py b/can/io/generic.py index d5c7a2057..77bba4501 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,5 +1,5 @@ """Contains generic base classes for file IO.""" - +import locale from abc import ABCMeta from typing import ( Optional, @@ -32,8 +32,7 @@ def __init__( self, file: Optional[can.typechecking.AcceptedIOType], mode: str = "rt", - *args: Any, - **kwargs: Any + **kwargs: Any, ) -> None: """ :param file: a path-like object to open a file, a file-like object @@ -45,11 +44,18 @@ def __init__( # file is None or some file-like object self.file = cast(Optional[can.typechecking.FileLike], file) else: + encoding: Optional[str] = ( + None + if "b" in mode + else kwargs.get("encoding", locale.getpreferredencoding(False)) + ) # pylint: disable=consider-using-with # file is some path-like object self.file = cast( can.typechecking.FileLike, - open(cast(can.typechecking.StringPathLike, file), mode), + open( + cast(can.typechecking.StringPathLike, file), mode, encoding=encoding + ), ) # for multiple inheritance @@ -74,37 +80,30 @@ def stop(self) -> None: self.file.close() -# pylint: disable=abstract-method,too-few-public-methods class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): """The base class for all writers.""" file: Optional[can.typechecking.FileLike] -# pylint: disable=abstract-method,too-few-public-methods class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): """A specialized base class for all writers with file descriptors.""" file: can.typechecking.FileLike def __init__( - self, - file: can.typechecking.AcceptedIOType, - mode: str = "wt", - *args: Any, - **kwargs: Any + self, file: can.typechecking.AcceptedIOType, mode: str = "wt", **kwargs: Any ) -> None: # Not possible with the type signature, but be verbose for user-friendliness if file is None: raise ValueError("The given file cannot be None") - super().__init__(file, mode) + super().__init__(file, mode, **kwargs) def file_size(self) -> int: """Return an estimate of the current file size in bytes.""" return self.file.tell() -# pylint: disable=too-few-public-methods class MessageReader(BaseIOHandler, Iterable[can.Message], metaclass=ABCMeta): """The base class for all readers.""" diff --git a/can/io/logger.py b/can/io/logger.py index a08cf9869..b6ea23380 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -27,7 +27,7 @@ from ..typechecking import StringPathLike, FileLike, AcceptedIOType -class Logger(MessageWriter): # pylint: disable=abstract-method +class Logger(MessageWriter): """ Logs CAN messages to a file. @@ -66,7 +66,7 @@ class Logger(MessageWriter): # pylint: disable=abstract-method @staticmethod def __new__( # type: ignore - cls: Any, filename: Optional[StringPathLike], *args: Any, **kwargs: Any + cls: Any, filename: Optional[StringPathLike], **kwargs: Any ) -> MessageWriter: """ :param filename: the filename/path of the file to write to, @@ -75,7 +75,7 @@ def __new__( # type: ignore :raises ValueError: if the filename's suffix is of an unknown file type """ if filename is None: - return Printer(*args, **kwargs) + return Printer(**kwargs) if not Logger.fetched_plugins: Logger.message_writers.update( @@ -90,19 +90,17 @@ def __new__( # type: ignore file_or_filename: AcceptedIOType = filename if suffix == ".gz": - suffix, file_or_filename = Logger.compress(filename, *args, **kwargs) + suffix, file_or_filename = Logger.compress(filename, **kwargs) try: - return Logger.message_writers[suffix](file_or_filename, *args, **kwargs) + return Logger.message_writers[suffix](file=file_or_filename, **kwargs) except KeyError: raise ValueError( f'No write support for this unknown log format "{suffix}"' ) from None @staticmethod - def compress( - filename: StringPathLike, *args: Any, **kwargs: Any - ) -> Tuple[str, FileLike]: + def compress(filename: StringPathLike, **kwargs: Any) -> Tuple[str, FileLike]: """ Return the suffix and io object of the decompressed file. File will automatically recompress upon close. @@ -154,11 +152,10 @@ class BaseRotatingLogger(Listener, BaseIOHandler, ABC): #: An integer counter to track the number of rollovers. rollover_count: int = 0 - def __init__(self, *args: Any, **kwargs: Any) -> None: + def __init__(self, **kwargs: Any) -> None: Listener.__init__(self) - BaseIOHandler.__init__(self, None) + BaseIOHandler.__init__(self, file=None) - self.writer_args = args self.writer_kwargs = kwargs # Expected to be set by the subclass @@ -184,7 +181,7 @@ def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: if not callable(self.namer): return default_name - return self.namer(default_name) + return self.namer(default_name) # pylint: disable=not-callable def rotate(self, source: StringPathLike, dest: StringPathLike) -> None: """When rotating, rotate the current log. @@ -205,7 +202,7 @@ def rotate(self, source: StringPathLike, dest: StringPathLike) -> None: if os.path.exists(source): os.rename(source, dest) else: - self.rotator(source, dest) + self.rotator(source, dest) # pylint: disable=not-callable def on_message_received(self, msg: Message) -> None: """This method is called to handle the given message. @@ -234,7 +231,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: suffix = "".join(pathlib.Path(filename).suffixes[-2:]).lower() if suffix in self._supported_formats: - logger = Logger(filename, *self.writer_args, **self.writer_kwargs) + logger = Logger(filename=filename, **self.writer_kwargs) if isinstance(logger, FileIOMessageWriter): return logger elif isinstance(logger, Printer) and logger.file is not None: @@ -323,7 +320,6 @@ def __init__( self, base_filename: StringPathLike, max_bytes: int = 0, - *args: Any, **kwargs: Any, ) -> None: """ @@ -334,7 +330,7 @@ def __init__( The size threshold at which a new log file shall be created. If set to 0, no rollover will be performed. """ - super().__init__(*args, **kwargs) + super().__init__(**kwargs) self.base_filename = os.path.abspath(base_filename) self.max_bytes = max_bytes diff --git a/can/io/player.py b/can/io/player.py index 21d1964bb..0e062ecb7 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -65,7 +65,6 @@ class LogReader(MessageReader): def __new__( # type: ignore cls: typing.Any, filename: StringPathLike, - *args: typing.Any, **kwargs: typing.Any, ) -> MessageReader: """ @@ -87,7 +86,7 @@ def __new__( # type: ignore if suffix == ".gz": suffix, file_or_filename = LogReader.decompress(filename) try: - return LogReader.message_readers[suffix](file_or_filename, *args, **kwargs) + return LogReader.message_readers[suffix](file=file_or_filename, **kwargs) except KeyError: raise ValueError( f'No read support for this unknown log format "{suffix}"' @@ -109,7 +108,7 @@ def __iter__(self) -> typing.Generator[Message, None, None]: raise NotImplementedError() -class MessageSync: # pylint: disable=too-few-public-methods +class MessageSync: """ Used to iterate over some given messages in the recorded time. """ diff --git a/can/io/printer.py b/can/io/printer.py index 6a43c63b9..01da12e84 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -29,7 +29,6 @@ def __init__( self, file: Optional[Union[StringPathLike, TextIO]] = None, append: bool = False, - *args: Any, **kwargs: Any ) -> None: """ diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 0a4de85f2..33f5d293f 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -36,7 +36,6 @@ def __init__( self, file: StringPathLike, table_name: str = "messages", - *args: Any, **kwargs: Any, ) -> None: """ @@ -138,7 +137,6 @@ def __init__( self, file: StringPathLike, table_name: str = "messages", - *args: Any, **kwargs: Any, ) -> None: """ diff --git a/can/io/trc.py b/can/io/trc.py index 0a07f01c9..ec08d1af1 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -7,12 +7,12 @@ Version 1.1 will be implemented as it is most commonly used """ # noqa -from typing import Generator, Optional, Union, TextIO from datetime import datetime, timedelta from enum import Enum -from io import TextIOWrapper +import io import os import logging +from typing import Generator, Optional, Union, TextIO, Callable, List from ..message import Message from ..util import channel2int @@ -55,11 +55,14 @@ def __init__( if not self.file: raise ValueError("The given file cannot be None") + self._parse_cols: Callable[[List[str]], Optional[Message]] = lambda x: None + def _extract_header(self): + line = "" for line in self.file: line = line.strip() if line.startswith(";$FILEVERSION"): - logger.debug(f"TRCReader: Found file version '{line}'") + logger.debug("TRCReader: Found file version '%s'", line) try: file_version = line.split("=")[1] if file_version == "1.1": @@ -91,7 +94,7 @@ def _extract_header(self): return line - def _parse_msg_V1_0(self, cols): + def _parse_msg_V1_0(self, cols: List[str]) -> Optional[Message]: arbit_id = cols[2] if arbit_id == "FFFFFFFF": logger.info("TRCReader: Dropping bus info line") @@ -106,7 +109,7 @@ def _parse_msg_V1_0(self, cols): msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) return msg - def _parse_msg_V1_1(self, cols): + def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]: arbit_id = cols[3] msg = Message() @@ -119,7 +122,7 @@ def _parse_msg_V1_1(self, cols): msg.is_rx = cols[2] == "Rx" return msg - def _parse_msg_V2_1(self, cols): + def _parse_msg_V2_1(self, cols: List[str]) -> Optional[Message]: msg = Message() msg.timestamp = float(cols[1]) / 1000 msg.arbitration_id = int(cols[4], 16) @@ -130,29 +133,29 @@ def _parse_msg_V2_1(self, cols): msg.is_rx = cols[5] == "Rx" return msg - def _parse_cols_V1_1(self, cols): + def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]: dtype = cols[2] - if dtype == "Tx" or dtype == "Rx": + if dtype in ("Tx", "Rx"): return self._parse_msg_V1_1(cols) else: - logger.info(f"TRCReader: Unsupported type '{dtype}'") + logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_V2_1(self, cols): + def _parse_cols_V2_1(self, cols: List[str]) -> Optional[Message]: dtype = cols[2] if dtype == "DT": return self._parse_msg_V2_1(cols) else: - logger.info(f"TRCReader: Unsupported type '{dtype}'") + logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_line(self, line): - logger.debug(f"TRCReader: Parse '{line}'") + def _parse_line(self, line: str) -> Optional[Message]: + logger.debug("TRCReader: Parse '%s'", line) try: cols = line.split() return self._parse_cols(cols) except IndexError: - logger.warning(f"TRCReader: Failed to parse message '{line}'") + logger.warning("TRCReader: Failed to parse message '%s'", line) return None def __iter__(self) -> Generator[Message, None, None]: @@ -211,81 +214,66 @@ def __init__( """ super().__init__(file, mode="w") self.channel = channel - if type(file) is str: - self.filepath = os.path.abspath(file) - elif type(file) is TextIOWrapper: - self.filepath = "Unknown" - logger.warning("TRCWriter: Text mode io can result in wrong line endings") - logger.debug( - f"TRCWriter: Text mode io line ending setting: {file.newlines}" - ) + + if isinstance(self.file, io.TextIOWrapper): + self.file.reconfigure(newline="\r\n") else: - self.filepath = "Unknown" + raise TypeError("File must be opened in text mode.") + self.filepath = os.path.abspath(self.file.name) self.header_written = False self.msgnr = 0 self.first_timestamp = None self.file_version = TRCFileVersion.V2_1 + self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 self._format_message = self._format_message_init - def _write_line(self, line: str) -> None: - self.file.write(line + "\r\n") - - def _write_lines(self, lines: list) -> None: - for line in lines: - self._write_line(line) - def _write_header_V1_0(self, start_time: timedelta) -> None: - self._write_line( - ";##########################################################################" - ) - self._write_line(f"; {self.filepath}") - self._write_line(";") - self._write_line("; Generated by python-can TRCWriter") - self._write_line(f"; Start time: {start_time}") - self._write_line("; PCAN-Net: N/A") - self._write_line(";") - self._write_line("; Columns description:") - self._write_line("; ~~~~~~~~~~~~~~~~~~~~~") - self._write_line("; +-current number in actual sample") - self._write_line("; | +time offset of message (ms)") - self._write_line("; | | +ID of message (hex)") - self._write_line("; | | | +data length code") - self._write_line("; | | | | +data bytes (hex) ...") - self._write_line("; | | | | |") - self._write_line(";----+- ---+--- ----+--- + -+ -- -- ...") + lines = [ + ";##########################################################################", + f"; {self.filepath}", + ";", + "; Generated by python-can TRCWriter", + f"; Start time: {start_time}", + "; PCAN-Net: N/A", + ";", + "; Columns description:", + "; ~~~~~~~~~~~~~~~~~~~~~", + "; +-current number in actual sample", + "; | +time offset of message (ms", + "; | | +ID of message (hex", + "; | | | +data length code", + "; | | | | +data bytes (hex ...", + "; | | | | |", + ";----+- ---+--- ----+--- + -+ -- -- ...", + ] + self.file.writelines(line + "\n" for line in lines) def _write_header_V2_1(self, header_time: timedelta, start_time: datetime) -> None: milliseconds = int( (header_time.seconds * 1000) + (header_time.microseconds / 1000) ) - - self._write_line(";$FILEVERSION=2.1") - self._write_line(f";$STARTTIME={header_time.days}.{milliseconds}") - self._write_line(";$COLUMNS=N,O,T,B,I,d,R,L,D") - self._write_line(";") - self._write_line(f"; {self.filepath}") - self._write_line(";") - self._write_line(f"; Start time: {start_time}") - self._write_line("; Generated by python-can TRCWriter") - self._write_line( - ";-------------------------------------------------------------------------------" - ) - self._write_line("; Bus Name Connection Protocol") - self._write_line("; N/A N/A N/A N/A") - self._write_line( - ";-------------------------------------------------------------------------------" - ) - self._write_lines( - [ - "; Message Time Type ID Rx/Tx", - "; Number Offset | Bus [hex] | Reserved", - "; | [ms] | | | | | Data Length Code", - "; | | | | | | | | Data [hex] ...", - "; | | | | | | | | |", - ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --", - ] - ) + lines = [ + ";$FILEVERSION=2.1", + f";$STARTTIME={header_time.days}.{milliseconds}", + ";$COLUMNS=N,O,T,B,I,d,R,L,D", + ";", + f"; {self.filepath}", + ";", + f"; Start time: {start_time}", + "; Generated by python-can TRCWriter", + ";-------------------------------------------------------------------------------", + "; Bus Name Connection Protocol", + "; N/A N/A N/A N/A", + ";-------------------------------------------------------------------------------", + "; Message Time Type ID Rx/Tx", + "; Number Offset | Bus [hex] | Reserved", + "; | [ms] | | | | | Data Length Code", + "; | | | | | | | | Data [hex] ...", + "; | | | | | | | | |", + ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --", + ] + self.file.writelines(line + "\n" for line in lines) def _format_message_by_format(self, msg, channel): if msg.is_extended_id: @@ -316,7 +304,7 @@ def _format_message_init(self, msg, channel): else: raise NotImplementedError("File format is not supported") - return self._format_message(msg, channel) + return self._format_message_by_format(msg, channel) def write_header(self, timestamp: float) -> None: # write start of file header @@ -336,7 +324,7 @@ def log_event(self, message: str, timestamp: float) -> None: if not self.header_written: self.write_header(timestamp) - self._write_line(message) + self.file.write(message + "\n") def on_message_received(self, msg: Message) -> None: if self.first_timestamp is None: diff --git a/can/listener.py b/can/listener.py index 6c9cdf0be..e68d813d1 100644 --- a/can/listener.py +++ b/can/listener.py @@ -7,7 +7,7 @@ import asyncio from abc import ABCMeta, abstractmethod from queue import SimpleQueue, Empty -from typing import Any, AsyncIterator, Awaitable, Optional +from typing import Any, AsyncIterator, Optional from can.message import Message from can.bus import BusABC @@ -126,7 +126,9 @@ def stop(self) -> None: self.is_stopped = True -class AsyncBufferedReader(Listener): # pylint: disable=abstract-method +class AsyncBufferedReader( + Listener, AsyncIterator[Message] +): # pylint: disable=abstract-method """A message buffer for use with :mod:`asyncio`. See :ref:`asyncio` for how to use with :class:`can.Notifier`. @@ -174,5 +176,5 @@ async def get_message(self) -> Message: def __aiter__(self) -> AsyncIterator[Message]: return self - def __anext__(self) -> Awaitable[Message]: - return self.buffer.get() + async def __anext__(self) -> Message: + return await self.buffer.get() diff --git a/can/logconvert.py b/can/logconvert.py index 730e82304..d89155758 100644 --- a/can/logconvert.py +++ b/can/logconvert.py @@ -56,7 +56,7 @@ def main(): with logger: try: - for m in reader: # pylint: disable=not-an-iterable + for m in reader: logger(m) except KeyboardInterrupt: sys.exit(1) diff --git a/can/logger.py b/can/logger.py index f13b78bfc..55e67b27e 100644 --- a/can/logger.py +++ b/can/logger.py @@ -58,7 +58,7 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: def _append_filter_argument( parser: Union[ argparse.ArgumentParser, - argparse._ArgumentGroup, # pylint: disable=protected-access + argparse._ArgumentGroup, ], *args: str, **kwargs: Any, diff --git a/can/message.py b/can/message.py index 8e0c4deee..48933b2da 100644 --- a/can/message.py +++ b/can/message.py @@ -228,9 +228,7 @@ def __deepcopy__(self, memo: dict) -> "Message": error_state_indicator=self.error_state_indicator, ) - def _check( - self, - ) -> None: # pylint: disable=too-many-branches; it's still simple code + def _check(self) -> None: """Checks if the message parameters are valid. Assumes that the attribute types are already correct. diff --git a/requirements-lint.txt b/requirements-lint.txt index 2952103c3..28bcf2aa2 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,4 @@ -pylint==2.12.2 +pylint==2.15.9 black~=22.10.0 mypy==0.991 mypy-extensions==0.4.3 From 7013796ad8341e627ea0f46cada2a82506be4562 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 2 Jan 2023 00:40:55 +0100 Subject: [PATCH 0991/1235] add `ignore_config` parameter to can.Bus --- can/interface.py | 45 +++++++++++++++++++++++++-------------------- test/test_bus.py | 14 ++++++++++++++ 2 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 test/test_bus.py diff --git a/can/interface.py b/can/interface.py index 76d0dd1a5..83ead4021 100644 --- a/can/interface.py +++ b/can/interface.py @@ -8,8 +8,8 @@ import logging from typing import Any, cast, Iterable, Type, Optional, Union, List +from . import util from .bus import BusABC -from .util import load_config, deprecated_args_alias from .interfaces import BACKENDS from .exceptions import CanInterfaceNotImplementedError from .typechecking import AutoDetectedConfig, Channel @@ -61,6 +61,13 @@ class Bus(BusABC): # pylint: disable=abstract-method Instantiates a CAN Bus of the given ``interface``, falls back to reading a configuration file from default locations. + .. note:: + Please note that while the arguments provided to this class take precedence + over any existing values from configuration, it is possible that other parameters + from the configuration may be added to the bus instantiation. + This could potentially have unintended consequences. To prevent this, + you may use the *ignore_config* parameter to ignore any existing configurations. + :param channel: Channel identification. Expected type is backend dependent. Set to ``None`` to let it be resolved automatically from the default @@ -71,8 +78,13 @@ class Bus(BusABC): # pylint: disable=abstract-method Set to ``None`` to let it be resolved automatically from the default :ref:`configuration`. - :param args: - ``interface`` specific positional arguments. + :param context: + Extra 'context', that is passed to config sources. + This can be used to select a section other than 'default' in the configuration file. + + :param ignore_config: + If ``True``, only the given arguments will be used for the bus instantiation. Existing + configuration sources will be ignored. :param kwargs: ``interface`` specific keyword arguments. @@ -88,12 +100,13 @@ class Bus(BusABC): # pylint: disable=abstract-method """ @staticmethod - @deprecated_args_alias(bustype="interface") # Deprecated since python-can 4.2 - def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg + @util.deprecated_args_alias(bustype="interface") # Deprecated since python-can 4.2 + def __new__( # type: ignore cls: Any, channel: Optional[Channel] = None, interface: Optional[str] = None, - *args: Any, + context: Optional[str] = None, + ignore_config: bool = False, **kwargs: Any, ) -> BusABC: # figure out the rest of the configuration; this might raise an error @@ -101,12 +114,9 @@ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg kwargs["interface"] = interface if channel is not None: kwargs["channel"] = channel - if "context" in kwargs: - context = kwargs["context"] - del kwargs["context"] - else: - context = None - kwargs = load_config(config=kwargs, context=context) + + if not ignore_config: + kwargs = util.load_config(config=kwargs, context=context) # resolve the bus class to use for that interface cls = _get_class_for_interface(kwargs["interface"]) @@ -115,17 +125,12 @@ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg del kwargs["interface"] # make sure the bus can handle this config format - if "channel" not in kwargs: - raise ValueError("'channel' argument missing") - else: - channel = kwargs["channel"] - del kwargs["channel"] - + channel = kwargs.pop("channel", channel) if channel is None: # Use the default channel for the backend - bus = cls(*args, **kwargs) + bus = cls(**kwargs) else: - bus = cls(channel, *args, **kwargs) + bus = cls(channel, **kwargs) return cast(BusABC, bus) diff --git a/test/test_bus.py b/test/test_bus.py new file mode 100644 index 000000000..e11d829d3 --- /dev/null +++ b/test/test_bus.py @@ -0,0 +1,14 @@ +from unittest.mock import patch + +import can + + +def test_bus_ignore_config(): + with patch.object( + target=can.util, attribute="load_config", side_effect=can.util.load_config + ): + _ = can.Bus(interface="virtual", ignore_config=True) + assert not can.util.load_config.called + + _ = can.Bus(interface="virtual") + assert can.util.load_config.called From 2f50efdc03960d5247dff5c4be957d26458912bb Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 2 Jan 2023 12:38:17 +0100 Subject: [PATCH 0992/1235] rename context -> config_context --- can/interface.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/can/interface.py b/can/interface.py index 83ead4021..2bc8820f5 100644 --- a/can/interface.py +++ b/can/interface.py @@ -78,7 +78,7 @@ class Bus(BusABC): # pylint: disable=abstract-method Set to ``None`` to let it be resolved automatically from the default :ref:`configuration`. - :param context: + :param config_context: Extra 'context', that is passed to config sources. This can be used to select a section other than 'default' in the configuration file. @@ -100,12 +100,14 @@ class Bus(BusABC): # pylint: disable=abstract-method """ @staticmethod - @util.deprecated_args_alias(bustype="interface") # Deprecated since python-can 4.2 + @util.deprecated_args_alias( # Deprecated since python-can 4.2 + bustype="interface", context="config_context" + ) def __new__( # type: ignore cls: Any, channel: Optional[Channel] = None, interface: Optional[str] = None, - context: Optional[str] = None, + config_context: Optional[str] = None, ignore_config: bool = False, **kwargs: Any, ) -> BusABC: @@ -116,7 +118,7 @@ def __new__( # type: ignore kwargs["channel"] = channel if not ignore_config: - kwargs = util.load_config(config=kwargs, context=context) + kwargs = util.load_config(config=kwargs, context=config_context) # resolve the bus class to use for that interface cls = _get_class_for_interface(kwargs["interface"]) From 1278a0f1accde270b7fdd5d1ff9127456820a794 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 4 Jan 2023 20:27:30 +0100 Subject: [PATCH 0993/1235] Run doctest in CI (#1476) --- .github/workflows/ci.yml | 5 ++++- can/bus.py | 10 ++++++---- can/exceptions.py | 24 +++++++++++++++--------- can/interfaces/kvaser/canlib.py | 15 ++++++++++++--- doc/interfaces/ixxat.rst | 19 ++++++++++++++----- doc/message.rst | 20 ++++++++++---------- 6 files changed, 61 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6a5c4253..c610c6a76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,7 +129,10 @@ jobs: pip install -r doc/doc-requirements.txt - name: Build documentation run: | - python -m sphinx -Wan doc build + python -m sphinx -Wan --keep-going doc build + - name: Run doctest + run: | + python -m sphinx -b doctest -W --keep-going doc build - uses: actions/upload-artifact@v3 with: name: sphinx-out diff --git a/can/bus.py b/can/bus.py index d0192fc2d..f29b8ea6e 100644 --- a/can/bus.py +++ b/can/bus.py @@ -314,8 +314,10 @@ def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None: def __iter__(self) -> Iterator[Message]: """Allow iteration on messages as they are received. - >>> for msg in bus: - ... print(msg) + .. code-block:: python + + for msg in bus: + print(msg) :yields: @@ -352,9 +354,9 @@ def set_filters( :param filters: A iterable of dictionaries each containing a "can_id", - a "can_mask", and an optional "extended" key. + a "can_mask", and an optional "extended" key:: - >>> [{"can_id": 0x11, "can_mask": 0x21, "extended": False}] + [{"can_id": 0x11, "can_mask": 0x21, "extended": False}] A filter matches, when `` & can_mask == can_id & can_mask``. diff --git a/can/exceptions.py b/can/exceptions.py index 7496c6c0e..57130082a 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -32,15 +32,21 @@ class CanError(Exception): If specified, the error code is automatically appended to the message: - >>> # With an error code (it also works with a specific error): - >>> error = CanOperationError(message="Failed to do the thing", error_code=42) - >>> str(error) - 'Failed to do the thing [Error Code 42]' - >>> - >>> # Missing the error code: - >>> plain_error = CanError(message="Something went wrong ...") - >>> str(plain_error) - 'Something went wrong ...' + .. testsetup:: canerror + + from can import CanError, CanOperationError + + .. doctest:: canerror + + >>> # With an error code (it also works with a specific error): + >>> error = CanOperationError(message="Failed to do the thing", error_code=42) + >>> str(error) + 'Failed to do the thing [Error Code 42]' + >>> + >>> # Missing the error code: + >>> plain_error = CanError(message="Something went wrong ...") + >>> str(plain_error) + 'Something went wrong ...' :param error_code: An optional error code to narrow down the cause of the fault diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index a8bb7bac7..2bbf8f0bf 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -662,9 +662,18 @@ def get_stats(self) -> structures.BusStatistics: Use like so: - >>> stats = bus.get_stats() - >>> print(stats) - std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0 + .. testsetup:: kvaser + + from unittest.mock import Mock + from can.interfaces.kvaser.structures import BusStatistics + bus = Mock() + bus.get_stats = Mock(side_effect=lambda: BusStatistics()) + + .. doctest:: kvaser + + >>> stats = bus.get_stats() + >>> print(stats) + std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0 :returns: bus statistics. """ diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 61df70638..f73a01036 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -59,11 +59,20 @@ List available devices In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id. To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids()`` as demonstrated below: - >>> from can.interfaces.ixxat import get_ixxat_hwids - >>> for hwid in get_ixxat_hwids(): - ... print("Found IXXAT with hardware id '%s'." % hwid) - Found IXXAT with hardware id 'HW441489'. - Found IXXAT with hardware id 'HW107422'. + .. testsetup:: ixxat + + from unittest.mock import Mock + import can.interfaces.ixxat + assert hasattr(can.interfaces.ixxat, "get_ixxat_hwids") + can.interfaces.ixxat.get_ixxat_hwids = Mock(side_effect=lambda: ['HW441489', 'HW107422']) + + .. doctest:: ixxat + + >>> from can.interfaces.ixxat import get_ixxat_hwids + >>> for hwid in get_ixxat_hwids(): + ... print("Found IXXAT with hardware id '%s'." % hwid) + Found IXXAT with hardware id 'HW441489'. + Found IXXAT with hardware id 'HW107422'. Bus diff --git a/doc/message.rst b/doc/message.rst index 78ccc0b50..d47473e17 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -15,7 +15,7 @@ Message >>> test.dlc 5 >>> print(test) - Timestamp: 0.000000 ID: 00000000 010 DLC: 5 01 02 03 04 05 + Timestamp: 0.000000 ID: 00000000 X Rx DL: 5 01 02 03 04 05 The :attr:`~can.Message.arbitration_id` field in a CAN message may be either @@ -44,7 +44,7 @@ Message 2\ :sup:`29` - 1 for 29-bit identifiers). >>> print(Message(is_extended_id=False, arbitration_id=100)) - Timestamp: 0.000000 ID: 0064 S DLC: 0 + Timestamp: 0.000000 ID: 0064 S Rx DL: 0 .. attribute:: data @@ -56,7 +56,7 @@ Message >>> example_data = bytearray([1, 2, 3]) >>> print(Message(data=example_data)) - Timestamp: 0.000000 ID: 00000000 X DLC: 3 01 02 03 + Timestamp: 0.000000 ID: 00000000 X Rx DL: 3 01 02 03 A :class:`~can.Message` can also be created with bytes, or lists of ints: @@ -106,9 +106,9 @@ Message Previously this was exposed as `id_type`. >>> print(Message(is_extended_id=False)) - Timestamp: 0.000000 ID: 0000 S DLC: 0 + Timestamp: 0.000000 ID: 0000 S Rx DL: 0 >>> print(Message(is_extended_id=True)) - Timestamp: 0.000000 ID: 00000000 X DLC: 0 + Timestamp: 0.000000 ID: 00000000 X Rx DL: 0 .. note:: @@ -124,7 +124,7 @@ Message This boolean parameter indicates if the message is an error frame or not. >>> print(Message(is_error_frame=True)) - Timestamp: 0.000000 ID: 00000000 X E DLC: 0 + Timestamp: 0.000000 ID: 00000000 X Rx E DL: 0 .. attribute:: is_remote_frame @@ -135,7 +135,7 @@ Message modifies the bit in the CAN message's flags field indicating this. >>> print(Message(is_remote_frame=True)) - Timestamp: 0.000000 ID: 00000000 X R DLC: 0 + Timestamp: 0.000000 ID: 00000000 X Rx R DL: 0 .. attribute:: is_fd @@ -174,17 +174,17 @@ Message >>> from can import Message >>> test = Message() >>> print(test) - Timestamp: 0.000000 ID: 00000000 X DLC: 0 + Timestamp: 0.000000 ID: 00000000 X Rx DL: 0 >>> test2 = Message(data=[1, 2, 3, 4, 5]) >>> print(test2) - Timestamp: 0.000000 ID: 00000000 X DLC: 5 01 02 03 04 05 + Timestamp: 0.000000 ID: 00000000 X Rx DL: 5 01 02 03 04 05 The fields in the printed message are (in order): - timestamp, - arbitration ID, - flags, - - dlc, + - data length (DL), - and data. From 5e5b950228302c21529ca1bb6f234f9bd7e0a372 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 5 Jan 2023 23:36:03 +0100 Subject: [PATCH 0994/1235] add deprecation period to deprecated_args_alias (#1477) --- can/interface.py | 7 +- can/interfaces/ixxat/canlib_vcinpl.py | 2 + can/interfaces/ixxat/canlib_vcinpl2.py | 2 + can/interfaces/vector/canlib.py | 6 +- can/util.py | 53 +++++++++++--- test/test_util.py | 95 +++++++++++++++++++++++--- 6 files changed, 144 insertions(+), 21 deletions(-) diff --git a/can/interface.py b/can/interface.py index 2bc8820f5..04fc84ae9 100644 --- a/can/interface.py +++ b/can/interface.py @@ -100,8 +100,11 @@ class Bus(BusABC): # pylint: disable=abstract-method """ @staticmethod - @util.deprecated_args_alias( # Deprecated since python-can 4.2 - bustype="interface", context="config_context" + @util.deprecated_args_alias( + deprecation_start="4.2.0", + deprecation_end="5.0.0", + bustype="interface", + context="config_context", ) def __new__( # type: ignore cls: Any, diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index d74da2539..8304a6dd7 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -417,6 +417,8 @@ class IXXATBus(BusABC): } @deprecated_args_alias( + deprecation_start="4.0.0", + deprecation_end="5.0.0", UniqueHardwareId="unique_hardware_id", rxFifoSize="rx_fifo_size", txFifoSize="tx_fifo_size", diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 2e3125e9b..b8ed916dc 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -417,6 +417,8 @@ class IXXATBus(BusABC): """ @deprecated_args_alias( + deprecation_start="4.0.0", + deprecation_end="5.0.0", UniqueHardwareId="unique_hardware_id", rxFifoSize="rx_fifo_size", txFifoSize="tx_fifo_size", diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ff72d262a..7c1ffac64 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -75,7 +75,11 @@ class VectorBus(BusABC): tseg2Dbr="tseg2_dbr", ) - @deprecated_args_alias(**deprecated_args) + @deprecated_args_alias( + deprecation_start="4.0.0", + deprecation_end="5.0.0", + **deprecated_args, + ) def __init__( self, channel: Union[int, Sequence[int], str], diff --git a/can/util.py b/can/util.py index aa4a28d15..e4a45f2d5 100644 --- a/can/util.py +++ b/can/util.py @@ -295,26 +295,47 @@ def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]: return None -def deprecated_args_alias(**aliases): +def deprecated_args_alias( # type: ignore + deprecation_start: str, deprecation_end: Optional[str] = None, **aliases +): """Allows to rename/deprecate a function kwarg(s) and optionally have the deprecated kwarg(s) set as alias(es) Example:: - @deprecated_args_alias(oldArg="new_arg", anotherOldArg="another_new_arg") + @deprecated_args_alias("1.2.0", oldArg="new_arg", anotherOldArg="another_new_arg") def library_function(new_arg, another_new_arg): pass - @deprecated_args_alias(oldArg="new_arg", obsoleteOldArg=None) + @deprecated_args_alias( + deprecation_start="1.2.0", + deprecation_end="3.0.0", + oldArg="new_arg", + obsoleteOldArg=None, + ) def library_function(new_arg): pass + :param deprecation_start: + The *python-can* version, that introduced the :class:`DeprecationWarning`. + :param deprecation_end: + The *python-can* version, that marks the end of the deprecation period. + :param aliases: + keyword arguments, that map the deprecated argument names + to the new argument names or ``None``. + """ def deco(f): @functools.wraps(f) def wrapper(*args, **kwargs): - _rename_kwargs(f.__name__, kwargs, aliases) + _rename_kwargs( + func_name=f.__name__, + start=deprecation_start, + end=deprecation_end, + kwargs=kwargs, + aliases=aliases, + ) return f(*args, **kwargs) return wrapper @@ -323,21 +344,35 @@ def wrapper(*args, **kwargs): def _rename_kwargs( - func_name: str, kwargs: Dict[str, str], aliases: Dict[str, str] + func_name: str, + start: str, + end: Optional[str], + kwargs: Dict[str, str], + aliases: Dict[str, str], ) -> None: """Helper function for `deprecated_args_alias`""" for alias, new in aliases.items(): if alias in kwargs: + deprecation_notice = ( + f"The '{alias}' argument is deprecated since python-can v{start}" + ) + if end: + deprecation_notice += ( + f", and scheduled for removal in python-can v{end}" + ) + deprecation_notice += "." + value = kwargs.pop(alias) if new is not None: - warnings.warn(f"{alias} is deprecated; use {new}", DeprecationWarning) + deprecation_notice += f" Use '{new}' instead." + if new in kwargs: raise TypeError( - f"{func_name} received both {alias} (deprecated) and {new}" + f"{func_name} received both '{alias}' (deprecated) and '{new}'." ) kwargs[new] = value - else: - warnings.warn(f"{alias} is deprecated", DeprecationWarning) + + warnings.warn(deprecation_notice, DeprecationWarning) def time_perfcounter_correlation() -> Tuple[float, float]: diff --git a/test/test_util.py b/test/test_util.py index 7048d6151..70941f23f 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -3,17 +3,26 @@ import unittest import warnings -from can.util import _create_bus_config, _rename_kwargs, channel2int +import pytest + +from can.util import ( + _create_bus_config, + _rename_kwargs, + channel2int, + deprecated_args_alias, +) class RenameKwargsTest(unittest.TestCase): expected_kwargs = dict(a=1, b=2, c=3, d=4) - def _test(self, kwargs, aliases): + def _test(self, start: str, end: str, kwargs, aliases): # Test that we do get the DeprecationWarning when called with deprecated kwargs - with self.assertWarnsRegex(DeprecationWarning, "is deprecated"): - _rename_kwargs("unit_test", kwargs, aliases) + with self.assertWarnsRegex( + DeprecationWarning, "is deprecated.*?" + start + ".*?" + end + ): + _rename_kwargs("unit_test", start, end, kwargs, aliases) # Test that the aliases contains the deprecated values and # the obsolete kwargs have been removed @@ -25,30 +34,98 @@ def _test(self, kwargs, aliases): # Cause all warnings to always be triggered. warnings.simplefilter("error", DeprecationWarning) try: - _rename_kwargs("unit_test", kwargs, aliases) + _rename_kwargs("unit_test", start, end, kwargs, aliases) finally: warnings.resetwarnings() def test_rename(self): kwargs = dict(old_a=1, old_b=2, c=3, d=4) aliases = {"old_a": "a", "old_b": "b"} - self._test(kwargs, aliases) + self._test("1.0", "2.0", kwargs, aliases) def test_obsolete(self): kwargs = dict(a=1, b=2, c=3, d=4, z=10) aliases = {"z": None} - self._test(kwargs, aliases) + self._test("1.0", "2.0", kwargs, aliases) def test_rename_and_obsolete(self): kwargs = dict(old_a=1, old_b=2, c=3, d=4, z=10) aliases = {"old_a": "a", "old_b": "b", "z": None} - self._test(kwargs, aliases) + self._test("1.0", "2.0", kwargs, aliases) def test_with_new_and_alias_present(self): kwargs = dict(old_a=1, a=1, b=2, c=3, d=4, z=10) aliases = {"old_a": "a", "old_b": "b", "z": None} with self.assertRaises(TypeError): - self._test(kwargs, aliases) + self._test("1.0", "2.0", kwargs, aliases) + + +class DeprecatedArgsAliasTest(unittest.TestCase): + def test_decorator(self): + @deprecated_args_alias("1.0.0", old_a="a") + def _test_func1(a): + pass + + with pytest.warns(DeprecationWarning) as record: + _test_func1(old_a=1) + assert len(record) == 1 + assert ( + record[0].message.args[0] + == "The 'old_a' argument is deprecated since python-can v1.0.0. Use 'a' instead." + ) + + @deprecated_args_alias("1.6.0", "3.4.0", old_a="a", old_b=None) + def _test_func2(a): + pass + + with pytest.warns(DeprecationWarning) as record: + _test_func2(old_a=1, old_b=2) + assert len(record) == 2 + assert record[0].message.args[0] == ( + "The 'old_a' argument is deprecated since python-can v1.6.0, and scheduled for " + "removal in python-can v3.4.0. Use 'a' instead." + ) + assert record[1].message.args[0] == ( + "The 'old_b' argument is deprecated since python-can v1.6.0, and scheduled for " + "removal in python-can v3.4.0." + ) + + @deprecated_args_alias("1.6.0", "3.4.0", old_a="a") + @deprecated_args_alias("2.0.0", "4.0.0", old_b=None) + def _test_func3(a): + pass + + with pytest.warns(DeprecationWarning) as record: + _test_func3(old_a=1, old_b=2) + assert len(record) == 2 + assert record[0].message.args[0] == ( + "The 'old_a' argument is deprecated since python-can v1.6.0, and scheduled " + "for removal in python-can v3.4.0. Use 'a' instead." + ) + assert record[1].message.args[0] == ( + "The 'old_b' argument is deprecated since python-can v2.0.0, and scheduled " + "for removal in python-can v4.0.0." + ) + + with pytest.warns(DeprecationWarning) as record: + _test_func3(old_a=1) + assert len(record) == 1 + assert record[0].message.args[0] == ( + "The 'old_a' argument is deprecated since python-can v1.6.0, and scheduled " + "for removal in python-can v3.4.0. Use 'a' instead." + ) + + with pytest.warns(DeprecationWarning) as record: + _test_func3(a=1, old_b=2) + assert len(record) == 1 + assert record[0].message.args[0] == ( + "The 'old_b' argument is deprecated since python-can v2.0.0, and scheduled " + "for removal in python-can v4.0.0." + ) + + with warnings.catch_warnings(): + warnings.simplefilter("error") + _test_func3(a=1) class TestBusConfig(unittest.TestCase): From 15b8af00af5869f7ed6b48f3e022576caab169ab Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Sat, 7 Jan 2023 07:01:47 +0100 Subject: [PATCH 0995/1235] Use ip link JSON output for SocketCAN find_available_interfaces (#1478) * Update find_available_interfaces to use ip link JSON output * Add unit test with known JSON output for find_available_interfaces * Add except clause for JSON decoding * Fix pylint complaints --- can/interfaces/socketcan/utils.py | 49 ++++++++++++++-------------- test/test_socketcan_helpers.py | 54 +++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 25f04617f..7a8538135 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -3,15 +3,15 @@ """ import errno +import json import logging import os -import re import struct import subprocess -from typing import cast, Iterable, Optional +from typing import cast, Optional, List -from can.interfaces.socketcan.constants import CAN_EFF_FLAG from can import typechecking +from can.interfaces.socketcan.constants import CAN_EFF_FLAG log = logging.getLogger(__name__) @@ -38,35 +38,36 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes return struct.pack(can_filter_fmt, *filter_data) -_PATTERN_CAN_INTERFACE = re.compile(r"(sl|v|vx)?can\d+") - +def find_available_interfaces() -> List[str]: + """Returns the names of all open can/vcan interfaces -def find_available_interfaces() -> Iterable[str]: - """Returns the names of all open can/vcan interfaces using - the ``ip link list`` command. If the lookup fails, an error + The function calls the ``ip link list`` command. If the lookup fails, an error is logged to the console and an empty list is returned. + + :return: The list of available and active CAN interfaces or an empty list of the command failed """ try: - # adding "type vcan" would exclude physical can devices - command = ["ip", "-o", "link", "list", "up"] - output = subprocess.check_output(command, text=True) - - except Exception as e: # pylint: disable=broad-except + command = ["ip", "-json", "link", "list", "up"] + output_str = subprocess.check_output(command, text=True) + except Exception: # pylint: disable=broad-except # subprocess.CalledProcessError is too specific - log.error("failed to fetch opened can devices: %s", e) + log.exception("failed to fetch opened can devices from ip link") return [] - else: - # log.debug("find_available_interfaces(): output=\n%s", output) - # output contains some lines like "1: vcan42: ..." - # extract the "vcan42" of each line - interfaces = [line.split(": ", 3)[1] for line in output.splitlines()] - log.debug( - "find_available_interfaces(): detected these interfaces (before filtering): %s", - interfaces, - ) - return filter(_PATTERN_CAN_INTERFACE.match, interfaces) + try: + output_json = json.loads(output_str) + except json.JSONDecodeError: + log.exception("Failed to parse ip link JSON output: %s", output_str) + return [] + + log.debug( + "find_available_interfaces(): detected these interfaces (before filtering): %s", + output_json, + ) + + interfaces = [i["ifname"] for i in output_json if i["link_type"] == "can"] + return interfaces def error_code_to_str(code: Optional[int]) -> str: diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index ad53836f2..29ceb11c0 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -4,7 +4,12 @@ Tests helpers in `can.interfaces.socketcan.socketcan_common`. """ +import gzip +from base64 import b64decode import unittest +from unittest import mock + +from subprocess import CalledProcessError from can.interfaces.socketcan.utils import find_available_interfaces, error_code_to_str @@ -26,17 +31,46 @@ def test_error_code_to_str(self): string = error_code_to_str(error_code) self.assertTrue(string) # not None or empty - @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") + @unittest.skipUnless( + TEST_INTERFACE_SOCKETCAN, "socketcan is only available on Linux" + ) def test_find_available_interfaces(self): - result = list(find_available_interfaces()) - self.assertGreaterEqual(len(result), 0) - for entry in result: - self.assertRegex(entry, r"(sl|v|vx)?can\d+") - if TEST_INTERFACE_SOCKETCAN: - self.assertGreaterEqual(len(result), 3) - self.assertIn("vcan0", result) - self.assertIn("vxcan0", result) - self.assertIn("slcan0", result) + result = find_available_interfaces() + + self.assertGreaterEqual(len(result), 3) + self.assertIn("vcan0", result) + self.assertIn("vxcan0", result) + self.assertIn("slcan0", result) + + def test_find_available_interfaces_w_patch(self): + # Contains lo, eth0, wlan0, vcan0, mycustomCan123 + ip_output_gz_b64 = ( + "H4sIAAAAAAAAA+2UzW+CMBjG7/wVhrNL+BC29IboEqNSwzQejDEViiMC5aNsmmX/+wpZTGUwDAcP" + "y5qmh+d5++bN80u7EXpsfZRnsUTf8yMXn0TQk/u8GqEQM1EMiMjpXoAOGZM3F6mUZxAuhoY55UpL" + "fbWoKjO4Hts7pl/kLdc+pDlrrmuaqnNq4vqZU8wSkSTHOeYHIjFOM4poOevKmlpwbfF+4EfHkLil" + "PRo/G6vZkrcPKcnjwnOxh/KA8h49JQGOimAkSaq03NFz/B0PiffIOfIXkeumOCtiEiUJXG++bp8S" + "5Dooo/WVZeFnvxmYUgsM01fpBmQWfDAN256M7SqioQ2NkWm8LKvGnIU3qTN+xylrV/FdaHrJzmFk" + "gkacozuzZMnhtAGkLANFAaoKBgOgaUDXG0F6Hrje7SDVWpDvAYpuIdmJV4dn2cSx9VUuGiFCe25Y" + "fwTi4KmW4ptzG0ULGvYPLN1APSqdMN3/82TRtOeqSbW5hmcnzygJTRTJivofcEvAgrAVvgD8aLkv" + "/AcAAA==" + ) + ip_output = gzip.decompress(b64decode(ip_output_gz_b64)).decode("ascii") + + with mock.patch("subprocess.check_output") as check_output: + check_output.return_value = ip_output + ifs = find_available_interfaces() + + self.assertEqual(["vcan0", "mycustomCan123"], ifs) + + def test_find_available_interfaces_exception(self): + with mock.patch("subprocess.check_output") as check_output: + check_output.return_value = "

Not JSON

" + result = find_available_interfaces() + self.assertEqual([], result) + + check_output.side_effect = Exception("Something went wrong :-/") + result = find_available_interfaces() + self.assertEqual([], result) if __name__ == "__main__": From c4c396b9a2441606a40c21ab0dedb54278e70149 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Wed, 11 Jan 2023 10:33:45 -0500 Subject: [PATCH 0996/1235] Avoid using root logger in usb2can (#1483) Replace the logger used to raise import error in usb2can serial selector --- can/interfaces/usb2can/serial_selector.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index c6b9053d6..22a95ae7c 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -4,10 +4,12 @@ import logging from typing import List +log = logging.getLogger("can.usb2can") + try: import win32com.client except ImportError: - logging.warning("win32com.client module required for usb2can") + log.warning("win32com.client module required for usb2can") raise From fabcdf7d175a320fa63b0f515ac7f5adcd4c184c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 12 Jan 2023 17:55:17 +0100 Subject: [PATCH 0997/1235] Vector: Check sample point instead of tseg & sjw (#1486) * check sample point instead of tseg & sjw * improve error message --- can/interfaces/vector/canlib.py | 310 ++++++++++++++++++++------------ test/test_vector.py | 4 +- 2 files changed, 196 insertions(+), 118 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 7c1ffac64..5c7f1a8ad 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -148,7 +148,8 @@ def __init__( If the bus could not be set up. This may or may not be a :class:`~can.interfaces.vector.VectorInitializationError`. """ - if os.name != "nt" and not kwargs.get("_testing", False): + self.__testing = kwargs.get("_testing", False) + if os.name != "nt" and not self.__testing: raise CanInterfaceNotImplementedError( f"The Vector interface is only supported on Windows, " f'but you are running "{os.name}"' @@ -232,66 +233,20 @@ def __init__( # set CAN settings for channel in self.channels: - if self._has_init_access(channel): - if fd: - self._set_bitrate_canfd( - channel=channel, - bitrate=bitrate, - data_bitrate=data_bitrate, - sjw_abr=sjw_abr, - tseg1_abr=tseg1_abr, - tseg2_abr=tseg2_abr, - sjw_dbr=sjw_dbr, - tseg1_dbr=tseg1_dbr, - tseg2_dbr=tseg2_dbr, - ) - elif bitrate: - self._set_bitrate_can(channel=channel, bitrate=bitrate) - - # Check CAN settings - for channel in self.channels: - if kwargs.get("_testing", False): - # avoid check if xldriver is mocked for testing - break - - bus_params = self._read_bus_params(channel) if fd: - _canfd = bus_params.canfd - if not all( - [ - bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - _canfd.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD, - _canfd.bitrate == bitrate if bitrate else True, - _canfd.sjw_abr == sjw_abr if bitrate else True, - _canfd.tseg1_abr == tseg1_abr if bitrate else True, - _canfd.tseg2_abr == tseg2_abr if bitrate else True, - _canfd.data_bitrate == data_bitrate if data_bitrate else True, - _canfd.sjw_dbr == sjw_dbr if data_bitrate else True, - _canfd.tseg1_dbr == tseg1_dbr if data_bitrate else True, - _canfd.tseg2_dbr == tseg2_dbr if data_bitrate else True, - ] - ): - raise CanInitializationError( - f"The requested CAN FD settings could not be set for channel {channel}. " - f"Another application might have set incompatible settings. " - f"These are the currently active settings: {_canfd._asdict()}" - ) - else: - _can = bus_params.can - if not all( - [ - bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - _can.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20, - _can.bitrate == bitrate if bitrate else True, - ] - ): - raise CanInitializationError( - f"The requested CAN settings could not be set for channel {channel}. " - f"Another application might have set incompatible settings. " - f"These are the currently active settings: {_can._asdict()}" - ) + self._set_bitrate_canfd( + channel=channel, + bitrate=bitrate, + data_bitrate=data_bitrate, + sjw_abr=sjw_abr, + tseg1_abr=tseg1_abr, + tseg2_abr=tseg2_abr, + sjw_dbr=sjw_dbr, + tseg1_dbr=tseg1_dbr, + tseg2_dbr=tseg2_dbr, + ) + elif bitrate: + self._set_bitrate_can(channel=channel, bitrate=bitrate) # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 @@ -422,32 +377,85 @@ def _set_bitrate_can( ) # set parameters if channel has init access - if any(kwargs): - chip_params = xlclass.XLchipParams() - chip_params.bitRate = bitrate - chip_params.sjw = sjw - chip_params.tseg1 = tseg1 - chip_params.tseg2 = tseg2 - chip_params.sam = sam - self.xldriver.xlCanSetChannelParams( - self.port_handle, - self.channel_masks[channel], - chip_params, + if self._has_init_access(channel): + if any(kwargs): + chip_params = xlclass.XLchipParams() + chip_params.bitRate = bitrate + chip_params.sjw = sjw + chip_params.tseg1 = tseg1 + chip_params.tseg2 = tseg2 + chip_params.sam = sam + self.xldriver.xlCanSetChannelParams( + self.port_handle, + self.channel_masks[channel], + chip_params, + ) + LOG.info( + "xlCanSetChannelParams: baudr.=%u, sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", + chip_params.bitRate, + chip_params.sjw, + chip_params.tseg1, + chip_params.tseg2, + ) + else: + self.xldriver.xlCanSetChannelBitrate( + self.port_handle, + self.channel_masks[channel], + bitrate, + ) + LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate) + + if self.__testing: + return + + # Compare requested CAN settings to active settings + bus_params = self._read_bus_params(channel) + settings_acceptable = True + + # check bus type + settings_acceptable &= ( + bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN + ) + + # check CAN operation mode. For CANcaseXL can_op_mode remains 0 + if bus_params.can.can_op_mode != 0: + settings_acceptable &= bool( + bus_params.can.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 ) - LOG.info( - "xlCanSetChannelParams: baudr.=%u, sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", - chip_params.bitRate, - chip_params.sjw, - chip_params.tseg1, - chip_params.tseg2, + + # check bitrate + settings_acceptable &= abs(bus_params.can.bitrate - bitrate) < bitrate / 256 + + # check sample point + if all(kwargs): + requested_sample_point = ( + 100 + * (1 + tseg1) # type: ignore[operator] + / (1 + tseg1 + tseg2) # type: ignore[operator] ) - else: - self.xldriver.xlCanSetChannelBitrate( - self.port_handle, - self.channel_masks[channel], - bitrate, + actual_sample_point = ( + 100 + * (1 + bus_params.can.tseg1) + / (1 + bus_params.can.tseg1 + bus_params.can.tseg2) + ) + settings_acceptable &= ( + abs(actual_sample_point - requested_sample_point) + < 1.0 # 1 percent threshold + ) + + if not settings_acceptable: + active_settings = ", ".join( + [ + f"{key}: {getattr(val, 'name', val)}" # print int or Enum/Flag name + for key, val in bus_params.can._asdict().items() + ] + ) + raise CanInitializationError( + f"The requested CAN settings could not be set for channel {channel}. " + f"Another application might have set incompatible settings. " + f"These are the currently active settings: {active_settings}" ) - LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate) def _set_bitrate_canfd( self, @@ -462,42 +470,112 @@ def _set_bitrate_canfd( tseg2_dbr: int = 3, ) -> None: # set parameters if channel has init access - canfd_conf = xlclass.XLcanFdConf() - if bitrate: - canfd_conf.arbitrationBitRate = int(bitrate) - else: - canfd_conf.arbitrationBitRate = 500_000 - canfd_conf.sjwAbr = int(sjw_abr) - canfd_conf.tseg1Abr = int(tseg1_abr) - canfd_conf.tseg2Abr = int(tseg2_abr) - if data_bitrate: - canfd_conf.dataBitRate = int(data_bitrate) - else: - canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate) - canfd_conf.sjwDbr = int(sjw_dbr) - canfd_conf.tseg1Dbr = int(tseg1_dbr) - canfd_conf.tseg2Dbr = int(tseg2_dbr) - self.xldriver.xlCanFdSetConfiguration( - self.port_handle, self.channel_masks[channel], canfd_conf - ) - LOG.info( - "xlCanFdSetConfiguration.: ABaudr.=%u, DBaudr.=%u", - canfd_conf.arbitrationBitRate, - canfd_conf.dataBitRate, - ) - LOG.info( - "xlCanFdSetConfiguration.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", - canfd_conf.sjwAbr, - canfd_conf.tseg1Abr, - canfd_conf.tseg2Abr, + if self._has_init_access(channel): + canfd_conf = xlclass.XLcanFdConf() + if bitrate: + canfd_conf.arbitrationBitRate = int(bitrate) + else: + canfd_conf.arbitrationBitRate = 500_000 + canfd_conf.sjwAbr = int(sjw_abr) + canfd_conf.tseg1Abr = int(tseg1_abr) + canfd_conf.tseg2Abr = int(tseg2_abr) + if data_bitrate: + canfd_conf.dataBitRate = int(data_bitrate) + else: + canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate) + canfd_conf.sjwDbr = int(sjw_dbr) + canfd_conf.tseg1Dbr = int(tseg1_dbr) + canfd_conf.tseg2Dbr = int(tseg2_dbr) + self.xldriver.xlCanFdSetConfiguration( + self.port_handle, self.channel_masks[channel], canfd_conf + ) + LOG.info( + "xlCanFdSetConfiguration.: ABaudr.=%u, DBaudr.=%u", + canfd_conf.arbitrationBitRate, + canfd_conf.dataBitRate, + ) + LOG.info( + "xlCanFdSetConfiguration.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", + canfd_conf.sjwAbr, + canfd_conf.tseg1Abr, + canfd_conf.tseg2Abr, + ) + LOG.info( + "xlCanFdSetConfiguration.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u", + canfd_conf.sjwDbr, + canfd_conf.tseg1Dbr, + canfd_conf.tseg2Dbr, + ) + + if self.__testing: + return + + # Compare requested CAN settings to active settings + bus_params = self._read_bus_params(channel) + settings_acceptable = True + + # check bus type + settings_acceptable &= ( + bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN ) - LOG.info( - "xlCanFdSetConfiguration.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u", - canfd_conf.sjwDbr, - canfd_conf.tseg1Dbr, - canfd_conf.tseg2Dbr, + + # check CAN operation mode + settings_acceptable &= bool( + bus_params.canfd.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD ) + # check bitrates + if bitrate: + settings_acceptable &= ( + abs(bus_params.canfd.bitrate - bitrate) < bitrate / 256 + ) + if data_bitrate: + settings_acceptable &= ( + abs(bus_params.canfd.data_bitrate - data_bitrate) < data_bitrate / 256 + ) + + # check sample points + if bitrate: + requested_nom_sample_point = ( + 100 * (1 + tseg1_abr) / (1 + tseg1_abr + tseg2_abr) + ) + actual_nom_sample_point = ( + 100 + * (1 + bus_params.canfd.tseg1_abr) + / (1 + bus_params.canfd.tseg1_abr + bus_params.canfd.tseg2_abr) + ) + settings_acceptable &= ( + abs(actual_nom_sample_point - requested_nom_sample_point) + < 1.0 # 1 percent threshold + ) + if data_bitrate: + requested_data_sample_point = ( + 100 * (1 + tseg1_dbr) / (1 + tseg1_dbr + tseg2_dbr) + ) + actual_data_sample_point = ( + 100 + * (1 + bus_params.canfd.tseg1_dbr) + / (1 + bus_params.canfd.tseg1_dbr + bus_params.canfd.tseg2_dbr) + ) + settings_acceptable &= ( + abs(actual_data_sample_point - requested_data_sample_point) + < 1.0 # 1 percent threshold + ) + + if not settings_acceptable: + active_settings = ", ".join( + [ + f"{key}: {getattr(val, 'name', val)}" # print int or Enum/Flag name + for key, val in bus_params.canfd._asdict().items() + ] + ) + raise CanInitializationError( + f"The requested CAN FD settings could not be set for channel {channel}. " + f"Another application might have set incompatible settings. " + f"These are the currently active settings: {active_settings}." + ) + def _apply_filters(self, filters: Optional[CanFilters]) -> None: if filters: # Only up to one filter per ID type allowed diff --git a/test/test_vector.py b/test/test_vector.py index 21125cc18..02c3c336d 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -459,7 +459,7 @@ def test_reset_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_reset_mocked() -> None: +def test_reset() -> None: bus = canlib.VectorBus( channel=0, serial=_find_virtual_can_serial(), interface="vector" ) @@ -696,7 +696,7 @@ def _find_virtual_can_serial() -> int: for i in range(xl_driver_config.channelCount): xl_channel_config: xlclass.XLchannelConfig = xl_driver_config.channel[i] - if xl_channel_config.transceiverName.decode() == "Virtual CAN": + if "Virtual CAN" in xl_channel_config.transceiverName.decode(): return xl_channel_config.serialNumber raise LookupError("Vector virtual CAN not found") From 4713c2ccda821f1fe39238361ed6b30692b70f7d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 12 Jan 2023 17:56:52 +0100 Subject: [PATCH 0998/1235] USB2CAN: Faster channel detection on Windows (#1480) Co-authored-by: zariiii9003 --- can/interfaces/usb2can/serial_selector.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index 22a95ae7c..c2e48ff97 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -49,11 +49,13 @@ def find_serial_devices(serial_matcher: str = "") -> List[str]: :param serial_matcher: only device IDs starting with this string are returned """ - objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") - objSWbemServices = objWMIService.ConnectServer(".", "root\\cimv2") - query = "SELECT * FROM CIM_LogicalDevice where Name LIKE '%USB2CAN%'" - devices = objSWbemServices.ExecQuery(query) - serial_numbers = [device.DeviceID.split("\\")[-1] for device in devices] + serial_numbers = [] + wmi = win32com.client.GetObject("winmgmts:") + for usb_controller in wmi.InstancesOf("Win32_USBControllerDevice"): + usb_device = wmi.Get(usb_controller.Dependent) + if "USB2CAN" in usb_device.Name: + serial_numbers.append(usb_device.DeviceID.split("\\")[-1]) + if serial_matcher: return [sn for sn in serial_numbers if serial_matcher in sn] return serial_numbers From ef803a57adac6e74b6a3af81ad52d1a7042814f9 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 12 Jan 2023 18:09:47 +0100 Subject: [PATCH 0999/1235] Fix logger name --- can/viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/viewer.py b/can/viewer.py index e87684485..5539ee3fb 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -39,7 +39,7 @@ ) -logger = logging.getLogger("can.serial") +logger = logging.getLogger("can.viewer") try: import curses From 69a5209edb893413d932d8881ad93b5cd599af95 Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Sat, 14 Jan 2023 16:34:29 +0100 Subject: [PATCH 1000/1235] Enable SocketCAN interface tests in GitHub CI (#1484) * Update CI to set up vcan and run SocketCAN tests * Add test to document PyPy raw CAN socket implementation status * Update test for restarting of SocketCAN SendTask Previously, it was not permitted to restart an already started period send task for SocketCAN. This behavior was changed in PR #1440. This commit adjusts the test to reflect this change. * Update PyPy raw CAN socket test failure into warning --- .github/workflows/ci.yml | 10 +++++++++- test/test_cyclic_socketcan.py | 10 ++-------- test/test_socketcan.py | 35 ++++++++++++++++++++++++++++------- tox.ini | 1 + 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c610c6a76..4f96578e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,10 +38,18 @@ jobs: run: | python -m pip install --upgrade pip pip install tox + - name: Setup SocketCAN + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt-get -y install linux-modules-extra-$(uname -r) + sudo ./test/open_vcan.sh - name: Test with pytest via tox run: | tox -e gh - + env: + # SocketCAN tests currently fail with PyPy because it does not support raw CAN sockets + # See: https://foss.heptapod.net/pypy/pypy/-/issues/3809 + TEST_SOCKETCAN: "${{ matrix.os == 'ubuntu-latest' && ! startsWith(matrix.python-version, 'pypy' ) }}" - name: Coveralls Parallel uses: coverallsapp/github-action@master with: diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index ca1db6bfc..30c86d6a5 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -256,14 +256,8 @@ def test_start_already_started_task(self): task_a = self._send_bus.send_periodic(messages_a, self.PERIOD) time.sleep(0.1) - # Try to start it again, task_id is not incremented in this case - with self.assertRaises(can.CanOperationError) as ctx: - task_a.start() - self.assertEqual( - "A periodic task for task ID 1 is already in progress by the SocketCAN Linux layer", - str(ctx.exception), - ) - + # Task restarting is permitted as of #1440 + task_a.start() task_a.stop() def test_create_same_id(self): diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 1c38e1583..324890dad 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -6,8 +6,17 @@ import ctypes import struct import unittest +import warnings from unittest.mock import patch +import can +from can.interfaces.socketcan.constants import ( + CAN_BCM_TX_DELETE, + CAN_BCM_TX_SETUP, + SETTIMER, + STARTTIMER, + TX_COUNTEVT, +) from can.interfaces.socketcan.socketcan import ( bcm_header_factory, build_bcm_header, @@ -16,13 +25,7 @@ build_bcm_update_header, BcmMsgHead, ) -from can.interfaces.socketcan.constants import ( - CAN_BCM_TX_DELETE, - CAN_BCM_TX_SETUP, - SETTIMER, - STARTTIMER, - TX_COUNTEVT, -) +from .config import IS_LINUX, IS_PYPY class SocketCANTest(unittest.TestCase): @@ -353,6 +356,24 @@ def test_build_bcm_update_header(self): self.assertEqual(can_id, result.can_id) self.assertEqual(1, result.nframes) + @unittest.skipUnless(IS_LINUX and IS_PYPY, "Only test when run on Linux with PyPy") + def test_pypy_socketcan_support(self): + """Wait for PyPy raw CAN socket support + + This test shall document raw CAN socket support under PyPy. Once this test fails, it is likely that PyPy + either implemented raw CAN socket support or at least changed the error that is thrown. + https://foss.heptapod.net/pypy/pypy/-/issues/3809 + https://github.com/hardbyte/python-can/issues/1479 + """ + try: + can.Bus(interface="socketcan", channel="vcan0", bitrate=500000) + except OSError as e: + if "unknown address family" not in str(e): + warnings.warn( + "Please check if PyPy has implemented raw CAN socket support! " + "See: https://foss.heptapod.net/pypy/pypy/-/issues/3809" + ) + if __name__ == "__main__": unittest.main() diff --git a/tox.ini b/tox.ini index 0dbd6423e..96cc82425 100644 --- a/tox.ini +++ b/tox.ini @@ -26,6 +26,7 @@ passenv = GITHUB_* COVERALLS_* PY_COLORS + TEST_SOCKETCAN [testenv:travis] passenv = From 35de98eccfa84f86f4754abec58d52037b4e8ffa Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 20 Jan 2023 15:50:33 +0100 Subject: [PATCH 1001/1235] Update PCAN Basic to 4.6.2.753 (#1481) * update PCAN Basic to 4.6.2.753 * faster channel detection * fix PcanBus._detect_available_configs * remove TODO * add more information to pcan channel detection Co-authored-by: zariiii9003 --- can/interfaces/pcan/basic.py | 82 ++++++++++++++++++++++++------------ can/interfaces/pcan/pcan.py | 50 ++++++++++++++++++---- test/test_pcan.py | 30 ++++++++++--- 3 files changed, 120 insertions(+), 42 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index e9fc5029a..77be2c854 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -8,15 +8,15 @@ # # ------------------------------------------------------------------ # Author : Keneth Wagner +# Last change: 2022-07-06 # ------------------------------------------------------------------ # -# Copyright (C) 1999-2021 PEAK-System Technik GmbH, Darmstadt +# Copyright (C) 1999-2022 PEAK-System Technik GmbH, Darmstadt # more Info at http://www.peak-system.com # Module Imports from ctypes import * from ctypes.util import find_library -from string import * import platform import logging @@ -282,6 +282,12 @@ PCAN_ATTACHED_CHANNELS = TPCANParameter( 0x2B ) # Get information about PCAN channels attached to a system +PCAN_ALLOW_ECHO_FRAMES = TPCANParameter( + 0x2C +) # Echo messages reception status within a PCAN-Channel +PCAN_DEVICE_PART_NUMBER = TPCANParameter( + 0x2D +) # Get the part number associated to a device # DEPRECATED parameters # @@ -354,8 +360,8 @@ 33 ) # Maximum length of the name of a device: 32 characters + terminator MAX_LENGTH_VERSION_STRING = int( - 18 -) # Maximum length of a version string: 17 characters + terminator + 256 +) # Maximum length of a version string: 255 characters + terminator # PCAN message types # @@ -377,6 +383,9 @@ PCAN_MESSAGE_ESI = TPCANMessageType( 0x10 ) # The PCAN message represents a FD error state indicator(CAN FD transmitter was error active) +PCAN_MESSAGE_ECHO = TPCANMessageType( + 0x20 +) # The PCAN message represents an echo CAN Frame PCAN_MESSAGE_ERRFRAME = TPCANMessageType( 0x40 ) # The PCAN message represents an error frame @@ -654,33 +663,38 @@ class PCANBasic: """PCAN-Basic API class implementation""" def __init__(self): - # Loads the PCANBasic API - # if platform.system() == "Windows": - # Loads the API on Windows - _dll_path = find_library("PCANBasic") - self.__m_dllBasic = windll.LoadLibrary(_dll_path) if _dll_path else None - aReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) - try: - aKey = winreg.OpenKey(aReg, r"SOFTWARE\PEAK-System\PEAK-Drivers") - winreg.CloseKey(aKey) - except OSError: - logger.error("Exception: The PEAK-driver couldn't be found!") - finally: - winreg.CloseKey(aReg) - elif "CYGWIN" in platform.system(): - self.__m_dllBasic = cdll.LoadLibrary("PCANBasic.dll") - # Unfortunately cygwin python has no winreg module, so we can't - # check for the registry key. - elif platform.system() == "Linux": - # Loads the API on Linux - self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so") + load_library_func = windll.LoadLibrary + + # look for Peak drivers in Windows registry + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as reg: + try: + with winreg.OpenKey(reg, r"SOFTWARE\PEAK-System\PEAK-Drivers"): + pass + except OSError: + raise OSError("The PEAK-driver could not be found!") from None + else: + load_library_func = cdll.LoadLibrary + + if platform.system() == "Windows" or "CYGWIN" in platform.system(): + lib_name = "PCANBasic.dll" elif platform.system() == "Darwin": - self.__m_dllBasic = cdll.LoadLibrary(find_library("libPCBUSB.dylib")) + # PCBUSB library is a third-party software created + # and maintained by the MacCAN project + lib_name = "libPCBUSB.dylib" else: - self.__m_dllBasic = cdll.LoadLibrary("libpcanbasic.so") - if self.__m_dllBasic is None: - logger.error("Exception: The PCAN-Basic DLL couldn't be loaded!") + lib_name = "libpcanbasic.so" + + lib_path = find_library(lib_name) + if not lib_path: + raise OSError(f"{lib_name} library not found.") + + try: + self.__m_dllBasic = load_library_func(lib_path) + except OSError: + raise OSError( + f"The PCAN-Basic API could not be loaded. ({lib_path})" + ) from None # Initializes a PCAN Channel # @@ -965,6 +979,7 @@ def GetValue(self, Channel, Parameter): or Parameter == PCAN_BITRATE_INFO_FD or Parameter == PCAN_IP_ADDRESS or Parameter == PCAN_FIRMWARE_VERSION + or Parameter == PCAN_DEVICE_PART_NUMBER ): mybuffer = create_string_buffer(256) @@ -974,6 +989,12 @@ def GetValue(self, Channel, Parameter): return (TPCANStatus(res[0]),) mybuffer = (TPCANChannelInformation * res[1])() + elif ( + Parameter == PCAN_ACCEPTANCE_FILTER_11BIT + or PCAN_ACCEPTANCE_FILTER_29BIT + ): + mybuffer = c_int64(0) + else: mybuffer = c_int(0) @@ -1017,6 +1038,11 @@ def SetValue(self, Channel, Parameter, Buffer): or Parameter == PCAN_TRACE_LOCATION ): mybuffer = create_string_buffer(256) + elif ( + Parameter == PCAN_ACCEPTANCE_FILTER_11BIT + or PCAN_ACCEPTANCE_FILTER_29BIT + ): + mybuffer = c_int64(0) else: mybuffer = c_int(0) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index cd0349c99..7f9b31f2f 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -6,16 +6,19 @@ import time from datetime import datetime import platform - -from typing import Optional +from typing import Optional, List from packaging import version -from ...message import Message -from ...bus import BusABC, BusState -from ...util import len2dlc, dlc2len -from ...exceptions import CanError, CanOperationError, CanInitializationError - +from can import ( + BusABC, + BusState, + CanError, + CanOperationError, + CanInitializationError, + Message, +) +from can.util import len2dlc, dlc2len from .basic import ( PCAN_BITRATES, @@ -57,6 +60,8 @@ FEATURE_FD_CAPABLE, PCAN_DICT_STATUS, PCAN_BUSOFF_AUTORESET, + PCAN_ATTACHED_CHANNELS, + TPCANChannelInformation, ) @@ -78,7 +83,6 @@ except ImportError as error: log.warning( "uptime library not available, timestamps are relative to boot time and not to Epoch UTC", - exc_info=True, ) boottimeEpoch = 0 @@ -278,7 +282,7 @@ def __init__( # TODO Remove Filter when MACCan actually supports it: # https://github.com/mac-can/PCBUSB-Library/ log.debug( - "Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.10" + "Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.11.2" ) if kwargs.get("auto_reset", False): @@ -624,7 +628,35 @@ def _detect_available_configs(): library_handle = PCANBasic() except OSError: return channels + interfaces = [] + + if platform.system() != "Darwin": + res, value = library_handle.GetValue(PCAN_NONEBUS, PCAN_ATTACHED_CHANNELS) + if res != PCAN_ERROR_OK: + return interfaces + channel_information: List[TPCANChannelInformation] = list(value) + for channel in channel_information: + # find channel name in PCAN_CHANNEL_NAMES by value + channel_name = next( + _channel_name + for _channel_name, channel_id in PCAN_CHANNEL_NAMES.items() + if channel_id.value == channel.channel_handle + ) + channel_config = { + "interface": "pcan", + "channel": channel_name, + "supports_fd": bool(channel.device_features & FEATURE_FD_CAPABLE), + "controller_number": channel.controller_number, + "device_features": channel.device_features, + "device_id": channel.device_id, + "device_name": channel.device_name.decode("latin-1"), + "device_type": channel.device_type, + "channel_condition": channel.channel_condition, + } + interfaces.append(channel_config) + return interfaces + for i in range(16): interfaces.append( { diff --git a/test/test_pcan.py b/test/test_pcan.py index 7e8e27cf6..10a69d7b9 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -3,6 +3,7 @@ """ import ctypes +import platform import unittest from unittest import mock from unittest.mock import Mock @@ -330,11 +331,30 @@ def test_state(self, name, bus_state: BusState, expected_parameter) -> None: ) def test_detect_available_configs(self) -> None: - self.mock_pcan.GetValue = Mock( - return_value=(PCAN_ERROR_OK, PCAN_CHANNEL_AVAILABLE) - ) - configs = PcanBus._detect_available_configs() - self.assertEqual(len(configs), 50) + if platform.system() == "Darwin": + self.mock_pcan.GetValue = Mock( + return_value=(PCAN_ERROR_OK, PCAN_CHANNEL_AVAILABLE) + ) + configs = PcanBus._detect_available_configs() + self.assertEqual(len(configs), 50) + else: + value = (TPCANChannelInformation * 1).from_buffer_copy( + b"Q\x00\x05\x00\x01\x00\x00\x00PCAN-USB FD\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b'\x00\x00\x00\x00\x00\x00\x003"\x11\x00\x01\x00\x00\x00' + ) + self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_OK, value)) + configs = PcanBus._detect_available_configs() + assert len(configs) == 1 + assert configs[0]["interface"] == "pcan" + assert configs[0]["channel"] == "PCAN_USBBUS1" + assert configs[0]["supports_fd"] + assert configs[0]["controller_number"] == 0 + assert configs[0]["device_features"] == 1 + assert configs[0]["device_id"] == 1122867 + assert configs[0]["device_name"] == "PCAN-USB FD" + assert configs[0]["device_type"] == 5 + assert configs[0]["channel_condition"] == 1 @parameterized.expand([("valid", PCAN_ERROR_OK, "OK"), ("invalid", 0x00005, None)]) def test_status_string(self, name, status, expected_result) -> None: From 5c523ec9cc5ab3badbb6def6fb3750d228c7c7c0 Mon Sep 17 00:00:00 2001 From: BKaDamien <94377088+BKaDamien@users.noreply.github.com> Date: Mon, 23 Jan 2023 09:26:13 +0100 Subject: [PATCH 1002/1235] Add usage of select instead of polling for Linux based platform (PCAN Interface) (#1410) * - integrate handling of Linux based event using select for pcan interface * - adapt pcan unit tests regarding PCAN_RECEIVE_EVENT GetValue call * - add the case HAS_EVENTS = FALSE whne no special event basic mechanism is importes * - force select mock for test_recv_no_message * - just for testing : see what is going wrong with CI testing and my setup * - correct patch on pcan test test_recv_no_message - run black * - remove debug log * -just reformat test_pcan * - move global variable IS_WINDOW and IS_LINUX from pcan to basic module * - remove redundant comment in pcan _recv_internal * refactor _recv_internal Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/pcan/basic.py | 7 +- can/interfaces/pcan/pcan.py | 178 ++++++++++++++++++++--------------- test/test_pcan.py | 9 +- 3 files changed, 113 insertions(+), 81 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 77be2c854..5f161eecc 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -21,9 +21,12 @@ import logging -if platform.system() == "Windows": - import winreg +PLATFORM = platform.system() +IS_WINDOWS = PLATFORM == "Windows" +IS_LINUX = PLATFORM == "Linux" +if IS_WINDOWS: + import winreg logger = logging.getLogger("can.pcan") diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 7f9b31f2f..01adbe0c2 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -1,12 +1,11 @@ """ Enable basic CAN over a PCAN USB device. """ - import logging import time from datetime import datetime import platform -from typing import Optional, List +from typing import Optional, List, Tuple from packaging import version @@ -50,6 +49,8 @@ PCAN_LISTEN_ONLY, PCAN_PARAMETER_OFF, TPCANHandle, + IS_LINUX, + IS_WINDOWS, PCAN_PCIBUS1, PCAN_USBBUS1, PCAN_PCCBUS1, @@ -70,7 +71,6 @@ MIN_PCAN_API_VERSION = version.parse("4.2.0") - try: # use the "uptime" library if available import uptime @@ -86,22 +86,27 @@ ) boottimeEpoch = 0 -try: - # Try builtin Python 3 Windows API - from _overlapped import CreateEvent - from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE +HAS_EVENTS = False - HAS_EVENTS = True -except ImportError: +if IS_WINDOWS: try: - # Try pywin32 package - from win32event import CreateEvent - from win32event import WaitForSingleObject, WAIT_OBJECT_0, INFINITE + # Try builtin Python 3 Windows API + from _overlapped import CreateEvent + from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE HAS_EVENTS = True except ImportError: - # Use polling instead - HAS_EVENTS = False + pass + +elif IS_LINUX: + try: + import errno + import os + import select + + HAS_EVENTS = True + except Exception: + pass class PcanBus(BusABC): @@ -294,10 +299,16 @@ def __init__( raise PcanCanInitializationError(self._get_formatted_error(result)) if HAS_EVENTS: - self._recv_event = CreateEvent(None, 0, 0, None) - result = self.m_objPCANBasic.SetValue( - self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event - ) + if IS_WINDOWS: + self._recv_event = CreateEvent(None, 0, 0, None) + result = self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_RECEIVE_EVENT, self._recv_event + ) + elif IS_LINUX: + result, self._recv_event = self.m_objPCANBasic.GetValue( + self.m_PcanHandle, PCAN_RECEIVE_EVENT + ) + if result != PCAN_ERROR_OK: raise PcanCanInitializationError(self._get_formatted_error(result)) @@ -441,84 +452,96 @@ def set_device_number(self, device_number): return False return True - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: + end_time = time.time() + timeout if timeout is not None else None - if HAS_EVENTS: - # We will utilize events for the timeout handling - timeout_ms = int(timeout * 1000) if timeout is not None else INFINITE - elif timeout is not None: - # Calculate max time - end_time = time.perf_counter() + timeout - - # log.debug("Trying to read a msg") - - result = None - while result is None: + while True: if self.fd: - result = self.m_objPCANBasic.ReadFD(self.m_PcanHandle) + result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.ReadFD( + self.m_PcanHandle + ) else: - result = self.m_objPCANBasic.Read(self.m_PcanHandle) - if result[0] == PCAN_ERROR_QRCVEMPTY: - if HAS_EVENTS: - result = None - val = WaitForSingleObject(self._recv_event, timeout_ms) - if val != WAIT_OBJECT_0: - return None, False - elif timeout is not None and time.perf_counter() >= end_time: - return None, False + result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.Read( + self.m_PcanHandle + ) + + if result == PCAN_ERROR_OK: + # message received + break + + if result == PCAN_ERROR_QRCVEMPTY: + # receive queue is empty, wait or return on timeout + + if end_time is None: + time_left: Optional[float] = None + timed_out = False else: - result = None + time_left = max(0.0, end_time - time.time()) + timed_out = time_left == 0.0 + + if timed_out: + return None, False + + if not HAS_EVENTS: + # polling mode time.sleep(0.001) - elif result[0] & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY): - log.warning(self._get_formatted_error(result[0])) - return None, False - elif result[0] != PCAN_ERROR_OK: - raise PcanCanOperationError(self._get_formatted_error(result[0])) - - theMsg = result[1] - itsTimeStamp = result[2] - - # log.debug("Received a message") - - is_extended_id = ( - theMsg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value - ) == PCAN_MESSAGE_EXTENDED.value - is_remote_frame = ( - theMsg.MSGTYPE & PCAN_MESSAGE_RTR.value - ) == PCAN_MESSAGE_RTR.value - is_fd = (theMsg.MSGTYPE & PCAN_MESSAGE_FD.value) == PCAN_MESSAGE_FD.value - bitrate_switch = ( - theMsg.MSGTYPE & PCAN_MESSAGE_BRS.value - ) == PCAN_MESSAGE_BRS.value - error_state_indicator = ( - theMsg.MSGTYPE & PCAN_MESSAGE_ESI.value - ) == PCAN_MESSAGE_ESI.value - is_error_frame = ( - theMsg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value - ) == PCAN_MESSAGE_ERRFRAME.value + continue + + if IS_WINDOWS: + # Windows with event + if time_left is None: + time_left_ms = INFINITE + else: + time_left_ms = int(time_left * 1000) + _ret = WaitForSingleObject(self._recv_event, time_left_ms) + if _ret == WAIT_OBJECT_0: + continue + + elif IS_LINUX: + # Linux with event + recv, _, _ = select.select([self._recv_event], [], [], time_left) + if self._recv_event in recv: + continue + + elif result & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY): + log.warning(self._get_formatted_error(result)) + + else: + raise PcanCanOperationError(self._get_formatted_error(result)) + + return None, False + + is_extended_id = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value) + is_remote_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_RTR.value) + is_fd = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_FD.value) + bitrate_switch = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_BRS.value) + error_state_indicator = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ESI.value) + is_error_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value) if self.fd: - dlc = dlc2len(theMsg.DLC) - timestamp = boottimeEpoch + (itsTimeStamp.value / (1000.0 * 1000.0)) + dlc = dlc2len(pcan_msg.DLC) + timestamp = boottimeEpoch + (pcan_timestamp.value / (1000.0 * 1000.0)) else: - dlc = theMsg.LEN + dlc = pcan_msg.LEN timestamp = boottimeEpoch + ( ( - itsTimeStamp.micros - + 1000 * itsTimeStamp.millis - + 0x100000000 * 1000 * itsTimeStamp.millis_overflow + pcan_timestamp.micros + + 1000 * pcan_timestamp.millis + + 0x100000000 * 1000 * pcan_timestamp.millis_overflow ) / (1000.0 * 1000.0) ) rx_msg = Message( timestamp=timestamp, - arbitration_id=theMsg.ID, + arbitration_id=pcan_msg.ID, is_extended_id=is_extended_id, is_remote_frame=is_remote_frame, is_error_frame=is_error_frame, dlc=dlc, - data=theMsg.DATA[:dlc], + data=pcan_msg.DATA[:dlc], is_fd=is_fd, bitrate_switch=bitrate_switch, error_state_indicator=error_state_indicator, @@ -597,6 +620,9 @@ def flash(self, flash): def shutdown(self): super().shutdown() + if HAS_EVENTS and IS_LINUX: + self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_RECEIVE_EVENT, 0) + self.m_objPCANBasic.Uninitialize(self.m_PcanHandle) @property diff --git a/test/test_pcan.py b/test/test_pcan.py index 10a69d7b9..01dac848c 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -6,7 +6,8 @@ import platform import unittest from unittest import mock -from unittest.mock import Mock +from unittest.mock import Mock, patch + import pytest from parameterized import parameterized @@ -30,7 +31,6 @@ def setUp(self) -> None: self.mock_pcan.SetValue = Mock(return_value=PCAN_ERROR_OK) self.mock_pcan.GetValue = self._mockGetValue self.PCAN_API_VERSION_SIM = "4.2" - self.bus = None def tearDown(self) -> None: @@ -45,6 +45,8 @@ def _mockGetValue(self, channel, parameter): """ if parameter == PCAN_API_VERSION: return PCAN_ERROR_OK, self.PCAN_API_VERSION_SIM.encode("ascii") + elif parameter == PCAN_RECEIVE_EVENT: + return PCAN_ERROR_OK, int.from_bytes(PCAN_RECEIVE_EVENT, "big") raise NotImplementedError( f"No mock return value specified for parameter {parameter}" ) @@ -205,7 +207,8 @@ def test_recv_fd(self): self.assertEqual(recv_msg.timestamp, 0) @pytest.mark.timeout(3.0) - def test_recv_no_message(self): + @patch("select.select", return_value=([], [], [])) + def test_recv_no_message(self, mock_select): self.mock_pcan.Read = Mock(return_value=(PCAN_ERROR_QRCVEMPTY, None, None)) self.bus = can.Bus(interface="pcan") self.assertEqual(self.bus.recv(timeout=0.5), None) From 3c7520e91d70ea2b825ddc58102ad9eec122be74 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:24:24 +0100 Subject: [PATCH 1003/1235] Add ODB II url to docs (#1497) --- doc/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.rst b/doc/index.rst index c55108d97..402a485e7 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -14,7 +14,7 @@ linux such as a BeagleBone or RaspberryPi. More concretely, some example uses of the library: * Passively logging what occurs on a CAN bus. For example monitoring a - commercial vehicle using its **OBD-II** port. + commercial vehicle using its `OBD-II port `__. * Testing of hardware that interacts via CAN. Modules found in modern cars, motorcycles, boats, and even wheelchairs have had components tested From 48b18f1b8f4836e657407df1761ea9d0e3efd18b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 25 Jan 2023 22:16:48 +0100 Subject: [PATCH 1004/1235] Unify bit timings with separate class for CAN FD (#1468) * rework bit timings * fix pylint * convert config values to int * simplify instantiation from sample point * add docstrings * update bit timing docs and tests * fix wrong info * remove links * adapt CANalystIIBus * adapt CantactBus * try to set correct f_clock before raising exception * improve bit timing docs * turn `oscillator_tolerance` into function, improve `from_sample_point` * add tests * cleanup * Remove BitTimingFd Co-authored-by: Lukas Magel * Update can/interfaces/cantact.py Co-authored-by: Lukas Magel * Add test_btr_persistence suggestion Co-authored-by: Lukas Magel * Format code with black * move imports up * undo type hint change * mention dict conversion in docs * change member order for docs * use bitrate prescaler as ground truth * avoid repetition * Add utility method to validate and adjust the timing clock value * Fix ZeroDivisionError * improve tests and docs * add deprecation period * check if "timing" already exists * add bit timing figure to docs Co-authored-by: zariiii9003 Co-authored-by: Lukas Magel Co-authored-by: Lukas Magel --- can/__init__.py | 2 +- can/bit_timing.py | 1136 +++++++++++++++++++++++++---- can/interfaces/canalystii.py | 45 +- can/interfaces/cantact.py | 58 +- can/typechecking.py | 21 + can/util.py | 86 ++- doc/bit_timing.rst | 113 ++- doc/conf.py | 2 + doc/images/bit_timing_dark.svg | 96 +++ doc/images/bit_timing_light.svg | 92 +++ test/test_bit_timing.py | 397 +++++++++- test/test_cantact.py | 4 +- test/test_interface_canalystii.py | 14 +- test/test_util.py | 95 +++ 14 files changed, 1901 insertions(+), 260 deletions(-) create mode 100644 doc/images/bit_timing_dark.svg create mode 100644 doc/images/bit_timing_light.svg diff --git a/can/__init__.py b/can/__init__.py index 773e94022..d9ff5ffcd 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -33,7 +33,7 @@ from .interfaces import VALID_INTERFACES from . import interface from .interface import Bus, detect_available_configs -from .bit_timing import BitTiming +from .bit_timing import BitTiming, BitTimingFd from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync from .io import ASCWriter, ASCReader diff --git a/can/bit_timing.py b/can/bit_timing.py index b0ad762fb..2dc78064b 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -1,50 +1,125 @@ -from typing import Optional, Union +# pylint: disable=too-many-lines +import math +from typing import List, Mapping, Iterator, cast +from can.typechecking import BitTimingFdDict, BitTimingDict -class BitTiming: - """Representation of a bit timing configuration. - The class can be constructed in various ways, depending on the information - available or the capabilities of the interfaces that need to be supported. +class BitTiming(Mapping): + """Representation of a bit timing configuration for a CAN 2.0 bus. - The preferred way is using bitrate, CAN clock frequency, TSEG1, TSEG2, SJW:: + The class can be constructed in multiple ways, depending on the information + available. The preferred way is using CAN clock frequency, prescaler, tseg1, tseg2 and sjw:: - can.BitTiming(bitrate=1000000, f_clock=8000000, tseg1=5, tseg2=1, sjw=1) + can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=1, sjw=1) - If the clock frequency is unknown it may be omitted but some interfaces may - require it. + Alternatively you can set the bitrate instead of the bit rate prescaler:: - Alternatively the BRP can be given instead of bitrate and clock frequency but this - will limit the number of supported interfaces. + can.BitTiming.from_bitrate_and_segments( + f_clock=8_000_000, bitrate=1_000_000, tseg1=5, tseg2=1, sjw=1 + ) - It is also possible specify BTR registers directly, - but will not work for all interfaces:: + It is also possible to specify BTR registers:: - can.BitTiming(btr0=0x00, btr1=0x14) - """ + can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14) + + or to calculate the timings for a given sample point:: - sync_seg = 1 + can.BitTiming.from_sample_point(f_clock=8_000_000, bitrate=1_000_000, sample_point=75.0) + """ def __init__( self, - 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, + f_clock: int, + brp: int, + tseg1: int, + tseg2: int, + sjw: int, nof_samples: int = 1, - btr0: Optional[int] = None, - btr1: Optional[int] = None, - ): + ) -> None: """ - :param int bitrate: - Bitrate in bits/s. :param int f_clock: The CAN system clock frequency in Hz. - Usually the oscillator frequency divided by 2. :param int brp: - Bit Rate Prescaler. Prefer to use bitrate and f_clock instead. + Bit rate prescaler. + :param int tseg1: + Time segment 1, that is, the number of quanta from (but not including) + the Sync Segment to the sampling point. + :param int tseg2: + Time segment 2, that is, the number of quanta from the sampling + point to the end of the bit. + :param int sjw: + The Synchronization Jump Width. Decides the maximum number of time quanta + that the controller can resynchronize every bit. + :param int nof_samples: + Either 1 or 3. Some CAN controllers can also sample each bit three times. + In this case, the bit will be sampled three quanta in a row, + with the last sample being taken in the edge between TSEG1 and TSEG2. + Three samples should only be used for relatively slow baudrates. + :raises ValueError: + if the arguments are invalid. + """ + self._data: BitTimingDict = { + "f_clock": f_clock, + "brp": brp, + "tseg1": tseg1, + "tseg2": tseg2, + "sjw": sjw, + "nof_samples": nof_samples, + } + self._validate() + + def _validate(self) -> None: + if not 8 <= self.nbt <= 25: + raise ValueError(f"nominal bit time (={self.nbt}) must be in [8...25].") + + if not 1 <= self.brp <= 64: + raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...64].") + + if not 5_000 <= self.bitrate <= 2_000_000: + raise ValueError( + f"bitrate (={self.bitrate}) must be in [5,000...2,000,000]." + ) + + if not 1 <= self.tseg1 <= 16: + raise ValueError(f"tseg1 (={self.tseg1}) must be in [1...16].") + + if not 1 <= self.tseg2 <= 8: + raise ValueError(f"tseg2 (={self.tseg2}) must be in [1...8].") + + if not 1 <= self.sjw <= 4: + raise ValueError(f"sjw (={self.sjw}) must be in [1...4].") + + if self.sjw > self.tseg2: + raise ValueError( + f"sjw (={self.sjw}) must not be greater than tseg2 (={self.tseg2})." + ) + + if self.sample_point < 50.0: + raise ValueError( + f"The sample point must be greater than or equal to 50% " + f"(sample_point={self.sample_point:.2f}%)." + ) + + if self.nof_samples not in (1, 3): + raise ValueError("nof_samples must be 1 or 3") + + @classmethod + def from_bitrate_and_segments( + cls, + f_clock: int, + bitrate: int, + tseg1: int, + tseg2: int, + sjw: int, + nof_samples: int = 1, + ) -> "BitTiming": + """Create a :class:`~can.BitTiming` instance from bitrate and segment lengths. + + :param int f_clock: + The CAN system clock frequency in Hz. + :param int bitrate: + Bitrate in bit/s. :param int tseg1: Time segment 1, that is, the number of quanta from (but not including) the Sync Segment to the sampling point. @@ -59,59 +134,154 @@ def __init__( In this case, the bit will be sampled three quanta in a row, with the last sample being taken in the edge between TSEG1 and TSEG2. Three samples should only be used for relatively slow baudrates. + :raises ValueError: + if the arguments are invalid. + """ + try: + brp = int(round(f_clock / (bitrate * (1 + tseg1 + tseg2)))) + except ZeroDivisionError: + raise ValueError("Invalid inputs") from None + + bt = cls( + f_clock=f_clock, + brp=brp, + tseg1=tseg1, + tseg2=tseg2, + sjw=sjw, + nof_samples=nof_samples, + ) + if abs(bt.bitrate - bitrate) > bitrate / 256: + raise ValueError( + f"the effective bitrate (={bt.bitrate}) diverges " + f"from the requested bitrate (={bitrate})" + ) + return bt + + @classmethod + def from_registers( + cls, + f_clock: int, + btr0: int, + btr1: int, + ) -> "BitTiming": + """Create a :class:`~can.BitTiming` instance from registers btr0 and btr1. + + :param int f_clock: + The CAN system clock frequency in Hz. :param int btr0: The BTR0 register value used by many CAN controllers. :param int btr1: The BTR1 register value used by many CAN controllers. + :raises ValueError: + if the arguments are invalid. """ - self._bitrate = bitrate - self._brp = brp - self._sjw = sjw - self._tseg1 = tseg1 - self._tseg2 = tseg2 - self._nof_samples = nof_samples - self._f_clock = f_clock - - if btr0 is not None: - self._brp = (btr0 & 0x3F) + 1 - self._sjw = (btr0 >> 6) + 1 - if btr1 is not None: - self._tseg1 = (btr1 & 0xF) + 1 - self._tseg2 = ((btr1 >> 4) & 0x7) + 1 - self._nof_samples = 3 if btr1 & 0x80 else 1 - - if nof_samples not in (1, 3): - raise ValueError("nof_samples must be 1 or 3") + brp = (btr0 & 0x3F) + 1 + sjw = (btr0 >> 6) + 1 + tseg1 = (btr1 & 0xF) + 1 + tseg2 = ((btr1 >> 4) & 0x7) + 1 + nof_samples = 3 if btr1 & 0x80 else 1 + return cls( + brp=brp, + f_clock=f_clock, + tseg1=tseg1, + tseg2=tseg2, + sjw=sjw, + nof_samples=nof_samples, + ) + + @classmethod + def from_sample_point( + cls, f_clock: int, bitrate: int, sample_point: float = 69.0 + ) -> "BitTiming": + """Create a :class:`~can.BitTiming` instance for a sample point. + + This function tries to find bit timings, which are close to the requested + sample point. It does not take physical bus properties into account, so the + calculated bus timings might not work properly for you. + + The :func:`oscillator_tolerance` function might be helpful to evaluate the + bus timings. + + :param int f_clock: + The CAN system clock frequency in Hz. + :param int bitrate: + Bitrate in bit/s. + :param int sample_point: + The sample point value in percent. + :raises ValueError: + if the arguments are invalid. + """ + + if sample_point < 50.0: + raise ValueError(f"sample_point (={sample_point}) must not be below 50%.") + + possible_solutions: List[BitTiming] = [] + for brp in range(1, 65): + nbt = round(int(f_clock / (bitrate * brp))) + if nbt < 8: + break + + effective_bitrate = f_clock / (nbt * brp) + if abs(effective_bitrate - bitrate) > bitrate / 256: + continue + + tseg1 = int(round(sample_point / 100 * nbt)) - 1 + # limit tseg1, so tseg2 is at least 1 TQ + tseg1 = min(tseg1, nbt - 2) + + tseg2 = nbt - tseg1 - 1 + sjw = min(tseg2, 4) + + try: + bt = BitTiming( + f_clock=f_clock, + brp=brp, + tseg1=tseg1, + tseg2=tseg2, + sjw=sjw, + ) + possible_solutions.append(bt) + except ValueError: + continue + + if not possible_solutions: + raise ValueError("No suitable bit timings found.") + + # sort solutions + for key, reverse in ( + # prefer low prescaler + (lambda x: x.brp, False), + # prefer low sample point deviation from requested values + (lambda x: abs(x.sample_point - sample_point), False), + ): + possible_solutions.sort(key=key, reverse=reverse) + + return possible_solutions[0] @property - def nbt(self) -> int: - """Nominal Bit Time.""" - return self.sync_seg + self.tseg1 + self.tseg2 + def f_clock(self) -> int: + """The CAN system clock frequency in Hz.""" + return self._data["f_clock"] @property - def bitrate(self) -> Union[int, float]: + def bitrate(self) -> int: """Bitrate in bits/s.""" - if self._bitrate: - return self._bitrate - if self._f_clock and self._brp: - return self._f_clock / (self._brp * self.nbt) - raise ValueError("bitrate must be specified") + return int(round(self.f_clock / (self.nbt * self.brp))) @property def brp(self) -> int: """Bit Rate Prescaler.""" - if self._brp: - return self._brp - if self._f_clock and self._bitrate: - return round(self._f_clock / (self._bitrate * self.nbt)) - raise ValueError("Either bitrate and f_clock or brp must be specified") + return self._data["brp"] @property - def sjw(self) -> int: - """Synchronization Jump Width.""" - if not self._sjw: - raise ValueError("sjw must be specified") - return self._sjw + def tq(self) -> int: + """Time quantum in nanoseconds""" + return int(round(self.brp / self.f_clock * 1e9)) + + @property + def nbt(self) -> int: + """Nominal Bit Time.""" + return 1 + self.tseg1 + self.tseg2 @property def tseg1(self) -> int: @@ -119,9 +289,7 @@ def tseg1(self) -> int: The number of quanta from (but not including) the Sync Segment to the sampling point. """ - if not self._tseg1: - raise ValueError("tseg1 must be specified") - return self._tseg1 + return self._data["tseg1"] @property def tseg2(self) -> int: @@ -129,104 +297,788 @@ def tseg2(self) -> int: The number of quanta from the sampling point to the end of the bit. """ - if not self._tseg2: - raise ValueError("tseg2 must be specified") - return self._tseg2 + return self._data["tseg2"] @property - def nof_samples(self) -> int: - """Number of samples (1 or 3).""" - if not self._nof_samples: - raise ValueError("nof_samples must be specified") - return self._nof_samples + def sjw(self) -> int: + """Synchronization Jump Width.""" + return self._data["sjw"] @property - def f_clock(self) -> int: - """The CAN system clock frequency in Hz. - - Usually the oscillator frequency divided by 2. - """ - if not self._f_clock: - raise ValueError("f_clock must be specified") - return self._f_clock + def nof_samples(self) -> int: + """Number of samples (1 or 3).""" + return self._data["nof_samples"] @property def sample_point(self) -> float: """Sample point in percent.""" - return 100.0 * (self.nbt - self.tseg2) / self.nbt + return 100.0 * (1 + self.tseg1) / (1 + self.tseg1 + self.tseg2) @property def btr0(self) -> int: - sjw = self.sjw - brp = self.brp - - if brp < 1 or brp > 64: - raise ValueError("brp must be 1 - 64") - if sjw < 1 or sjw > 4: - raise ValueError("sjw must be 1 - 4") - - return (sjw - 1) << 6 | brp - 1 + """Bit timing register 0.""" + return (self.sjw - 1) << 6 | self.brp - 1 @property def btr1(self) -> int: + """Bit timing register 1.""" sam = 1 if self.nof_samples == 3 else 0 - tseg1 = self.tseg1 - tseg2 = self.tseg2 + return sam << 7 | (self.tseg2 - 1) << 4 | self.tseg1 - 1 - if tseg1 < 1 or tseg1 > 16: - raise ValueError("tseg1 must be 1 - 16") - if tseg2 < 1 or tseg2 > 8: - raise ValueError("tseg2 must be 1 - 8") + def oscillator_tolerance( + self, + node_loop_delay_ns: float = 250.0, + bus_length_m: float = 10.0, + ) -> float: + """Oscillator tolerance in percent according to ISO 11898-1. - return sam << 7 | (tseg2 - 1) << 4 | tseg1 - 1 + :param float node_loop_delay_ns: + Transceiver loop delay in nanoseconds. + :param float bus_length_m: + Bus length in meters. + """ + delay_per_meter = 5 + bidirectional_propagation_delay_ns = 2 * ( + node_loop_delay_ns + delay_per_meter * bus_length_m + ) - def __str__(self) -> str: - segments = [] - try: - segments.append(f"{self.bitrate} bits/s") - except ValueError: - pass - try: - segments.append(f"sample point: {self.sample_point:.2f}%") - except ValueError: - pass - try: - segments.append(f"BRP: {self.brp}") - except ValueError: - pass - try: - segments.append(f"TSEG1: {self.tseg1}") - except ValueError: - pass + prop_seg = math.ceil(bidirectional_propagation_delay_ns / self.tq) + nom_phase_seg1 = self.tseg1 - prop_seg + nom_phase_seg2 = self.tseg2 + df_clock_list = [ + _oscillator_tolerance_condition_1(nom_sjw=self.sjw, nbt=self.nbt), + _oscillator_tolerance_condition_2( + nbt=self.nbt, + nom_phase_seg1=nom_phase_seg1, + nom_phase_seg2=nom_phase_seg2, + ), + ] + return max(0.0, min(df_clock_list) * 100) + + def recreate_with_f_clock(self, f_clock: int) -> "BitTiming": + """Return a new :class:`~can.BitTiming` instance with the given *f_clock* but the same + bit rate and sample point. + + :param int f_clock: + The CAN system clock frequency in Hz. + :raises ValueError: + if no suitable bit timings were found. + """ + # try the most simple solution first: another bitrate prescaler try: - segments.append(f"TSEG2: {self.tseg2}") + return BitTiming.from_bitrate_and_segments( + f_clock=f_clock, + bitrate=self.bitrate, + tseg1=self.tseg1, + tseg2=self.tseg2, + sjw=self.sjw, + nof_samples=self.nof_samples, + ) except ValueError: pass + + # create a new timing instance with the same sample point + bt = BitTiming.from_sample_point( + f_clock=f_clock, bitrate=self.bitrate, sample_point=self.sample_point + ) + if abs(bt.sample_point - self.sample_point) > 1.0: + raise ValueError( + "f_clock change failed because of sample point discrepancy." + ) + # adapt synchronization jump width, so it has the same size relative to bit time as self + sjw = int(round(self.sjw / self.nbt * bt.nbt)) + sjw = max(1, min(4, bt.tseg2, sjw)) + bt._data["sjw"] = sjw # pylint: disable=protected-access + bt._data["nof_samples"] = self.nof_samples # pylint: disable=protected-access + bt._validate() # pylint: disable=protected-access + return bt + + def __str__(self) -> str: + segments = [ + f"BR {self.bitrate} bit/s", + f"SP: {self.sample_point:.2f}%", + f"BRP: {self.brp}", + f"TSEG1: {self.tseg1}", + f"TSEG2: {self.tseg2}", + f"SJW: {self.sjw}", + f"BTR: {self.btr0:02X}{self.btr1:02X}h", + f"f_clock: {self.f_clock / 1e6:.0f}MHz", + ] + return ", ".join(segments) + + def __repr__(self) -> str: + args = ", ".join(f"{key}={value}" for key, value in self.items()) + return f"can.{self.__class__.__name__}({args})" + + def __getitem__(self, key: str) -> int: + return cast(int, self._data.__getitem__(key)) + + def __len__(self) -> int: + return self._data.__len__() + + def __iter__(self) -> Iterator[str]: + return self._data.__iter__() + + def __eq__(self, other: object) -> bool: + if not isinstance(other, BitTiming): + return False + + return self._data == other._data + + +class BitTimingFd(Mapping): + """Representation of a bit timing configuration for a CAN FD bus. + + The class can be constructed in multiple ways, depending on the information + available. The preferred way is using CAN clock frequency, bit rate prescaler, tseg1, + tseg2 and sjw for both the arbitration (nominal) and data phase:: + + can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=59, + nom_tseg2=20, + nom_sjw=10, + data_brp=1, + data_tseg1=6, + data_tseg2=3, + data_sjw=2, + ) + + Alternatively you can set the bit rates instead of the bit rate prescalers:: + + can.BitTimingFd.from_bitrate_and_segments( + f_clock=80_000_000, + nom_bitrate=1_000_000, + nom_tseg1=59, + nom_tseg2=20, + nom_sjw=10, + data_bitrate=8_000_000, + data_tseg1=6, + data_tseg2=3, + data_sjw=2, + ) + + It is also possible to calculate the timings for a given + pair of arbitration and data sample points:: + + can.BitTimingFd.from_sample_point( + f_clock=80_000_000, + nom_bitrate=1_000_000, + nom_sample_point=75.0, + data_bitrate=8_000_000, + data_sample_point=70.0, + ) + """ + + def __init__( + self, + f_clock: int, + nom_brp: int, + nom_tseg1: int, + nom_tseg2: int, + nom_sjw: int, + data_brp: int, + data_tseg1: int, + data_tseg2: int, + data_sjw: int, + ) -> None: + """ + Initialize a BitTimingFd instance with the specified parameters. + + :param int f_clock: + The CAN system clock frequency in Hz. + :param int nom_brp: + Nominal (arbitration) phase bitrate prescaler. + :param int nom_tseg1: + Nominal phase Time segment 1, that is, the number of quanta from (but not including) + the Sync Segment to the sampling point. + :param int nom_tseg2: + Nominal phase Time segment 2, that is, the number of quanta from the sampling + point to the end of the bit. + :param int nom_sjw: + The Synchronization Jump Width for the nominal phase. This value determines + the maximum number of time quanta that the controller can resynchronize every bit. + :param int data_brp: + Data phase bitrate prescaler. + :param int data_tseg1: + Data phase Time segment 1, that is, the number of quanta from (but not including) + the Sync Segment to the sampling point. + :param int data_tseg2: + Data phase Time segment 2, that is, the number of quanta from the sampling + point to the end of the bit. + :param int data_sjw: + The Synchronization Jump Width for the data phase. This value determines + the maximum number of time quanta that the controller can resynchronize every bit. + :raises ValueError: + if the arguments are invalid. + """ + self._data: BitTimingFdDict = { + "f_clock": f_clock, + "nom_brp": nom_brp, + "nom_tseg1": nom_tseg1, + "nom_tseg2": nom_tseg2, + "nom_sjw": nom_sjw, + "data_brp": data_brp, + "data_tseg1": data_tseg1, + "data_tseg2": data_tseg2, + "data_sjw": data_sjw, + } + self._validate() + + def _validate(self) -> None: + if self.nbt < 8: + raise ValueError(f"nominal bit time (={self.nbt}) must be at least 8.") + + if self.dbt < 8: + raise ValueError(f"data bit time (={self.dbt}) must be at least 8.") + + if not 1 <= self.nom_brp <= 256: + raise ValueError( + f"nominal bitrate prescaler (={self.nom_brp}) must be in [1...256]." + ) + + if not 1 <= self.data_brp <= 256: + raise ValueError( + f"data bitrate prescaler (={self.data_brp}) must be in [1...256]." + ) + + if not 5_000 <= self.nom_bitrate <= 2_000_000: + raise ValueError( + f"nom_bitrate (={self.nom_bitrate}) must be in [5,000...2,000,000]." + ) + + if not 25_000 <= self.data_bitrate <= 8_000_000: + raise ValueError( + f"data_bitrate (={self.data_bitrate}) must be in [25,000...8,000,000]." + ) + + if self.data_bitrate < self.nom_bitrate: + raise ValueError( + f"data_bitrate (={self.data_bitrate}) must be greater than or " + f"equal to nom_bitrate (={self.nom_bitrate})" + ) + + if not 2 <= self.nom_tseg1 <= 256: + raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...256].") + + if not 1 <= self.nom_tseg2 <= 128: + raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [1...128].") + + if not 1 <= self.data_tseg1 <= 32: + raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...32].") + + if not 1 <= self.data_tseg2 <= 16: + raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [1...16].") + + if not 1 <= self.nom_sjw <= 128: + raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...128].") + + if self.nom_sjw > self.nom_tseg2: + raise ValueError( + f"nom_sjw (={self.nom_sjw}) must not be " + f"greater than nom_tseg2 (={self.nom_tseg2})." + ) + + if not 1 <= self.data_sjw <= 16: + raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...128].") + + if self.data_sjw > self.data_tseg2: + raise ValueError( + f"data_sjw (={self.data_sjw}) must not be " + f"greater than data_tseg2 (={self.data_tseg2})." + ) + + if self.nom_sample_point < 50.0: + raise ValueError( + f"The arbitration sample point must be greater than or equal to 50% " + f"(nom_sample_point={self.nom_sample_point:.2f}%)." + ) + + if self.data_sample_point < 50.0: + raise ValueError( + f"The data sample point must be greater than or equal to 50% " + f"(data_sample_point={self.data_sample_point:.2f}%)." + ) + + @classmethod + def from_bitrate_and_segments( + cls, + f_clock: int, + nom_bitrate: int, + nom_tseg1: int, + nom_tseg2: int, + nom_sjw: int, + data_bitrate: int, + data_tseg1: int, + data_tseg2: int, + data_sjw: int, + ) -> "BitTimingFd": + """ + Create a :class:`~can.BitTimingFd` instance with the bitrates and segments lengths. + + :param int f_clock: + The CAN system clock frequency in Hz. + :param int nom_bitrate: + Nominal (arbitration) phase bitrate in bit/s. + :param int nom_tseg1: + Nominal phase Time segment 1, that is, the number of quanta from (but not including) + the Sync Segment to the sampling point. + :param int nom_tseg2: + Nominal phase Time segment 2, that is, the number of quanta from the sampling + point to the end of the bit. + :param int nom_sjw: + The Synchronization Jump Width for the nominal phase. This value determines + the maximum number of time quanta that the controller can resynchronize every bit. + :param int data_bitrate: + Data phase bitrate in bit/s. + :param int data_tseg1: + Data phase Time segment 1, that is, the number of quanta from (but not including) + the Sync Segment to the sampling point. + :param int data_tseg2: + Data phase Time segment 2, that is, the number of quanta from the sampling + point to the end of the bit. + :param int data_sjw: + The Synchronization Jump Width for the data phase. This value determines + the maximum number of time quanta that the controller can resynchronize every bit. + :raises ValueError: + if the arguments are invalid. + """ try: - segments.append(f"SJW: {self.sjw}") - except ValueError: - pass + nom_brp = int(round(f_clock / (nom_bitrate * (1 + nom_tseg1 + nom_tseg2)))) + data_brp = int( + round(f_clock / (data_bitrate * (1 + data_tseg1 + data_tseg2))) + ) + except ZeroDivisionError: + raise ValueError("Invalid inputs.") from None + + bt = cls( + f_clock=f_clock, + nom_brp=nom_brp, + nom_tseg1=nom_tseg1, + nom_tseg2=nom_tseg2, + nom_sjw=nom_sjw, + data_brp=data_brp, + data_tseg1=data_tseg1, + data_tseg2=data_tseg2, + data_sjw=data_sjw, + ) + + if abs(bt.nom_bitrate - nom_bitrate) > nom_bitrate / 256: + raise ValueError( + f"the effective nom. bitrate (={bt.nom_bitrate}) diverges " + f"from the requested nom. bitrate (={nom_bitrate})" + ) + + if abs(bt.data_bitrate - data_bitrate) > data_bitrate / 256: + raise ValueError( + f"the effective data bitrate (={bt.data_bitrate}) diverges " + f"from the requested data bitrate (={data_bitrate})" + ) + + return bt + + @classmethod + def from_sample_point( + cls, + f_clock: int, + nom_bitrate: int, + nom_sample_point: float, + data_bitrate: int, + data_sample_point: float, + ) -> "BitTimingFd": + """Create a :class:`~can.BitTimingFd` instance for a given nominal/data sample point pair. + + This function tries to find bit timings, which are close to the requested + sample points. It does not take physical bus properties into account, so the + calculated bus timings might not work properly for you. + + The :func:`oscillator_tolerance` function might be helpful to evaluate the + bus timings. + + :param int f_clock: + The CAN system clock frequency in Hz. + :param int nom_bitrate: + Nominal bitrate in bit/s. + :param int nom_sample_point: + The sample point value of the arbitration phase in percent. + :param int data_bitrate: + Data bitrate in bit/s. + :param int data_sample_point: + The sample point value of the data phase in percent. + :raises ValueError: + if the arguments are invalid. + """ + if nom_sample_point < 50.0: + raise ValueError( + f"nom_sample_point (={nom_sample_point}) must not be below 50%." + ) + + if data_sample_point < 50.0: + raise ValueError( + f"data_sample_point (={data_sample_point}) must not be below 50%." + ) + + possible_solutions: List[BitTimingFd] = [] + + for nom_brp in range(1, 257): + nbt = round(int(f_clock / (nom_bitrate * nom_brp))) + if nbt < 8: + break + + effective_nom_bitrate = f_clock / (nbt * nom_brp) + if abs(effective_nom_bitrate - nom_bitrate) > nom_bitrate / 256: + continue + + nom_tseg1 = int(round(nom_sample_point / 100 * nbt)) - 1 + # limit tseg1, so tseg2 is at least 1 TQ + nom_tseg1 = min(nom_tseg1, nbt - 2) + nom_tseg2 = nbt - nom_tseg1 - 1 + + nom_sjw = min(nom_tseg2, 128) + + for data_brp in range(1, 257): + dbt = round(int(f_clock / (data_bitrate * data_brp))) + if dbt < 8: + break + + effective_data_bitrate = f_clock / (dbt * data_brp) + if abs(effective_data_bitrate - data_bitrate) > data_bitrate / 256: + continue + + data_tseg1 = int(round(data_sample_point / 100 * dbt)) - 1 + # limit tseg1, so tseg2 is at least 1 TQ + data_tseg1 = min(data_tseg1, dbt - 2) + data_tseg2 = dbt - data_tseg1 - 1 + + data_sjw = min(data_tseg2, 16) + + try: + bt = BitTimingFd( + f_clock=f_clock, + nom_brp=nom_brp, + nom_tseg1=nom_tseg1, + nom_tseg2=nom_tseg2, + nom_sjw=nom_sjw, + data_brp=data_brp, + data_tseg1=data_tseg1, + data_tseg2=data_tseg2, + data_sjw=data_sjw, + ) + possible_solutions.append(bt) + except ValueError: + continue + + if not possible_solutions: + raise ValueError("No suitable bit timings found.") + + # prefer using the same prescaler for arbitration and data phase + same_prescaler = list( + filter(lambda x: x.nom_brp == x.data_brp, possible_solutions) + ) + if same_prescaler: + possible_solutions = same_prescaler + + # sort solutions + for key, reverse in ( + # prefer low prescaler + (lambda x: x.nom_brp + x.data_brp, False), + # prefer same prescaler for arbitration and data + (lambda x: abs(x.nom_brp - x.data_brp), False), + # prefer low sample point deviation from requested values + ( + lambda x: ( + abs(x.nom_sample_point - nom_sample_point) + + abs(x.data_sample_point - data_sample_point) + ), + False, + ), + ): + possible_solutions.sort(key=key, reverse=reverse) + + return possible_solutions[0] + + @property + def f_clock(self) -> int: + """The CAN system clock frequency in Hz.""" + return self._data["f_clock"] + + @property + def nom_bitrate(self) -> int: + """Nominal (arbitration phase) bitrate.""" + return int(round(self.f_clock / (self.nbt * self.nom_brp))) + + @property + def nom_brp(self) -> int: + """Prescaler value for the arbitration phase.""" + return self._data["nom_brp"] + + @property + def nom_tq(self) -> int: + """Nominal time quantum in nanoseconds""" + return int(round(self.nom_brp / self.f_clock * 1e9)) + + @property + def nbt(self) -> int: + """Number of time quanta in a bit of the arbitration phase.""" + return 1 + self.nom_tseg1 + self.nom_tseg2 + + @property + def nom_tseg1(self) -> int: + """Time segment 1 value of the arbitration phase. + + This is the sum of the propagation time segment and the phase buffer segment 1. + """ + return self._data["nom_tseg1"] + + @property + def nom_tseg2(self) -> int: + """Time segment 2 value of the arbitration phase. Also known as phase buffer segment 2.""" + return self._data["nom_tseg2"] + + @property + def nom_sjw(self) -> int: + """Synchronization jump width of the arbitration phase. + + The phase buffer segments may be shortened or lengthened by this value. + """ + return self._data["nom_sjw"] + + @property + def nom_sample_point(self) -> float: + """Sample point of the arbitration phase in percent.""" + return 100.0 * (1 + self.nom_tseg1) / (1 + self.nom_tseg1 + self.nom_tseg2) + + @property + def data_bitrate(self) -> int: + """Bitrate of the data phase in bit/s.""" + return int(round(self.f_clock / (self.dbt * self.data_brp))) + + @property + def data_brp(self) -> int: + """Prescaler value for the data phase.""" + return self._data["data_brp"] + + @property + def data_tq(self) -> int: + """Data time quantum in nanoseconds""" + return int(round(self.data_brp / self.f_clock * 1e9)) + + @property + def dbt(self) -> int: + """Number of time quanta in a bit of the data phase.""" + return 1 + self.data_tseg1 + self.data_tseg2 + + @property + def data_tseg1(self) -> int: + """TSEG1 value of the data phase. + + This is the sum of the propagation time segment and the phase buffer segment 1. + """ + return self._data["data_tseg1"] + + @property + def data_tseg2(self) -> int: + """TSEG2 value of the data phase. Also known as phase buffer segment 2.""" + return self._data["data_tseg2"] + + @property + def data_sjw(self) -> int: + """Synchronization jump width of the data phase. + + The phase buffer segments may be shortened or lengthened by this value. + """ + return self._data["data_sjw"] + + @property + def data_sample_point(self) -> float: + """Sample point of the data phase in percent.""" + return 100.0 * (1 + self.data_tseg1) / (1 + self.data_tseg1 + self.data_tseg2) + + def oscillator_tolerance( + self, + node_loop_delay_ns: float = 250.0, + bus_length_m: float = 10.0, + ) -> float: + """Oscillator tolerance in percent according to ISO 11898-1. + + :param float node_loop_delay_ns: + Transceiver loop delay in nanoseconds. + :param float bus_length_m: + Bus length in meters. + """ + delay_per_meter = 5 + bidirectional_propagation_delay_ns = 2 * ( + node_loop_delay_ns + delay_per_meter * bus_length_m + ) + + prop_seg = math.ceil(bidirectional_propagation_delay_ns / self.nom_tq) + nom_phase_seg1 = self.nom_tseg1 - prop_seg + nom_phase_seg2 = self.nom_tseg2 + + data_phase_seg2 = self.data_tseg2 + + df_clock_list = [ + _oscillator_tolerance_condition_1(nom_sjw=self.nom_sjw, nbt=self.nbt), + _oscillator_tolerance_condition_2( + nbt=self.nbt, + nom_phase_seg1=nom_phase_seg1, + nom_phase_seg2=nom_phase_seg2, + ), + _oscillator_tolerance_condition_3(data_sjw=self.data_sjw, dbt=self.dbt), + _oscillator_tolerance_condition_4( + nom_phase_seg1=nom_phase_seg1, + nom_phase_seg2=nom_phase_seg2, + data_phase_seg2=data_phase_seg2, + nbt=self.nbt, + dbt=self.dbt, + data_brp=self.data_brp, + nom_brp=self.nom_brp, + ), + _oscillator_tolerance_condition_5( + data_sjw=self.data_sjw, + data_brp=self.data_brp, + nom_brp=self.nom_brp, + data_phase_seg2=data_phase_seg2, + nom_phase_seg2=nom_phase_seg2, + nbt=self.nbt, + dbt=self.dbt, + ), + ] + return max(0.0, min(df_clock_list) * 100) + + def recreate_with_f_clock(self, f_clock: int) -> "BitTimingFd": + """Return a new :class:`~can.BitTimingFd` instance with the given *f_clock* but the same + bit rates and sample points. + + :param int f_clock: + The CAN system clock frequency in Hz. + :raises ValueError: + if no suitable bit timings were found. + """ + # try the most simple solution first: another bitrate prescaler try: - segments.append(f"BTR: {self.btr0:02X}{self.btr1:02X}h") + return BitTimingFd.from_bitrate_and_segments( + f_clock=f_clock, + nom_bitrate=self.nom_bitrate, + nom_tseg1=self.nom_tseg1, + nom_tseg2=self.nom_tseg2, + nom_sjw=self.nom_sjw, + data_bitrate=self.data_bitrate, + data_tseg1=self.data_tseg1, + data_tseg2=self.data_tseg2, + data_sjw=self.data_sjw, + ) except ValueError: pass + + # create a new timing instance with the same sample points + bt = BitTimingFd.from_sample_point( + f_clock=f_clock, + nom_bitrate=self.nom_bitrate, + nom_sample_point=self.nom_sample_point, + data_bitrate=self.data_bitrate, + data_sample_point=self.data_sample_point, + ) + if ( + abs(bt.nom_sample_point - self.nom_sample_point) > 1.0 + or abs(bt.data_sample_point - self.data_sample_point) > 1.0 + ): + raise ValueError( + "f_clock change failed because of sample point discrepancy." + ) + # adapt synchronization jump width, so it has the same size relative to bit time as self + nom_sjw = int(round(self.nom_sjw / self.nbt * bt.nbt)) + nom_sjw = max(1, min(bt.nom_tseg2, nom_sjw)) + bt._data["nom_sjw"] = nom_sjw # pylint: disable=protected-access + data_sjw = int(round(self.data_sjw / self.dbt * bt.dbt)) + data_sjw = max(1, min(bt.data_tseg2, data_sjw)) + bt._data["data_sjw"] = data_sjw # pylint: disable=protected-access + bt._validate() # pylint: disable=protected-access + return bt + + def __str__(self) -> str: + segments = [ + f"NBR: {self.nom_bitrate} bit/s", + f"NSP: {self.nom_sample_point:.2f}%", + f"NBRP: {self.nom_brp}", + f"NTSEG1: {self.nom_tseg1}", + f"NTSEG2: {self.nom_tseg2}", + f"NSJW: {self.nom_sjw}", + f"DBR: {self.data_bitrate} bit/s", + f"DSP: {self.data_sample_point:.2f}%", + f"DBRP: {self.data_brp}", + f"DTSEG1: {self.data_tseg1}", + f"DTSEG2: {self.data_tseg2}", + f"DSJW: {self.data_sjw}", + f"f_clock: {self.f_clock / 1e6:.0f}MHz", + ] return ", ".join(segments) def __repr__(self) -> str: - kwargs = {} - if self._f_clock: - kwargs["f_clock"] = self._f_clock - if self._bitrate: - kwargs["bitrate"] = self._bitrate - if self._brp: - kwargs["brp"] = self._brp - if self._tseg1: - kwargs["tseg1"] = self._tseg1 - if self._tseg2: - kwargs["tseg2"] = self._tseg2 - if self._sjw: - kwargs["sjw"] = self._sjw - if self._nof_samples != 1: - kwargs["nof_samples"] = self._nof_samples - args = ", ".join(f"{key}={value}" for key, value in kwargs.items()) - return f"can.BitTiming({args})" + args = ", ".join(f"{key}={value}" for key, value in self.items()) + return f"can.{self.__class__.__name__}({args})" + + def __getitem__(self, key: str) -> int: + return cast(int, self._data.__getitem__(key)) + + def __len__(self) -> int: + return self._data.__len__() + + def __iter__(self) -> Iterator[str]: + return self._data.__iter__() + + def __eq__(self, other: object) -> bool: + if not isinstance(other, BitTimingFd): + return False + + return self._data == other._data + + +def _oscillator_tolerance_condition_1(nom_sjw: int, nbt: int) -> float: + """Arbitration phase - resynchronization""" + return nom_sjw / (2 * 10 * nbt) + + +def _oscillator_tolerance_condition_2( + nbt: int, nom_phase_seg1: int, nom_phase_seg2: int +) -> float: + """Arbitration phase - sampling of bit after error flag""" + return min(nom_phase_seg1, nom_phase_seg2) / (2 * (13 * nbt - nom_phase_seg2)) + + +def _oscillator_tolerance_condition_3(data_sjw: int, dbt: int) -> float: + """Data phase - resynchronization""" + return data_sjw / (2 * 10 * dbt) + + +def _oscillator_tolerance_condition_4( + nom_phase_seg1: int, + nom_phase_seg2: int, + data_phase_seg2: int, + nbt: int, + dbt: int, + data_brp: int, + nom_brp: int, +) -> float: + """Data phase - sampling of bit after error flag""" + return min(nom_phase_seg1, nom_phase_seg2) / ( + 2 * ((6 * dbt - data_phase_seg2) * data_brp / nom_brp + 7 * nbt) + ) + + +def _oscillator_tolerance_condition_5( + data_sjw: int, + data_brp: int, + nom_brp: int, + nom_phase_seg2: int, + data_phase_seg2: int, + nbt: int, + dbt: int, +) -> float: + """Data phase - bit rate switch""" + max_correctable_phase_shift = data_sjw - max(0.0, nom_brp / data_brp - 1) + time_between_resync = 2 * ( + (2 * nbt - nom_phase_seg2) * nom_brp / data_brp + data_phase_seg2 + 4 * dbt + ) + return max_correctable_phase_shift / time_between_resync diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 395e4b399..7150a60bd 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,24 +1,29 @@ import collections from ctypes import c_ubyte import logging -import canalystii as driver import time -import warnings from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union -from can import BitTiming, BusABC, Message -from can.exceptions import CanTimeoutError + +from can import BitTiming, BusABC, Message, BitTimingFd +from can.exceptions import CanTimeoutError, CanInitializationError from can.typechecking import CanFilters +from can.util import deprecated_args_alias, check_or_adjust_timing_clock + +import canalystii as driver logger = logging.getLogger(__name__) class CANalystIIBus(BusABC): + @deprecated_args_alias( + deprecation_start="4.2.0", deprecation_end="5.0.0", bit_timing="timing" + ) def __init__( self, channel: Union[int, Sequence[int], str] = (0, 1), device: int = 0, bitrate: Optional[int] = None, - bit_timing: Optional[BitTiming] = None, + timing: Optional[Union[BitTiming, BitTimingFd]] = None, can_filters: Optional[CanFilters] = None, rx_queue_size: Optional[int] = None, **kwargs: Dict[str, Any], @@ -33,9 +38,12 @@ def __init__( Optional USB device number. Default is 0 (first device found). :param bitrate: CAN bitrate in bits/second. Required unless the bit_timing argument is set. - :param bit_timing: - Optional BitTiming instance to use for custom bit timing setting. - If this argument is set then it overrides the bitrate argument. + :param timing: + Optional :class:`~can.BitTiming` instance to use for custom bit timing setting. + If this argument is set then it overrides the bitrate argument. The + `f_clock` value of the timing instance must be set to 8_000_000 (8MHz) + for standard CAN. + CAN FD and the :class:`~can.BitTimingFd` class are not supported. :param can_filters: Optional filters for received CAN messages. :param rx_queue_size: @@ -44,8 +52,8 @@ def __init__( """ super().__init__(channel=channel, can_filters=can_filters, **kwargs) - if not (bitrate or bit_timing): - raise ValueError("Either bitrate or bit_timing argument is required") + if not (bitrate or timing): + raise ValueError("Either bitrate or timing argument is required") if isinstance(channel, str): # Assume comma separated string of channels @@ -63,17 +71,12 @@ def __init__( self.device = driver.CanalystDevice(device_index=device) for channel in self.channels: - if bit_timing: - try: - if bit_timing.f_clock != 8_000_000: - warnings.warn( - f"bit_timing.f_clock value {bit_timing.f_clock} " - "doesn't match expected device f_clock 8MHz." - ) - except ValueError: - pass # f_clock not specified - self.device.init( - channel, timing0=bit_timing.btr0, timing1=bit_timing.btr1 + if isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000]) + self.device.init(channel, timing0=timing.btr0, timing1=timing.btr1) + elif isinstance(timing, BitTimingFd): + raise NotImplementedError( + f"CAN FD is not supported by {self.__class__.__name__}." ) else: self.device.init(channel, bitrate=bitrate) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index d735b7ee3..20e4d0cb7 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -4,14 +4,16 @@ import time import logging +from typing import Optional, Union, Any from unittest.mock import Mock -from can import BusABC, Message +from can import BusABC, Message, BitTiming, BitTimingFd from ..exceptions import ( CanInitializationError, CanInterfaceNotImplementedError, error_check, ) +from ..util import deprecated_args_alias, check_or_adjust_timing_clock logger = logging.getLogger(__name__) @@ -42,16 +44,18 @@ def _detect_available_configs(): channels.append({"interface": "cantact", "channel": f"ch:{i}"}) return channels + @deprecated_args_alias( + deprecation_start="4.2.0", deprecation_end="5.0.0", bit_timing="timing" + ) def __init__( self, - channel, - bitrate=500000, - poll_interval=0.01, - monitor=False, - bit_timing=None, - _testing=False, - **kwargs, - ): + channel: int, + bitrate: int = 500_000, + poll_interval: float = 0.01, + monitor: bool = False, + timing: Optional[Union[BitTiming, BitTimingFd]] = None, + **kwargs: Any, + ) -> None: """ :param int channel: Channel number (zero indexed, labeled on multi-channel devices) @@ -59,16 +63,21 @@ def __init__( Bitrate in bits/s :param bool monitor: If true, operate in listen-only monitoring mode - :param BitTiming bit_timing: - Optional BitTiming to use for custom bit timing setting. Overrides bitrate if not None. + :param timing: + Optional :class:`~can.BitTiming` instance to use for custom bit timing setting. + If this argument is set then it overrides the bitrate argument. The + `f_clock` value of the timing instance must be set to 24_000_000 (24MHz) + for standard CAN. + CAN FD and the :class:`~can.BitTimingFd` class are not supported. """ - if _testing: + if kwargs.get("_testing", False): self.interface = MockInterface() else: if cantact is None: raise CanInterfaceNotImplementedError( - "The CANtact module is not installed. Install it using `python -m pip install cantact`" + "The CANtact module is not installed. " + "Install it using `python -m pip install cantact`" ) with error_check( "Cannot create the cantact.Interface", CanInitializationError @@ -80,18 +89,25 @@ def __init__( # Configure the interface with error_check("Cannot setup the cantact.Interface", CanInitializationError): - if bit_timing is None: - # use bitrate - self.interface.set_bitrate(int(channel), int(bitrate)) - else: + if isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, valid_clocks=[24_000_000]) + # use custom bit timing self.interface.set_bit_timing( int(channel), - int(bit_timing.brp), - int(bit_timing.tseg1), - int(bit_timing.tseg2), - int(bit_timing.sjw), + int(timing.brp), + int(timing.tseg1), + int(timing.tseg2), + int(timing.sjw), ) + elif isinstance(timing, BitTimingFd): + raise NotImplementedError( + f"CAN FD is not supported by {self.__class__.__name__}." + ) + else: + # use bitrate + self.interface.set_bitrate(int(channel), int(bitrate)) + self.interface.set_enabled(int(channel), True) self.interface.set_monitor(int(channel), monitor) self.interface.start() diff --git a/can/typechecking.py b/can/typechecking.py index 7aa4f7e5c..dc5c22270 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -48,3 +48,24 @@ class AutoDetectedConfig(typing_extensions.TypedDict): ReadableBytesLike = typing.Union[bytes, bytearray, memoryview] + + +class BitTimingDict(typing_extensions.TypedDict): + f_clock: int + brp: int + tseg1: int + tseg2: int + sjw: int + nof_samples: int + + +class BitTimingFdDict(typing_extensions.TypedDict): + f_clock: int + nom_brp: int + nom_tseg1: int + nom_tseg2: int + nom_sjw: int + data_brp: int + data_tseg1: int + data_tseg2: int + data_sjw: int diff --git a/can/util.py b/can/util.py index e4a45f2d5..8f5cea0c4 100644 --- a/can/util.py +++ b/can/util.py @@ -1,23 +1,35 @@ """ Utilities and configuration file parsing. """ - +import copy import functools -import warnings -from typing import Any, Callable, cast, Dict, Iterable, Tuple, Optional, Union -from time import time, perf_counter, get_clock_info import json +import logging import os import os.path import platform import re -import logging +import warnings from configparser import ConfigParser +from time import time, perf_counter, get_clock_info +from typing import ( + Any, + Callable, + cast, + Dict, + Iterable, + Tuple, + Optional, + Union, + TypeVar, +) import can -from .interfaces import VALID_INTERFACES from . import typechecking +from .bit_timing import BitTiming, BitTimingFd +from .exceptions import CanInitializationError from .exceptions import CanInterfaceNotImplementedError +from .interfaces import VALID_INTERFACES log = logging.getLogger("can.util") @@ -226,6 +238,25 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: if not 0 < port < 65535: raise ValueError("Port config must be inside 0-65535 range!") + if config.get("timing", None) is None: + try: + if set(typechecking.BitTimingFdDict.__annotations__).issubset(config): + config["timing"] = can.BitTimingFd( + **{ + key: int(config[key]) + for key in typechecking.BitTimingFdDict.__annotations__ + } + ) + elif set(typechecking.BitTimingDict.__annotations__).issubset(config): + config["timing"] = can.BitTiming( + **{ + key: int(config[key]) + for key in typechecking.BitTimingDict.__annotations__ + } + ) + except (ValueError, TypeError): + pass + if "bitrate" in config: config["bitrate"] = int(config["bitrate"]) if "fd" in config: @@ -343,6 +374,49 @@ def wrapper(*args, **kwargs): return deco +T = TypeVar("T", BitTiming, BitTimingFd) + + +def check_or_adjust_timing_clock(timing: T, valid_clocks: Iterable[int]) -> T: + """Adjusts the given timing instance to have an *f_clock* value that is within the + allowed values specified by *valid_clocks*. If the *f_clock* value of timing is + already within *valid_clocks*, then *timing* is returned unchanged. + + :param timing: + The :class:`~can.BitTiming` or :class:`~can.BitTimingFd` instance to adjust. + :param valid_clocks: + An iterable of integers representing the valid *f_clock* values that the timing instance + can be changed to. The order of the values in *valid_clocks* determines the priority in + which they are tried, with earlier values being tried before later ones. + :return: + A new :class:`~can.BitTiming` or :class:`~can.BitTimingFd` instance with an + *f_clock* value within *valid_clocks*. + :raises ~can.exceptions.CanInitializationError: + If no compatible *f_clock* value can be found within *valid_clocks*. + """ + if timing.f_clock in valid_clocks: + # create a copy so this function always returns a new instance + return copy.deepcopy(timing) + + for clock in valid_clocks: + try: + # Try to use a different f_clock + adjusted_timing = timing.recreate_with_f_clock(clock) + warnings.warn( + f"Adjusted f_clock in {timing.__class__.__name__} from " + f"{timing.f_clock} to {adjusted_timing.f_clock}" + ) + return adjusted_timing + except ValueError: + pass + + raise CanInitializationError( + f"The specified timing.f_clock value {timing.f_clock} " + f"doesn't match any of the allowed device f_clock values: " + f"{', '.join([str(f) for f in valid_clocks])}" + ) from None + + def _rename_kwargs( func_name: str, start: str, diff --git a/doc/bit_timing.rst b/doc/bit_timing.rst index e1d2feeeb..b48a133b8 100644 --- a/doc/bit_timing.rst +++ b/doc/bit_timing.rst @@ -1,49 +1,112 @@ Bit Timing Configuration ======================== -The CAN protocol allows the bitrate, sample point and number of samples to be -optimized for a given application. You can read more on Wikipedia_, Kvaser_ -and other sources. - -In most cases the recommended settings for a predefined set of common -bitrates will work just fine. In some cases it may however be necessary to specify -custom settings. The :class:`can.BitTiming` class can be used for this purpose to -specify them in a relatively interface agnostic manner. - -It is also possible to specify the same settings for a CAN 2.0 bus +.. attention:: + This feature is experimental. The implementation might change in future + versions. + +The CAN protocol, specified in ISO 11898, allows the bitrate, sample point +and number of samples to be optimized for a given application. These +parameters, known as bit timings, can be adjusted to meet the requirements +of the communication system and the physical communication channel. + +These parameters include: + +* **tseg1**: The time segment 1 (TSEG1) is the amount of time from the end + of the sync segment until the sample point. It is expressed in time quanta (TQ). +* **tseg2**: The time segment 2 (TSEG2) is the amount of time from the + sample point until the end of the bit. It is expressed in TQ. +* **sjw**: The synchronization jump width (SJW) is the maximum number + of TQ that the controller can resynchronize every bit. +* **sample point**: The sample point is defined as the point in time + within a bit where the bus controller samples the bus for dominant or + recessive levels. It is typically expressed as a percentage of the bit time. + The sample point depends on the bus length and propagation time as well + as the information processing time of the nodes. + +.. figure:: images/bit_timing_light.svg + :align: center + :class: only-light + +.. figure:: images/bit_timing_dark.svg + :align: center + :class: only-dark + + Bit Timing and Sample Point + + +For example, consider a bit with a total duration of 8 TQ and a sample +point at 75%. The values for TSEG1, TSEG2 and SJW would be 5, 2, and 2, +respectively. The sample point would be 6 TQ after the start of the bit, +leaving 2 TQ for the information processing by the bus nodes. + +.. note:: + The values for TSEG1, TSEG2 and SJW are chosen such that the + sample point is at least 50% of the total bit time. This ensures that + there is sufficient time for the signal to stabilize before it is sampled. + +.. note:: + In CAN FD, the arbitration (nominal) phase and the data phase can have + different bit rates. As a result, there are two separate sample points + to consider. + +Another important parameter is **f_clock**: The CAN system clock frequency +in Hz. This frequency is used to derive the TQ size from the bit rate. +The relationship is ``f_clock = (tseg1+tseg2+1) * bitrate * brp``. +The bit rate prescaler value **brp** is usually determined by the controller +and is chosen to ensure that the resulting bit time is an integer value. +Typical CAN clock frequencies are 8-80 MHz. + +In most cases, the recommended settings for a predefined set of common +bit rates will work just fine. In some cases, however, it may be necessary +to specify custom bit timings. The :class:`~can.BitTiming` and +:class:`~can.BitTimingFd` classes can be used for this purpose to specify +bit timings in a relatively interface agnostic manner. + +It is possible to specify CAN 2.0 bit timings using the config file: .. code-block:: none [default] - bitrate=1000000 f_clock=8000000 - tseg1=5 - tseg2=2 - sjw=1 - nof_samples=1 - - -.. code-block:: none - - [default] brp=1 tseg1=5 tseg2=2 sjw=1 nof_samples=1 +The same is possible for CAN FD: .. code-block:: none [default] - btr0=0x00 - btr1=0x14 + f_clock=80000000 + nom_brp=1 + nom_tseg1=119 + nom_tseg2=40 + nom_sjw=40 + data_brp=1 + data_tseg1=29 + data_tseg2=10 + data_sjw=10 +A :class:`dict` of the relevant config parameters can be easily obtained by calling +``dict(timing)`` or ``{**timing}`` where ``timing`` is the :class:`~can.BitTiming` or +:class:`~can.BitTimingFd` instance. -.. autoclass:: can.BitTiming +Check :doc:`configuration` for more information about saving and loading configurations. -.. _Wikipedia: https://en.wikipedia.org/wiki/CAN_bus#Bit_timing -.. _Kvaser: https://www.kvaser.com/about-can/the-can-protocol/can-bit-timing/ +.. autoclass:: can.BitTiming + :class-doc-from: both + :show-inheritance: + :members: + :member-order: bysource + +.. autoclass:: can.BitTimingFd + :class-doc-from: both + :show-inheritance: + :members: + :member-order: bysource diff --git a/doc/conf.py b/doc/conf.py index de08fc300..6ba661a30 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -120,10 +120,12 @@ # disable specific warnings nitpick_ignore = [ # Ignore warnings for type aliases. Remove once Sphinx supports PEP613 + ("py:class", "BusConfig"), ("py:class", "can.typechecking.BusConfig"), ("py:class", "can.typechecking.CanFilter"), ("py:class", "can.typechecking.CanFilterExtended"), ("py:class", "can.typechecking.AutoDetectedConfig"), + ("py:class", "can.util.T"), # intersphinx fails to reference some builtins ("py:class", "asyncio.events.AbstractEventLoop"), ("py:class", "_thread.allocate_lock"), diff --git a/doc/images/bit_timing_dark.svg b/doc/images/bit_timing_dark.svg new file mode 100644 index 000000000..cc54a3f51 --- /dev/null +++ b/doc/images/bit_timing_dark.svg @@ -0,0 +1,96 @@ + + + + + + + + + sync_seg + + prop_seg + + phase_seg1 + + phase_seg2 + + + Nominal Bit Time + + + + + + + tseg1 + + + tseg2 + + + 1 TQ + + + + 75% Sample Point + + + \ No newline at end of file diff --git a/doc/images/bit_timing_light.svg b/doc/images/bit_timing_light.svg new file mode 100644 index 000000000..eb021ea34 --- /dev/null +++ b/doc/images/bit_timing_light.svg @@ -0,0 +1,92 @@ + + + + + + + + + sync_seg + + prop_seg + + phase_seg1 + + phase_seg2 + + + Nominal Bit Time + + + + + + + tseg1 + + + tseg2 + + + 1 TQ + + + + 75% Sample Point + + + \ No newline at end of file diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py index 2a9b1ac79..669308f9d 100644 --- a/test/test_bit_timing.py +++ b/test/test_bit_timing.py @@ -1,15 +1,20 @@ #!/usr/bin/env python +import struct + +import pytest + import can +from can.interfaces.pcan.pcan import PCAN_BITRATES def test_sja1000(): """Test some values obtained using other bit timing calculators.""" timing = can.BitTiming( - f_clock=8000000, bitrate=125000, tseg1=11, tseg2=4, sjw=2, nof_samples=3 + f_clock=8_000_000, brp=4, tseg1=11, tseg2=4, sjw=2, nof_samples=3 ) - assert timing.f_clock == 8000000 - assert timing.bitrate == 125000 + assert timing.f_clock == 8_000_000 + assert timing.bitrate == 125_000 assert timing.brp == 4 assert timing.nbt == 16 assert timing.tseg1 == 11 @@ -20,9 +25,9 @@ def test_sja1000(): assert timing.btr0 == 0x43 assert timing.btr1 == 0xBA - timing = can.BitTiming(f_clock=8000000, bitrate=500000, tseg1=13, tseg2=2, sjw=1) - assert timing.f_clock == 8000000 - assert timing.bitrate == 500000 + timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=13, tseg2=2, sjw=1) + assert timing.f_clock == 8_000_000 + assert timing.bitrate == 500_000 assert timing.brp == 1 assert timing.nbt == 16 assert timing.tseg1 == 13 @@ -33,9 +38,9 @@ def test_sja1000(): assert timing.btr0 == 0x00 assert timing.btr1 == 0x1C - timing = can.BitTiming(f_clock=8000000, bitrate=1000000, tseg1=5, tseg2=2, sjw=1) - assert timing.f_clock == 8000000 - assert timing.bitrate == 1000000 + timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1) + assert timing.f_clock == 8_000_000 + assert timing.bitrate == 1_000_000 assert timing.brp == 1 assert timing.nbt == 8 assert timing.tseg1 == 5 @@ -47,35 +52,116 @@ def test_sja1000(): assert timing.btr1 == 0x14 -def test_can_fd(): - timing = can.BitTiming( - f_clock=80000000, bitrate=500000, tseg1=119, tseg2=40, sjw=40 +def test_from_bitrate_and_segments(): + timing = can.BitTiming.from_bitrate_and_segments( + f_clock=8_000_000, bitrate=125_000, tseg1=11, tseg2=4, sjw=2, nof_samples=3 ) - assert timing.f_clock == 80000000 - assert timing.bitrate == 500000 - assert timing.brp == 1 - assert timing.nbt == 160 - assert timing.tseg1 == 119 - assert timing.tseg2 == 40 - assert timing.sjw == 40 + assert timing.f_clock == 8_000_000 + assert timing.bitrate == 125_000 + assert timing.brp == 4 + assert timing.nbt == 16 + assert timing.tseg1 == 11 + assert timing.tseg2 == 4 + assert timing.sjw == 2 + assert timing.nof_samples == 3 assert timing.sample_point == 75 + assert timing.btr0 == 0x43 + assert timing.btr1 == 0xBA - timing = can.BitTiming( - f_clock=80000000, bitrate=2000000, tseg1=29, tseg2=10, sjw=10 + timing = can.BitTiming.from_bitrate_and_segments( + f_clock=8_000_000, bitrate=500_000, tseg1=13, tseg2=2, sjw=1 + ) + assert timing.f_clock == 8_000_000 + assert timing.bitrate == 500_000 + assert timing.brp == 1 + assert timing.nbt == 16 + assert timing.tseg1 == 13 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + assert timing.nof_samples == 1 + assert timing.sample_point == 87.5 + assert timing.btr0 == 0x00 + assert timing.btr1 == 0x1C + + timing = can.BitTiming.from_bitrate_and_segments( + f_clock=8_000_000, bitrate=1_000_000, tseg1=5, tseg2=2, sjw=1 ) - assert timing.f_clock == 80000000 - assert timing.bitrate == 2000000 + assert timing.f_clock == 8_000_000 + assert timing.bitrate == 1_000_000 assert timing.brp == 1 - assert timing.nbt == 40 - assert timing.tseg1 == 29 - assert timing.tseg2 == 10 - assert timing.sjw == 10 + assert timing.nbt == 8 + assert timing.tseg1 == 5 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + assert timing.nof_samples == 1 assert timing.sample_point == 75 + assert timing.btr0 == 0x00 + assert timing.btr1 == 0x14 + + timing = can.BitTimingFd.from_bitrate_and_segments( + f_clock=80_000_000, + nom_bitrate=500_000, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_bitrate=2_000_000, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + + assert timing.f_clock == 80_000_000 + assert timing.nom_bitrate == 500_000 + assert timing.nom_brp == 1 + assert timing.nbt == 160 + assert timing.nom_tseg1 == 119 + assert timing.nom_tseg2 == 40 + assert timing.nom_sjw == 40 + assert timing.nom_sample_point == 75 + assert timing.f_clock == 80_000_000 + assert timing.data_bitrate == 2_000_000 + assert timing.data_brp == 1 + assert timing.dbt == 40 + assert timing.data_tseg1 == 29 + assert timing.data_tseg2 == 10 + assert timing.data_sjw == 10 + assert timing.data_sample_point == 75 + + +def test_can_fd(): + timing = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + + assert timing.f_clock == 80_000_000 + assert timing.nom_bitrate == 500_000 + assert timing.nom_brp == 1 + assert timing.nbt == 160 + assert timing.nom_tseg1 == 119 + assert timing.nom_tseg2 == 40 + assert timing.nom_sjw == 40 + assert timing.nom_sample_point == 75 + assert timing.f_clock == 80_000_000 + assert timing.data_bitrate == 2_000_000 + assert timing.data_brp == 1 + assert timing.dbt == 40 + assert timing.data_tseg1 == 29 + assert timing.data_tseg2 == 10 + assert timing.data_sjw == 10 + assert timing.data_sample_point == 75 def test_from_btr(): - timing = can.BitTiming(f_clock=8000000, btr0=0x00, btr1=0x14) - assert timing.bitrate == 1000000 + timing = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14) + assert timing.bitrate == 1_000_000 assert timing.brp == 1 assert timing.nbt == 8 assert timing.tseg1 == 5 @@ -86,9 +172,254 @@ def test_from_btr(): assert timing.btr1 == 0x14 +def test_btr_persistence(): + f_clock = 8_000_000 + for btr0btr1 in PCAN_BITRATES.values(): + btr1, btr0 = struct.unpack("BB", btr0btr1) + + t = can.BitTiming.from_registers(f_clock, btr0, btr1) + assert t.btr0 == btr0 + assert t.btr1 == btr1 + + +def test_from_sample_point(): + timing = can.BitTiming.from_sample_point( + f_clock=16_000_000, + bitrate=500_000, + sample_point=69.0, + ) + assert timing.f_clock == 16_000_000 + assert timing.bitrate == 500_000 + assert 68 < timing.sample_point < 70 + + fd_timing = can.BitTimingFd.from_sample_point( + f_clock=80_000_000, + nom_bitrate=1_000_000, + nom_sample_point=75.0, + data_bitrate=8_000_000, + data_sample_point=70.0, + ) + assert fd_timing.f_clock == 80_000_000 + assert fd_timing.nom_bitrate == 1_000_000 + assert 74 < fd_timing.nom_sample_point < 76 + assert fd_timing.data_bitrate == 8_000_000 + assert 69 < fd_timing.data_sample_point < 71 + + # check that there is a solution for every sample point + for sp in range(50, 100): + can.BitTiming.from_sample_point( + f_clock=16_000_000, bitrate=500_000, sample_point=sp + ) + + # check that there is a solution for every sample point + for nsp in range(50, 100): + for dsp in range(50, 100): + can.BitTimingFd.from_sample_point( + f_clock=80_000_000, + nom_bitrate=500_000, + nom_sample_point=nsp, + data_bitrate=2_000_000, + data_sample_point=dsp, + ) + + +def test_equality(): + t1 = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14) + t2 = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1, nof_samples=1) + t3 = can.BitTiming( + f_clock=16_000_000, brp=2, tseg1=5, tseg2=2, sjw=1, nof_samples=1 + ) + assert t1 == t2 + assert t1 != t3 + assert t2 != t3 + assert t1 != 10 + + t4 = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + t5 = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + t6 = can.BitTimingFd.from_sample_point( + f_clock=80_000_000, + nom_bitrate=1_000_000, + nom_sample_point=75.0, + data_bitrate=8_000_000, + data_sample_point=70.0, + ) + assert t4 == t5 + assert t4 != t6 + assert t4 != t1 + + def test_string_representation(): - timing = can.BitTiming(f_clock=8000000, bitrate=1000000, tseg1=5, tseg2=2, sjw=1) - assert ( - str(timing) - == "1000000 bits/s, sample point: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, BTR: 0014h" + timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1) + assert str(timing) == ( + "BR 1000000 bit/s, SP: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, " + "BTR: 0014h, f_clock: 8MHz" + ) + + fd_timing = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + assert str(fd_timing) == ( + "NBR: 500000 bit/s, NSP: 75.00%, NBRP: 1, NTSEG1: 119, NTSEG2: 40, NSJW: 40, " + "DBR: 2000000 bit/s, DSP: 75.00%, DBRP: 1, DTSEG1: 29, DTSEG2: 10, DSJW: 10, " + "f_clock: 80MHz" + ) + + +def test_repr(): + timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1) + assert repr(timing) == ( + "can.BitTiming(f_clock=8000000, brp=1, tseg1=5, tseg2=2, sjw=1, nof_samples=1)" + ) + + fd_timing = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + assert repr(fd_timing) == ( + "can.BitTimingFd(f_clock=80000000, nom_brp=1, nom_tseg1=119, nom_tseg2=40, " + "nom_sjw=40, data_brp=1, data_tseg1=29, data_tseg2=10, data_sjw=10)" + ) + + +def test_mapping(): + timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1) + timing_dict = dict(timing) + assert timing_dict["f_clock"] == timing["f_clock"] + assert timing_dict["brp"] == timing["brp"] + assert timing_dict["tseg1"] == timing["tseg1"] + assert timing_dict["tseg2"] == timing["tseg2"] + assert timing_dict["sjw"] == timing["sjw"] + assert timing == can.BitTiming(**timing_dict) + + fd_timing = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + fd_timing_dict = dict(fd_timing) + assert fd_timing_dict["f_clock"] == fd_timing["f_clock"] + assert fd_timing_dict["nom_brp"] == fd_timing["nom_brp"] + assert fd_timing_dict["nom_tseg1"] == fd_timing["nom_tseg1"] + assert fd_timing_dict["nom_tseg2"] == fd_timing["nom_tseg2"] + assert fd_timing_dict["nom_sjw"] == fd_timing["nom_sjw"] + assert fd_timing_dict["data_brp"] == fd_timing["data_brp"] + assert fd_timing_dict["data_tseg1"] == fd_timing["data_tseg1"] + assert fd_timing_dict["data_tseg2"] == fd_timing["data_tseg2"] + assert fd_timing_dict["data_sjw"] == fd_timing["data_sjw"] + assert fd_timing == can.BitTimingFd(**fd_timing_dict) + + +def test_oscillator_tolerance(): + timing = can.BitTiming(f_clock=16_000_000, brp=2, tseg1=10, tseg2=5, sjw=4) + osc_tol = timing.oscillator_tolerance( + node_loop_delay_ns=250, + bus_length_m=10.0, + ) + assert osc_tol == pytest.approx(1.23, abs=1e-2) + + fd_timing = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=5, + nom_tseg1=27, + nom_tseg2=4, + nom_sjw=4, + data_brp=5, + data_tseg1=6, + data_tseg2=1, + data_sjw=1, + ) + osc_tol = fd_timing.oscillator_tolerance( + node_loop_delay_ns=250, + bus_length_m=10.0, + ) + assert osc_tol == pytest.approx(0.48, abs=1e-2) + + +def test_recreate_with_f_clock(): + timing_8mhz = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1) + timing_16mhz = timing_8mhz.recreate_with_f_clock(f_clock=16_000_000) + assert timing_8mhz.bitrate == timing_16mhz.bitrate + assert timing_8mhz.sample_point == timing_16mhz.sample_point + assert (timing_8mhz.sjw / timing_8mhz.nbt) == pytest.approx( + timing_16mhz.sjw / timing_16mhz.nbt, abs=1e-3 + ) + assert timing_8mhz.nof_samples == timing_16mhz.nof_samples + + timing_16mhz = can.BitTiming( + f_clock=16000000, brp=2, tseg1=12, tseg2=3, sjw=3, nof_samples=1 + ) + timing_8mhz = timing_16mhz.recreate_with_f_clock(f_clock=8_000_000) + assert timing_8mhz.bitrate == timing_16mhz.bitrate + assert timing_8mhz.sample_point == timing_16mhz.sample_point + assert (timing_8mhz.sjw / timing_8mhz.nbt) == pytest.approx( + timing_16mhz.sjw / timing_16mhz.nbt, abs=1e-2 + ) + assert timing_8mhz.nof_samples == timing_16mhz.nof_samples + + fd_timing_80mhz = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=5, + nom_tseg1=27, + nom_tseg2=4, + nom_sjw=4, + data_brp=5, + data_tseg1=6, + data_tseg2=1, + data_sjw=1, + ) + fd_timing_60mhz = fd_timing_80mhz.recreate_with_f_clock(f_clock=60_000_000) + assert fd_timing_80mhz.nom_bitrate == fd_timing_60mhz.nom_bitrate + assert fd_timing_80mhz.nom_sample_point == pytest.approx( + fd_timing_60mhz.nom_sample_point, abs=1.0 + ) + assert (fd_timing_80mhz.nom_sjw / fd_timing_80mhz.nbt) == pytest.approx( + fd_timing_60mhz.nom_sjw / fd_timing_60mhz.nbt, abs=1e-2 + ) + assert fd_timing_80mhz.data_bitrate == fd_timing_60mhz.data_bitrate + assert fd_timing_80mhz.data_sample_point == pytest.approx( + fd_timing_60mhz.data_sample_point, abs=1.0 + ) + assert (fd_timing_80mhz.data_sjw / fd_timing_80mhz.dbt) == pytest.approx( + fd_timing_60mhz.data_sjw / fd_timing_60mhz.dbt, abs=1e-2 ) diff --git a/test/test_cantact.py b/test/test_cantact.py index 4383fab37..2cc3e479c 100644 --- a/test/test_cantact.py +++ b/test/test_cantact.py @@ -23,8 +23,8 @@ def test_bus_creation(self): def test_bus_creation_bittiming(self): cantact.MockInterface.set_bitrate.reset_mock() - bt = can.BitTiming(tseg1=13, tseg2=2, brp=6, sjw=1) - bus = can.Bus(channel=0, interface="cantact", bit_timing=bt, _testing=True) + bt = can.BitTiming(f_clock=24_000_000, brp=3, tseg1=13, tseg2=2, sjw=1) + bus = can.Bus(channel=0, interface="cantact", timing=bt, _testing=True) self.assertIsInstance(bus, cantact.CantactBus) cantact.MockInterface.set_bitrate.assert_not_called() cantact.MockInterface.set_bit_timing.assert_called() diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py index 467473671..4d1d3eb84 100755 --- a/test/test_interface_canalystii.py +++ b/test/test_interface_canalystii.py @@ -39,8 +39,10 @@ def test_initialize_single_channel_only(self): def test_initialize_with_timing_registers(self): with create_mock_device() as mock_device: instance = mock_device.return_value - timing = can.BitTiming(btr0=0x03, btr1=0x6F) - bus = CANalystIIBus(bitrate=None, bit_timing=timing) + timing = can.BitTiming.from_registers( + f_clock=8_000_000, btr0=0x03, btr1=0x6F + ) + bus = CANalystIIBus(bitrate=None, timing=timing) instance.init.assert_has_calls( [ call(0, timing0=0x03, timing1=0x6F), @@ -50,15 +52,9 @@ def test_initialize_with_timing_registers(self): def test_missing_bitrate(self): with self.assertRaises(ValueError) as cm: - bus = CANalystIIBus(0, bitrate=None, bit_timing=None) + bus = CANalystIIBus(0, bitrate=None, timing=None) self.assertIn("bitrate", str(cm.exception)) - def test_invalid_bit_timing(self): - with create_mock_device() as mock_device: - with self.assertRaises(ValueError) as cm: - invalid_timings = can.BitTiming() - CANalystIIBus(0, bit_timing=invalid_timings) - def test_receive_message(self): driver_message = driver.Message( can_id=0x333, diff --git a/test/test_util.py b/test/test_util.py index 70941f23f..a738e5c6a 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -5,11 +5,14 @@ import pytest +from can import BitTiming, BitTimingFd +from can.exceptions import CanInitializationError from can.util import ( _create_bus_config, _rename_kwargs, channel2int, deprecated_args_alias, + check_or_adjust_timing_clock, ) @@ -167,3 +170,95 @@ def test_channel2int(self) -> None: self.assertEqual(42, channel2int("42")) self.assertEqual(None, channel2int("can")) self.assertEqual(None, channel2int("can0a")) + + +class TestCheckAdjustTimingClock(unittest.TestCase): + def test_adjust_timing(self): + timing = BitTiming(f_clock=80_000_000, brp=10, tseg1=13, tseg2=2, sjw=1) + + # Check identity case + new_timing = check_or_adjust_timing_clock(timing, valid_clocks=[80_000_000]) + assert timing == new_timing + + with pytest.warns(UserWarning) as record: + new_timing = check_or_adjust_timing_clock( + timing, valid_clocks=[8_000_000, 24_000_000] + ) + assert len(record) == 1 + assert ( + record[0].message.args[0] + == "Adjusted f_clock in BitTiming from 80000000 to 8000000" + ) + assert new_timing.__class__ == BitTiming + assert new_timing.f_clock == 8_000_000 + assert new_timing.bitrate == timing.bitrate + assert new_timing.tseg1 == timing.tseg1 + assert new_timing.tseg2 == timing.tseg2 + assert new_timing.sjw == timing.sjw + + # Check that order is preserved + with pytest.warns(UserWarning) as record: + new_timing = check_or_adjust_timing_clock( + timing, valid_clocks=[24_000_000, 8_000_000] + ) + assert new_timing.f_clock == 24_000_000 + assert len(record) == 1 + assert ( + record[0].message.args[0] + == "Adjusted f_clock in BitTiming from 80000000 to 24000000" + ) + + # Check that order is preserved for all valid clock rates + with pytest.warns(UserWarning) as record: + new_timing = check_or_adjust_timing_clock( + timing, valid_clocks=[8_000, 24_000_000, 8_000_000] + ) + assert new_timing.f_clock == 24_000_000 + assert len(record) == 1 + assert ( + record[0].message.args[0] + == "Adjusted f_clock in BitTiming from 80000000 to 24000000" + ) + + with pytest.raises(CanInitializationError): + check_or_adjust_timing_clock(timing, valid_clocks=[8_000, 16_000]) + + def test_adjust_timing_fd(self): + timing = BitTimingFd( + f_clock=160_000_000, + nom_brp=2, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=2, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + + # Check identity case + new_timing = check_or_adjust_timing_clock(timing, valid_clocks=[160_000_000]) + assert timing == new_timing + + with pytest.warns(UserWarning) as record: + new_timing = check_or_adjust_timing_clock( + timing, valid_clocks=[8_000, 80_000_000] + ) + assert len(record) == 1 + assert ( + record[0].message.args[0] + == "Adjusted f_clock in BitTimingFd from 160000000 to 80000000" + ) + assert new_timing.__class__ == BitTimingFd + assert new_timing.f_clock == 80_000_000 + assert new_timing.nom_bitrate == 500_000 + assert new_timing.nom_tseg1 == 119 + assert new_timing.nom_tseg2 == 40 + assert new_timing.nom_sjw == 40 + assert new_timing.data_bitrate == 2_000_000 + assert new_timing.data_tseg1 == 29 + assert new_timing.data_tseg2 == 10 + assert new_timing.data_sjw == 10 + + with pytest.raises(CanInitializationError): + check_or_adjust_timing_clock(timing, valid_clocks=[8_000, 16_000]) From 86a91bb25d12a97ec5c7632c6f745cf6c079c288 Mon Sep 17 00:00:00 2001 From: Martin Thompson Date: Wed, 25 Jan 2023 21:42:44 +0000 Subject: [PATCH 1005/1235] Add VN5611 HWTYPE (#1501) --- can/interfaces/vector/xldefine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index 5a1084f48..aa1a6740c 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -289,6 +289,7 @@ class XL_HardwareType(IntEnum): XL_HWTYPE_VN7570 = 67 XL_HWTYPE_VN5650 = 68 XL_HWTYPE_IPCLIENT = 69 + XL_HWTYPE_VN5611 = 70 XL_HWTYPE_IPSERVER = 71 XL_HWTYPE_VX1121 = 73 XL_HWTYPE_VX1131 = 75 From dcf15c962786b969fa45232e176ed02eabc889b4 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 25 Jan 2023 23:03:01 +0100 Subject: [PATCH 1006/1235] improve robustness against unknown HardwareType values (#1502) --- can/interfaces/vector/canlib.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 5c7f1a8ad..ead3ee933 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -299,19 +299,21 @@ def _find_global_channel_idx( channel_configs: List["VectorChannelConfig"], ) -> int: if serial is not None: - hw_type: Optional[xldefine.XL_HardwareType] = None + serial_found = False for channel_config in channel_configs: if channel_config.serial_number != serial: continue - hw_type = xldefine.XL_HardwareType(channel_config.hw_type) + serial_found = True if channel_config.hw_channel == channel: return channel_config.channel_index - if hw_type is None: + if not serial_found: err_msg = f"No interface with serial {serial} found." else: - err_msg = f"Channel {channel} not found on interface {hw_type.name} ({serial})." + err_msg = ( + f"Channel {channel} not found on interface with serial {serial}." + ) raise CanInitializationError( err_msg, error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT ) @@ -915,7 +917,7 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: @staticmethod def get_application_config( app_name: str, app_channel: int - ) -> Tuple[xldefine.XL_HardwareType, int, int]: + ) -> Tuple[Union[int, xldefine.XL_HardwareType], int, int]: """Retrieve information for an application in Vector Hardware Configuration. :param app_name: @@ -955,13 +957,13 @@ def get_application_config( ), function="xlGetApplConfig", ) from None - return xldefine.XL_HardwareType(hw_type.value), hw_index.value, hw_channel.value + return _hw_type(hw_type.value), hw_index.value, hw_channel.value @staticmethod def set_application_config( app_name: str, app_channel: int, - hw_type: xldefine.XL_HardwareType, + hw_type: Union[int, xldefine.XL_HardwareType], hw_index: int, hw_channel: int, **kwargs: Any, @@ -1055,7 +1057,7 @@ class VectorChannelConfig(NamedTuple): """NamedTuple which contains the channel properties from Vector XL API.""" name: str - hw_type: xldefine.XL_HardwareType + hw_type: Union[int, xldefine.XL_HardwareType] hw_index: int hw_channel: int channel_index: int @@ -1128,7 +1130,7 @@ def get_channel_configs() -> List[VectorChannelConfig]: xlcc: xlclass.XLchannelConfig = driver_config.channel[i] vcc = VectorChannelConfig( name=xlcc.name.decode(), - hw_type=xldefine.XL_HardwareType(xlcc.hwType), + hw_type=_hw_type(xlcc.hwType), hw_index=xlcc.hwIndex, hw_channel=xlcc.hwChannel, channel_index=xlcc.channelIndex, @@ -1148,3 +1150,11 @@ def get_channel_configs() -> List[VectorChannelConfig]: ) channel_list.append(vcc) return channel_list + + +def _hw_type(hw_type: int) -> Union[int, xldefine.XL_HardwareType]: + try: + return xldefine.XL_HardwareType(hw_type) + except ValueError: + LOG.warning(f'Unknown XL_HardwareType value "{hw_type}"') + return hw_type From 1dddc1dbd748e3854398210aa32ecfc3b33b71ac Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 27 Jan 2023 17:41:42 +0100 Subject: [PATCH 1007/1235] Automatic type conversion for config values (#1499) --- can/logger.py | 14 ++------------ can/util.py | 37 +++++++++++++++++++++++++++++-------- test/test_load_config.py | 37 +++++++++++++++++++++---------------- test/test_util.py | 25 +++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 36 deletions(-) diff --git a/can/logger.py b/can/logger.py index 55e67b27e..9448fe6b4 100644 --- a/can/logger.py +++ b/can/logger.py @@ -8,6 +8,7 @@ import can from can.io import BaseRotatingLogger from can.io.generic import MessageWriter +from can.util import cast_from_string from . import Bus, BusState, Logger, SizedRotatingLogger from .typechecking import CanFilter, CanFilters @@ -134,18 +135,7 @@ def _split_arg(_arg: str) -> Tuple[str, str]: args: Dict[str, Union[str, int, float, bool]] = {} for key, string_val in map(_split_arg, unknown_args): - if re.match(r"^[-+]?\d+$", string_val): - # value is integer - args[key] = int(string_val) - elif re.match(r"^[-+]?\d*\.\d+$", string_val): - # value is float - args[key] = float(string_val) - elif re.match(r"^(?:True|False)$", string_val): - # value is bool - args[key] = string_val == "True" - else: - # value is string - args[key] = string_val + args[key] = cast_from_string(string_val) return args diff --git a/can/util.py b/can/util.py index 8f5cea0c4..42d99272e 100644 --- a/can/util.py +++ b/can/util.py @@ -147,7 +147,7 @@ def load_config( It may set other values that are passed through. :param context: - Extra 'context' pass to config sources. This can be use to section + Extra 'context' pass to config sources. This can be used to section other than 'default' in the configuration file. :return: @@ -197,9 +197,12 @@ def load_config( cfg["interface"] = cfg["bustype"] del cfg["bustype"] # copy all new parameters - for key in cfg: + for key, val in cfg.items(): if key not in config: - config[key] = cfg[key] + if isinstance(val, str): + config[key] = cast_from_string(val) + else: + config[key] = cfg[key] bus_config = _create_bus_config(config) can.log.debug("can config: %s", bus_config) @@ -257,12 +260,8 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: except (ValueError, TypeError): pass - if "bitrate" in config: - config["bitrate"] = int(config["bitrate"]) if "fd" in config: - config["fd"] = config["fd"] not in ("0", "False", "false", False) - if "data_bitrate" in config: - config["data_bitrate"] = int(config["data_bitrate"]) + config["fd"] = config["fd"] not in (0, False) return cast(typechecking.BusConfig, config) @@ -478,6 +477,28 @@ def time_perfcounter_correlation() -> Tuple[float, float]: return t1, performance_counter +def cast_from_string(string_val: str) -> Union[str, int, float, bool]: + """Perform trivial type conversion from :class:`str` values. + + :param string_val: + the string, that shall be converted + """ + if re.match(r"^[-+]?\d+$", string_val): + # value is integer + return int(string_val) + + if re.match(r"^[-+]?\d*\.\d+(?:e[-+]?\d+)?$", string_val): + # value is float + return float(string_val) + + if re.match(r"^(?:True|False)$", string_val, re.IGNORECASE): + # value is bool + return string_val.lower() == "true" + + # value is string + return string_val + + if __name__ == "__main__": print("Searching for configuration named:") print("\n".join(CONFIG_FILES)) diff --git a/test/test_load_config.py b/test/test_load_config.py index 1bfba450a..3c850a730 100644 --- a/test/test_load_config.py +++ b/test/test_load_config.py @@ -1,20 +1,25 @@ #!/usr/bin/env python -import os import shutil import tempfile import unittest +import unittest.mock from tempfile import NamedTemporaryFile import can class LoadConfigTest(unittest.TestCase): - configuration = { + configuration_in = { "default": {"interface": "serial", "channel": "0"}, "one": {"interface": "kvaser", "channel": "1", "bitrate": 100000}, "two": {"channel": "2"}, } + configuration_out = { + "default": {"interface": "serial", "channel": 0}, + "one": {"interface": "kvaser", "channel": 1, "bitrate": 100000}, + "two": {"channel": 2}, + } def setUp(self): # Create a temporary directory @@ -31,7 +36,7 @@ def _gen_configration_file(self, sections): content = [] for section in sections: content.append(f"[{section}]") - for k, v in self.configuration[section].items(): + for k, v in self.configuration_in[section].items(): content.append(f"{k} = {v}") tmp_config_file.write("\n".join(content)) return tmp_config_file.name @@ -42,43 +47,43 @@ def _dict_to_env(self, d): def test_config_default(self): tmp_config = self._gen_configration_file(["default"]) config = can.util.load_config(path=tmp_config) - self.assertEqual(config, self.configuration["default"]) + self.assertEqual(config, self.configuration_out["default"]) def test_config_whole_default(self): - tmp_config = self._gen_configration_file(self.configuration) + tmp_config = self._gen_configration_file(self.configuration_in) config = can.util.load_config(path=tmp_config) - self.assertEqual(config, self.configuration["default"]) + self.assertEqual(config, self.configuration_out["default"]) def test_config_whole_context(self): - tmp_config = self._gen_configration_file(self.configuration) + tmp_config = self._gen_configration_file(self.configuration_in) config = can.util.load_config(path=tmp_config, context="one") - self.assertEqual(config, self.configuration["one"]) + self.assertEqual(config, self.configuration_out["one"]) def test_config_merge_context(self): - tmp_config = self._gen_configration_file(self.configuration) + tmp_config = self._gen_configration_file(self.configuration_in) config = can.util.load_config(path=tmp_config, context="two") - expected = self.configuration["default"] - expected.update(self.configuration["two"]) + expected = self.configuration_out["default"].copy() + expected.update(self.configuration_out["two"]) self.assertEqual(config, expected) def test_config_merge_environment_to_context(self): - tmp_config = self._gen_configration_file(self.configuration) + tmp_config = self._gen_configration_file(self.configuration_in) env_data = {"interface": "serial", "bitrate": 125000} env_dict = self._dict_to_env(env_data) with unittest.mock.patch.dict("os.environ", env_dict): config = can.util.load_config(path=tmp_config, context="one") - expected = self.configuration["one"] + expected = self.configuration_out["one"].copy() expected.update(env_data) self.assertEqual(config, expected) def test_config_whole_environment(self): - tmp_config = self._gen_configration_file(self.configuration) + tmp_config = self._gen_configration_file(self.configuration_in) env_data = {"interface": "socketcan", "channel": "3", "bitrate": 250000} env_dict = self._dict_to_env(env_data) with unittest.mock.patch.dict("os.environ", env_dict): config = can.util.load_config(path=tmp_config, context="one") - expected = self.configuration["one"] - expected.update(env_data) + expected = self.configuration_out["one"].copy() + expected.update({"interface": "socketcan", "channel": 3, "bitrate": 250000}) self.assertEqual(config, expected) diff --git a/test/test_util.py b/test/test_util.py index a738e5c6a..88349d974 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -13,6 +13,7 @@ channel2int, deprecated_args_alias, check_or_adjust_timing_clock, + cast_from_string, ) @@ -262,3 +263,27 @@ def test_adjust_timing_fd(self): with pytest.raises(CanInitializationError): check_or_adjust_timing_clock(timing, valid_clocks=[8_000, 16_000]) + + +class TestCastFromString(unittest.TestCase): + def test_cast_from_string(self) -> None: + self.assertEqual(1, cast_from_string("1")) + self.assertEqual(-1, cast_from_string("-1")) + self.assertEqual(0, cast_from_string("-0")) + self.assertEqual(1.1, cast_from_string("1.1")) + self.assertEqual(-1.1, cast_from_string("-1.1")) + self.assertEqual(0.1, cast_from_string(".1")) + self.assertEqual(10.0, cast_from_string(".1e2")) + self.assertEqual(0.001, cast_from_string(".1e-2")) + self.assertEqual(-0.001, cast_from_string("-.1e-2")) + self.assertEqual("text", cast_from_string("text")) + self.assertEqual("", cast_from_string("")) + self.assertEqual("can0", cast_from_string("can0")) + self.assertEqual("0can", cast_from_string("0can")) + self.assertEqual(False, cast_from_string("false")) + self.assertEqual(False, cast_from_string("False")) + self.assertEqual(True, cast_from_string("true")) + self.assertEqual(True, cast_from_string("True")) + + with self.assertRaises(TypeError): + cast_from_string(None) From d2abd34b7868e4b47a7dfc0ebb1fbcd9c3c5a174 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 27 Jan 2023 18:44:56 +0100 Subject: [PATCH 1008/1235] Update XL_HardwareType (#1509) --- can/interfaces/vector/xldefine.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index aa1a6740c..e2fd288b9 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -291,7 +291,9 @@ class XL_HardwareType(IntEnum): XL_HWTYPE_IPCLIENT = 69 XL_HWTYPE_VN5611 = 70 XL_HWTYPE_IPSERVER = 71 + XL_HWTYPE_VN5612 = 72 XL_HWTYPE_VX1121 = 73 + XL_HWTYPE_VN5601 = 74 XL_HWTYPE_VX1131 = 75 XL_HWTYPE_VT6204 = 77 XL_HWTYPE_VN1630_LOG = 79 @@ -318,6 +320,8 @@ class XL_HardwareType(IntEnum): XL_HWTYPE_VN1531 = 113 XL_HWTYPE_VX1161A = 114 XL_HWTYPE_VX1161B = 115 + XL_HWTYPE_VGNSS = 116 + XL_HWTYPE_VXLAPINIC = 118 XL_MAX_HWTYPE = 120 From 7a4c6f80782add361cf0c95613559ace408f17c4 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 27 Jan 2023 22:19:42 +0100 Subject: [PATCH 1009/1235] Add BitTiming/BitTimingFd support to VectorBus (#1470) * add BitTiming parameter to VectorBus * use correct interface_version * implement tests for bittiming classes with vector --- can/interfaces/vector/canlib.py | 51 +++++++++-- test/test_vector.py | 148 ++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 5 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index ead3ee933..287f45437 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -37,12 +37,20 @@ # Import Modules # ============== -from can import BusABC, Message, CanInterfaceNotImplementedError, CanInitializationError +from can import ( + BusABC, + Message, + CanInterfaceNotImplementedError, + CanInitializationError, + BitTiming, + BitTimingFd, +) from can.util import ( len2dlc, dlc2len, deprecated_args_alias, time_perfcounter_correlation, + check_or_adjust_timing_clock, ) from can.typechecking import AutoDetectedConfig, CanFilters @@ -86,6 +94,7 @@ def __init__( can_filters: Optional[CanFilters] = None, poll_interval: float = 0.01, receive_own_messages: bool = False, + timing: Optional[Union[BitTiming, BitTimingFd]] = None, bitrate: Optional[int] = None, rx_queue_size: int = 2**14, app_name: Optional[str] = "CANalyzer", @@ -108,6 +117,15 @@ def __init__( See :class:`can.BusABC`. :param receive_own_messages: See :class:`can.BusABC`. + :param timing: + An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd` + to specify the bit timing parameters for the VectorBus interface. The + `f_clock` value of the timing instance must be set to 16.000.000 (16MHz) + for standard CAN or 80.000.000 (80MHz) for CAN FD. If this parameter is provided, + it takes precedence over all other timing-related parameters. + Otherwise, the bit timing can be specified using the following parameters: + `bitrate` for standard CAN or `fd`, `data_bitrate`, `sjw_abr`, `tseg1_abr`, + `tseg2_abr`, `sjw_dbr`, `tseg1_dbr`, and `tseg2_dbr` for CAN FD. :param poll_interval: Poll interval in seconds. :param bitrate: @@ -184,7 +202,7 @@ def __init__( channel_configs = get_channel_configs() self.mask = 0 - self.fd = fd + self.fd = isinstance(timing, BitTimingFd) if timing else fd self.channel_masks: Dict[int, int] = {} self.index_to_channel: Dict[int, int] = {} @@ -204,12 +222,12 @@ def __init__( permission_mask = xlclass.XLaccess() # Set mask to request channel init permission if needed - if bitrate or fd: + if bitrate or fd or timing: permission_mask.value = self.mask interface_version = ( xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 - if fd + if self.fd else xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION ) @@ -233,7 +251,30 @@ def __init__( # set CAN settings for channel in self.channels: - if fd: + if isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, [16_000_000]) + self._set_bitrate_can( + channel=channel, + bitrate=timing.bitrate, + sjw=timing.sjw, + tseg1=timing.tseg1, + tseg2=timing.tseg2, + sam=timing.nof_samples, + ) + elif isinstance(timing, BitTimingFd): + timing = check_or_adjust_timing_clock(timing, [80_000_000]) + self._set_bitrate_canfd( + channel=channel, + bitrate=timing.nom_bitrate, + data_bitrate=timing.data_bitrate, + sjw_abr=timing.nom_sjw, + tseg1_abr=timing.nom_tseg1, + tseg2_abr=timing.nom_tseg2, + sjw_dbr=timing.data_sjw, + tseg1_dbr=timing.data_tseg1, + tseg2_dbr=timing.data_tseg2, + ) + elif fd: self._set_bitrate_canfd( channel=channel, bitrate=bitrate, diff --git a/test/test_vector.py b/test/test_vector.py index 02c3c336d..fd95cef8a 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -270,6 +270,154 @@ def test_bus_creation_fd_bitrate_timings() -> None: bus.shutdown() +def test_bus_creation_timing_mocked(mock_xldriver) -> None: + timing = can.BitTiming.from_bitrate_and_segments( + f_clock=16_000_000, + bitrate=125_000, + tseg1=13, + tseg2=2, + sjw=1, + ) + bus = can.Bus(channel=0, interface="vector", timing=timing, _testing=True) + assert isinstance(bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value + assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelParams.assert_called() + chip_params = ( + can.interfaces.vector.canlib.xldriver.xlCanSetChannelParams.call_args[0] + )[2] + assert chip_params.bitRate == 125_000 + assert chip_params.sjw == 1 + assert chip_params.tseg1 == 13 + assert chip_params.tseg2 == 2 + assert chip_params.sam == 1 + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_bus_creation_timing() -> None: + timing = can.BitTiming.from_bitrate_and_segments( + f_clock=16_000_000, + bitrate=125_000, + tseg1=13, + tseg2=2, + sjw=1, + ) + bus = can.Bus( + channel=0, + serial=_find_virtual_can_serial(), + interface="vector", + timing=timing, + ) + assert isinstance(bus, canlib.VectorBus) + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert xl_channel_config.busParams.data.can.bitRate == 125_000 + assert xl_channel_config.busParams.data.can.sjw == 1 + assert xl_channel_config.busParams.data.can.tseg1 == 13 + assert xl_channel_config.busParams.data.can.tseg2 == 2 + + bus.shutdown() + + +def test_bus_creation_timingfd_mocked(mock_xldriver) -> None: + timing = can.BitTimingFd.from_bitrate_and_segments( + f_clock=80_000_000, + nom_bitrate=500_000, + nom_tseg1=68, + nom_tseg2=11, + nom_sjw=10, + data_bitrate=2_000_000, + data_tseg1=10, + data_tseg2=9, + data_sjw=8, + ) + bus = can.Bus( + channel=0, + interface="vector", + timing=timing, + _testing=True, + ) + assert isinstance(bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + assert ( + xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value + ) + + assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + xlCanFdSetConfiguration_args = ( + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[0] + ) + canFdConf = xlCanFdSetConfiguration_args[2] + assert canFdConf.arbitrationBitRate == 500_000 + assert canFdConf.dataBitRate == 2_000_000 + assert canFdConf.sjwAbr == 10 + assert canFdConf.tseg1Abr == 68 + assert canFdConf.tseg2Abr == 11 + assert canFdConf.sjwDbr == 8 + assert canFdConf.tseg1Dbr == 10 + assert canFdConf.tseg2Dbr == 9 + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_bus_creation_timingfd() -> None: + timing = can.BitTimingFd.from_bitrate_and_segments( + f_clock=80_000_000, + nom_bitrate=500_000, + nom_tseg1=68, + nom_tseg2=11, + nom_sjw=10, + data_bitrate=2_000_000, + data_tseg1=10, + data_tseg2=9, + data_sjw=8, + ) + bus = can.Bus( + channel=0, + serial=_find_virtual_can_serial(), + interface="vector", + timing=timing, + ) + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert ( + xl_channel_config.interfaceVersion + == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 + ) + assert ( + xl_channel_config.busParams.data.canFD.canOpMode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD + ) + assert xl_channel_config.busParams.data.canFD.arbitrationBitRate == 500_000 + assert xl_channel_config.busParams.data.canFD.sjwAbr == 10 + assert xl_channel_config.busParams.data.canFD.tseg1Abr == 68 + assert xl_channel_config.busParams.data.canFD.tseg2Abr == 11 + assert xl_channel_config.busParams.data.canFD.sjwDbr == 8 + assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 10 + assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 9 + assert xl_channel_config.busParams.data.canFD.dataBitRate == 2_000_000 + + bus.shutdown() + + def test_send_mocked(mock_xldriver) -> None: bus = can.Bus(channel=0, interface="vector", _testing=True) msg = can.Message( From 356f6f9fde3c99d83a9b174d53f092e99a83a45b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 28 Jan 2023 16:02:25 +0100 Subject: [PATCH 1010/1235] Add code example to BitTiming docs (#1515) * add copy button * make bittiming classes hashable * add code example to show possible bit timings * fix tests * rename variable --- can/bit_timing.py | 16 +++++++++++----- doc/bit_timing.rst | 30 +++++++++++++++++++++++++++++- doc/conf.py | 1 + doc/doc-requirements.txt | 1 + test/test_bit_timing.py | 27 ++++++++++++++++++++++----- 5 files changed, 64 insertions(+), 11 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index 2dc78064b..4fada145b 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -395,14 +395,14 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming": def __str__(self) -> str: segments = [ - f"BR {self.bitrate} bit/s", + f"BR: {self.bitrate:_} bit/s", f"SP: {self.sample_point:.2f}%", f"BRP: {self.brp}", f"TSEG1: {self.tseg1}", f"TSEG2: {self.tseg2}", f"SJW: {self.sjw}", f"BTR: {self.btr0:02X}{self.btr1:02X}h", - f"f_clock: {self.f_clock / 1e6:.0f}MHz", + f"CLK: {self.f_clock / 1e6:.0f}MHz", ] return ", ".join(segments) @@ -425,6 +425,9 @@ def __eq__(self, other: object) -> bool: return self._data == other._data + def __hash__(self) -> int: + return tuple(self._data.values()).__hash__() + class BitTimingFd(Mapping): """Representation of a bit timing configuration for a CAN FD bus. @@ -999,19 +1002,19 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTimingFd": def __str__(self) -> str: segments = [ - f"NBR: {self.nom_bitrate} bit/s", + f"NBR: {self.nom_bitrate:_} bit/s", f"NSP: {self.nom_sample_point:.2f}%", f"NBRP: {self.nom_brp}", f"NTSEG1: {self.nom_tseg1}", f"NTSEG2: {self.nom_tseg2}", f"NSJW: {self.nom_sjw}", - f"DBR: {self.data_bitrate} bit/s", + f"DBR: {self.data_bitrate:_} bit/s", f"DSP: {self.data_sample_point:.2f}%", f"DBRP: {self.data_brp}", f"DTSEG1: {self.data_tseg1}", f"DTSEG2: {self.data_tseg2}", f"DSJW: {self.data_sjw}", - f"f_clock: {self.f_clock / 1e6:.0f}MHz", + f"CLK: {self.f_clock / 1e6:.0f}MHz", ] return ", ".join(segments) @@ -1034,6 +1037,9 @@ def __eq__(self, other: object) -> bool: return self._data == other._data + def __hash__(self) -> int: + return tuple(self._data.values()).__hash__() + def _oscillator_tolerance_condition_1(nom_sjw: int, nbt: int) -> float: """Arbitration phase - resynchronization""" diff --git a/doc/bit_timing.rst b/doc/bit_timing.rst index b48a133b8..73005a3c6 100644 --- a/doc/bit_timing.rst +++ b/doc/bit_timing.rst @@ -63,10 +63,38 @@ to specify custom bit timings. The :class:`~can.BitTiming` and :class:`~can.BitTimingFd` classes can be used for this purpose to specify bit timings in a relatively interface agnostic manner. +:class:`~can.BitTiming` or :class:`~can.BitTimingFd` can also help you to +produce an overview of possible bit timings for your desired bit rate: + + >>> import contextlib + >>> import can + ... + >>> timings = set() + >>> for sample_point in range(50, 100): + ... with contextlib.suppress(ValueError): + ... timings.add( + ... can.BitTiming.from_sample_point( + ... f_clock=8_000_000, + ... bitrate=250_000, + ... sample_point=sample_point, + ... ) + ... ) + ... + >>> for timing in sorted(timings, key=lambda x: x.sample_point): + ... print(timing) + BR: 250_000 bit/s, SP: 50.00%, BRP: 2, TSEG1: 7, TSEG2: 8, SJW: 4, BTR: C176h, CLK: 8MHz + BR: 250_000 bit/s, SP: 56.25%, BRP: 2, TSEG1: 8, TSEG2: 7, SJW: 4, BTR: C167h, CLK: 8MHz + BR: 250_000 bit/s, SP: 62.50%, BRP: 2, TSEG1: 9, TSEG2: 6, SJW: 4, BTR: C158h, CLK: 8MHz + BR: 250_000 bit/s, SP: 68.75%, BRP: 2, TSEG1: 10, TSEG2: 5, SJW: 4, BTR: C149h, CLK: 8MHz + BR: 250_000 bit/s, SP: 75.00%, BRP: 2, TSEG1: 11, TSEG2: 4, SJW: 4, BTR: C13Ah, CLK: 8MHz + BR: 250_000 bit/s, SP: 81.25%, BRP: 2, TSEG1: 12, TSEG2: 3, SJW: 3, BTR: 812Bh, CLK: 8MHz + BR: 250_000 bit/s, SP: 87.50%, BRP: 2, TSEG1: 13, TSEG2: 2, SJW: 2, BTR: 411Ch, CLK: 8MHz + BR: 250_000 bit/s, SP: 93.75%, BRP: 2, TSEG1: 14, TSEG2: 1, SJW: 1, BTR: 010Dh, CLK: 8MHz + + It is possible to specify CAN 2.0 bit timings using the config file: - .. code-block:: none [default] diff --git a/doc/conf.py b/doc/conf.py index 6ba661a30..cea93440d 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,6 +49,7 @@ "sphinx.ext.graphviz", "sphinxcontrib.programoutput", "sphinx_inline_tabs", + "sphinx_copybutton", ] # 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 c61b55683..9a01cf589 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,4 +1,5 @@ sphinx>=5.2.3 sphinxcontrib-programoutput sphinx-inline-tabs +sphinx-copybutton furo diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py index 669308f9d..a0d4e03a5 100644 --- a/test/test_bit_timing.py +++ b/test/test_bit_timing.py @@ -271,8 +271,8 @@ def test_equality(): def test_string_representation(): timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1) assert str(timing) == ( - "BR 1000000 bit/s, SP: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, " - "BTR: 0014h, f_clock: 8MHz" + "BR: 1_000_000 bit/s, SP: 75.00%, BRP: 1, TSEG1: 5, TSEG2: 2, SJW: 1, " + "BTR: 0014h, CLK: 8MHz" ) fd_timing = can.BitTimingFd( @@ -287,9 +287,9 @@ def test_string_representation(): data_sjw=10, ) assert str(fd_timing) == ( - "NBR: 500000 bit/s, NSP: 75.00%, NBRP: 1, NTSEG1: 119, NTSEG2: 40, NSJW: 40, " - "DBR: 2000000 bit/s, DSP: 75.00%, DBRP: 1, DTSEG1: 29, DTSEG2: 10, DSJW: 10, " - "f_clock: 80MHz" + "NBR: 500_000 bit/s, NSP: 75.00%, NBRP: 1, NTSEG1: 119, NTSEG2: 40, NSJW: 40, " + "DBR: 2_000_000 bit/s, DSP: 75.00%, DBRP: 1, DTSEG1: 29, DTSEG2: 10, DSJW: 10, " + "CLK: 80MHz" ) @@ -316,6 +316,23 @@ def test_repr(): ) +def test_hash(): + _timings = { + can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1, nof_samples=1), + can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ), + } + + def test_mapping(): timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1) timing_dict = dict(timing) From e96abcf42996d73c30de003b576428e48e27183e Mon Sep 17 00:00:00 2001 From: Faisal Shah <37458679+faisal-shah@users.noreply.github.com> Date: Sat, 28 Jan 2023 09:58:56 -0600 Subject: [PATCH 1011/1235] Socketcand ext ID bug fixes, and implement channel{_info} and Message attributes (#1508) * Handle extended arbitration id properly socketcand uses the length of the arbitration id field to indicate whether a frame is using extended id or not. 3 characters for standard, 8 for extended. Prior to this fix, the arbitration id would be truncated to the lower 11 bits, or at times even garbage. * Add is_rx attribute * Populate channel{_info} and Message attributes --- can/interfaces/socketcand/socketcand.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 28f0c700f..32b9a0edf 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -27,11 +27,17 @@ def convert_ascii_message_to_can_message(ascii_msg: str) -> can.Message: frame_string = ascii_msg[8:-2] parts = frame_string.split(" ", 3) can_id, timestamp = int(parts[0], 16), float(parts[1]) + is_ext = len(parts[0]) != 3 data = bytearray.fromhex(parts[2]) can_dlc = len(data) can_message = can.Message( - timestamp=timestamp, arbitration_id=can_id, data=data, dlc=can_dlc + timestamp=timestamp, + arbitration_id=can_id, + data=data, + dlc=can_dlc, + is_extended_id=is_ext, + is_rx=True, ) return can_message @@ -40,11 +46,15 @@ def convert_can_message_to_ascii_message(can_message: can.Message) -> str: # Note: socketcan bus adds extended flag, remote_frame_flag & error_flag to id # not sure if that is necessary here can_id = can_message.arbitration_id + if can_message.is_extended_id: + can_id_string = f"{(can_id&0x1FFFFFFF):08X}" + else: + can_id_string = f"{(can_id&0x7FF):03X}" # Note: seems like we cannot add CANFD_BRS (bitrate_switch) and CANFD_ESI (error_state_indicator) flags data = can_message.data length = can_message.dlc bytes_string = " ".join(f"{x:x}" for x in data[0:length]) - return f"< send {can_id:X} {length:X} {bytes_string} >" + return f"< send {can_id_string} {length:X} {bytes_string} >" def connect_to_server(s, host, port): @@ -70,6 +80,8 @@ def __init__(self, channel, host, port, can_filters=None, **kwargs): self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.__message_buffer = deque() self.__receive_buffer = "" # i know string is not the most efficient here + self.channel = channel + self.channel_info = f"socketcand on {channel}@{host}:{port}" connect_to_server(self.__socket, self.__host, self.__port) self._expect_msg("< hi >") @@ -139,6 +151,7 @@ def _recv_internal(self, timeout): if parsed_can_message is None: log.warning(f"Invalid Frame: {single_message}") else: + parsed_can_message.channel = self.channel self.__message_buffer.append(parsed_can_message) buffer_view = buffer_view[end + 1 :] From 047cbe44d46105d3433f4041edf4ef4a30b3dfd1 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Mon, 30 Jan 2023 11:01:58 +0100 Subject: [PATCH 1012/1235] Align ixxat interface's `shutdown()` with `can.BusABC` typing --- can/interfaces/ixxat/canlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 1e4055f89..cb7131c57 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -146,8 +146,8 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: def _send_periodic_internal(self, msgs, period, duration=None): return self.bus._send_periodic_internal(msgs, period, duration) - def shutdown(self): - return self.bus.shutdown() + def shutdown(self) -> None: + self.bus.shutdown() @property def state(self) -> BusState: From 73593762d946ad40e83a5b59f82eea4f4757a5de Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Mon, 30 Jan 2023 14:51:04 +0100 Subject: [PATCH 1013/1235] Cleanup ixxat interface code (#1517) --- can/interfaces/ixxat/canlib.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index cb7131c57..a20e4f59b 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -155,6 +155,3 @@ def state(self) -> BusState: Return the current state of the hardware """ return self.bus.state - - -# ~class IXXATBus(BusABC): --------------------------------------------------- From 9cd7b49b7bf8c33cf0cdea0ba15cde7cf0ef2fa3 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 2 Feb 2023 22:06:55 +0100 Subject: [PATCH 1014/1235] print warnings --- test/test_util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_util.py b/test/test_util.py index 88349d974..b6c261602 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -245,7 +245,9 @@ def test_adjust_timing_fd(self): new_timing = check_or_adjust_timing_clock( timing, valid_clocks=[8_000, 80_000_000] ) - assert len(record) == 1 + assert len(record) == 1, "; ".join( + [record[i].message.args[0] for i in range(len(record))] + ) # print all warnings, if more than one warning is present assert ( record[0].message.args[0] == "Adjusted f_clock in BitTimingFd from 160000000 to 80000000" From 8b6bfa9c9591fad2ab4f860cb6733c28aaf43f56 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 4 Feb 2023 01:02:22 +0100 Subject: [PATCH 1015/1235] Vector: refactor bit timing setup (#1516) * add xlCanSetChannelParamsC200 * refactor VectorBus * move check into separate method * improve error message * use Optional instead of | --- can/interfaces/vector/canlib.py | 294 ++++++++++++++---------------- can/interfaces/vector/xldriver.py | 12 +- test/test_vector.py | 126 ++++++++----- 3 files changed, 221 insertions(+), 211 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 287f45437..d53b1418d 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -120,9 +120,10 @@ def __init__( :param timing: An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd` to specify the bit timing parameters for the VectorBus interface. The - `f_clock` value of the timing instance must be set to 16.000.000 (16MHz) - for standard CAN or 80.000.000 (80MHz) for CAN FD. If this parameter is provided, - it takes precedence over all other timing-related parameters. + `f_clock` value of the timing instance must be set to 8_000_000 (8MHz) + or 16_000_000 (16MHz) for CAN 2.0 or 80_000_000 (80MHz) for CAN FD. + If this parameter is provided, it takes precedence over all other + timing-related parameters. Otherwise, the bit timing can be specified using the following parameters: `bitrate` for standard CAN or `fd`, `data_bitrate`, `sjw_abr`, `tseg1_abr`, `tseg2_abr`, `sjw_dbr`, `tseg1_dbr`, and `tseg2_dbr` for CAN FD. @@ -252,42 +253,34 @@ def __init__( # set CAN settings for channel in self.channels: if isinstance(timing, BitTiming): - timing = check_or_adjust_timing_clock(timing, [16_000_000]) - self._set_bitrate_can( + timing = check_or_adjust_timing_clock(timing, [16_000_000, 8_000_000]) + self._set_bit_timing( channel=channel, - bitrate=timing.bitrate, - sjw=timing.sjw, - tseg1=timing.tseg1, - tseg2=timing.tseg2, - sam=timing.nof_samples, + timing=timing, ) elif isinstance(timing, BitTimingFd): timing = check_or_adjust_timing_clock(timing, [80_000_000]) - self._set_bitrate_canfd( + self._set_bit_timing_fd( channel=channel, - bitrate=timing.nom_bitrate, - data_bitrate=timing.data_bitrate, - sjw_abr=timing.nom_sjw, - tseg1_abr=timing.nom_tseg1, - tseg2_abr=timing.nom_tseg2, - sjw_dbr=timing.data_sjw, - tseg1_dbr=timing.data_tseg1, - tseg2_dbr=timing.data_tseg2, + timing=timing, ) elif fd: - self._set_bitrate_canfd( + self._set_bit_timing_fd( channel=channel, - bitrate=bitrate, - data_bitrate=data_bitrate, - sjw_abr=sjw_abr, - tseg1_abr=tseg1_abr, - tseg2_abr=tseg2_abr, - sjw_dbr=sjw_dbr, - tseg1_dbr=tseg1_dbr, - tseg2_dbr=tseg2_dbr, + timing=BitTimingFd.from_bitrate_and_segments( + f_clock=80_000_000, + nom_bitrate=bitrate or 500_000, + nom_tseg1=tseg1_abr, + nom_tseg2=tseg2_abr, + nom_sjw=sjw_abr, + data_bitrate=data_bitrate or bitrate or 500_000, + data_tseg1=tseg1_dbr, + data_tseg2=tseg2_dbr, + data_sjw=sjw_dbr, + ), ) elif bitrate: - self._set_bitrate_can(channel=channel, bitrate=bitrate) + self._set_bitrate(channel=channel, bitrate=bitrate) # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 @@ -404,30 +397,44 @@ def _read_bus_params(self, channel: int) -> "VectorBusParams": f"Channel configuration for channel {channel} not found." ) - def _set_bitrate_can( - self, - channel: int, - bitrate: int, - sjw: Optional[int] = None, - tseg1: Optional[int] = None, - tseg2: Optional[int] = None, - sam: int = 1, - ) -> None: - kwargs = [sjw, tseg1, tseg2] - if any(kwargs) and not all(kwargs): - raise ValueError( - f"Either all of sjw, tseg1, tseg2 must be set or none of them." + def _set_bitrate(self, channel: int, bitrate: int) -> None: + # set parameters if channel has init access + if self._has_init_access(channel): + self.xldriver.xlCanSetChannelBitrate( + self.port_handle, + self.channel_masks[channel], + bitrate, ) + LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate) + if not self.__testing: + self._check_can_settings( + channel=channel, + bitrate=bitrate, + ) + + def _set_bit_timing(self, channel: int, timing: BitTiming) -> None: # set parameters if channel has init access if self._has_init_access(channel): - if any(kwargs): + if timing.f_clock == 8_000_000: + self.xldriver.xlCanSetChannelParamsC200( + self.port_handle, + self.channel_masks[channel], + timing.btr0, + timing.btr1, + ) + LOG.info( + "xlCanSetChannelParamsC200: BTR0=%#02x, BTR1=%#02x", + timing.btr0, + timing.btr1, + ) + elif timing.f_clock == 16_000_000: chip_params = xlclass.XLchipParams() - chip_params.bitRate = bitrate - chip_params.sjw = sjw - chip_params.tseg1 = tseg1 - chip_params.tseg2 = tseg2 - chip_params.sam = sam + chip_params.bitRate = timing.bitrate + chip_params.sjw = timing.sjw + chip_params.tseg1 = timing.tseg1 + chip_params.tseg2 = timing.tseg2 + chip_params.sam = timing.nof_samples self.xldriver.xlCanSetChannelParams( self.port_handle, self.channel_masks[channel], @@ -441,94 +448,33 @@ def _set_bitrate_can( chip_params.tseg2, ) else: - self.xldriver.xlCanSetChannelBitrate( - self.port_handle, - self.channel_masks[channel], - bitrate, + raise CanInitializationError( + f"timing.f_clock must be 8_000_000 or 16_000_000 (is {timing.f_clock})" ) - LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate) - - if self.__testing: - return - # Compare requested CAN settings to active settings - bus_params = self._read_bus_params(channel) - settings_acceptable = True - - # check bus type - settings_acceptable &= ( - bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN - ) - - # check CAN operation mode. For CANcaseXL can_op_mode remains 0 - if bus_params.can.can_op_mode != 0: - settings_acceptable &= bool( - bus_params.can.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 - ) - - # check bitrate - settings_acceptable &= abs(bus_params.can.bitrate - bitrate) < bitrate / 256 - - # check sample point - if all(kwargs): - requested_sample_point = ( - 100 - * (1 + tseg1) # type: ignore[operator] - / (1 + tseg1 + tseg2) # type: ignore[operator] - ) - actual_sample_point = ( - 100 - * (1 + bus_params.can.tseg1) - / (1 + bus_params.can.tseg1 + bus_params.can.tseg2) - ) - settings_acceptable &= ( - abs(actual_sample_point - requested_sample_point) - < 1.0 # 1 percent threshold - ) - - if not settings_acceptable: - active_settings = ", ".join( - [ - f"{key}: {getattr(val, 'name', val)}" # print int or Enum/Flag name - for key, val in bus_params.can._asdict().items() - ] - ) - raise CanInitializationError( - f"The requested CAN settings could not be set for channel {channel}. " - f"Another application might have set incompatible settings. " - f"These are the currently active settings: {active_settings}" + if not self.__testing: + self._check_can_settings( + channel=channel, + bitrate=timing.bitrate, + sample_point=timing.sample_point, ) - def _set_bitrate_canfd( + def _set_bit_timing_fd( self, channel: int, - bitrate: Optional[int] = None, - data_bitrate: Optional[int] = None, - sjw_abr: int = 2, - tseg1_abr: int = 6, - tseg2_abr: int = 3, - sjw_dbr: int = 2, - tseg1_dbr: int = 6, - tseg2_dbr: int = 3, + timing: BitTimingFd, ) -> None: # set parameters if channel has init access if self._has_init_access(channel): canfd_conf = xlclass.XLcanFdConf() - if bitrate: - canfd_conf.arbitrationBitRate = int(bitrate) - else: - canfd_conf.arbitrationBitRate = 500_000 - canfd_conf.sjwAbr = int(sjw_abr) - canfd_conf.tseg1Abr = int(tseg1_abr) - canfd_conf.tseg2Abr = int(tseg2_abr) - if data_bitrate: - canfd_conf.dataBitRate = int(data_bitrate) - else: - canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate) - canfd_conf.sjwDbr = int(sjw_dbr) - canfd_conf.tseg1Dbr = int(tseg1_dbr) - canfd_conf.tseg2Dbr = int(tseg2_dbr) + canfd_conf.arbitrationBitRate = timing.nom_bitrate + canfd_conf.sjwAbr = timing.nom_sjw + canfd_conf.tseg1Abr = timing.nom_tseg1 + canfd_conf.tseg2Abr = timing.nom_tseg2 + canfd_conf.dataBitRate = timing.data_bitrate + canfd_conf.sjwDbr = timing.data_sjw + canfd_conf.tseg1Dbr = timing.data_tseg1 + canfd_conf.tseg2Dbr = timing.data_tseg2 self.xldriver.xlCanFdSetConfiguration( self.port_handle, self.channel_masks[channel], canfd_conf ) @@ -550,11 +496,29 @@ def _set_bitrate_canfd( canfd_conf.tseg2Dbr, ) - if self.__testing: - return + if not self.__testing: + self._check_can_settings( + channel=channel, + bitrate=timing.nom_bitrate, + sample_point=timing.nom_sample_point, + fd=True, + data_bitrate=timing.data_bitrate, + data_sample_point=timing.data_sample_point, + ) - # Compare requested CAN settings to active settings + def _check_can_settings( + self, + channel: int, + bitrate: int, + sample_point: Optional[float] = None, + fd: bool = False, + data_bitrate: Optional[int] = None, + data_sample_point: Optional[float] = None, + ) -> None: + """Compare requested CAN settings to active settings in driver.""" bus_params = self._read_bus_params(channel) + # use canfd even if fd==False, bus_params.can and bus_params.canfd are a C union + bus_params_data = bus_params.canfd settings_acceptable = True # check bus type @@ -563,60 +527,68 @@ def _set_bitrate_canfd( ) # check CAN operation mode - settings_acceptable &= bool( - bus_params.canfd.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD - ) + if fd: + settings_acceptable &= bool( + bus_params_data.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD + ) + elif bus_params_data.can_op_mode != 0: # can_op_mode is always 0 for cancaseXL + settings_acceptable &= bool( + bus_params_data.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 + ) # check bitrates if bitrate: settings_acceptable &= ( - abs(bus_params.canfd.bitrate - bitrate) < bitrate / 256 + abs(bus_params_data.bitrate - bitrate) < bitrate / 256 ) - if data_bitrate: + if fd and data_bitrate: settings_acceptable &= ( - abs(bus_params.canfd.data_bitrate - data_bitrate) < data_bitrate / 256 + abs(bus_params_data.data_bitrate - data_bitrate) < data_bitrate / 256 ) # check sample points - if bitrate: - requested_nom_sample_point = ( - 100 * (1 + tseg1_abr) / (1 + tseg1_abr + tseg2_abr) - ) - actual_nom_sample_point = ( + if sample_point: + nom_sample_point_act = ( 100 - * (1 + bus_params.canfd.tseg1_abr) - / (1 + bus_params.canfd.tseg1_abr + bus_params.canfd.tseg2_abr) + * (1 + bus_params_data.tseg1_abr) + / (1 + bus_params_data.tseg1_abr + bus_params_data.tseg2_abr) ) settings_acceptable &= ( - abs(actual_nom_sample_point - requested_nom_sample_point) - < 1.0 # 1 percent threshold - ) - if data_bitrate: - requested_data_sample_point = ( - 100 * (1 + tseg1_dbr) / (1 + tseg1_dbr + tseg2_dbr) + abs(nom_sample_point_act - sample_point) < 2.0 # 2 percent tolerance ) - actual_data_sample_point = ( + if fd and data_sample_point: + data_sample_point_act = ( 100 - * (1 + bus_params.canfd.tseg1_dbr) - / (1 + bus_params.canfd.tseg1_dbr + bus_params.canfd.tseg2_dbr) + * (1 + bus_params_data.tseg1_dbr) + / (1 + bus_params_data.tseg1_dbr + bus_params_data.tseg2_dbr) ) settings_acceptable &= ( - abs(actual_data_sample_point - requested_data_sample_point) - < 1.0 # 1 percent threshold + abs(data_sample_point_act - data_sample_point) + < 2.0 # 2 percent tolerance ) if not settings_acceptable: - active_settings = ", ".join( - [ - f"{key}: {getattr(val, 'name', val)}" # print int or Enum/Flag name - for key, val in bus_params.canfd._asdict().items() - ] + # The error message depends on the currently active CAN settings. + # If the active operation mode is CAN FD, show the active CAN FD timings, + # otherwise show CAN 2.0 timings. + if bool( + bus_params_data.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD + ): + active_settings = bus_params.canfd._asdict() + active_settings["can_op_mode"] = "CAN FD" + else: + active_settings = bus_params.can._asdict() + active_settings["can_op_mode"] = "CAN 2.0" + settings_string = ", ".join( + [f"{key}: {val}" for key, val in active_settings.items()] ) raise CanInitializationError( - f"The requested CAN FD settings could not be set for channel {channel}. " + f"The requested settings could not be set for channel {channel}. " f"Another application might have set incompatible settings. " - f"These are the currently active settings: {active_settings}." + f"These are the currently active settings: {settings_string}." ) def _apply_filters(self, filters: Optional[CanFilters]) -> None: diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 8df39e9dc..29791e32f 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -201,7 +201,17 @@ def check_status_initialization(result, function, arguments): ctypes.POINTER(xlclass.XLchipParams), ] xlCanSetChannelParams.restype = xlclass.XLstatus -xlCanSetChannelParams.errcheck = check_status_operation +xlCanSetChannelParams.errcheck = check_status_initialization + +xlCanSetChannelParamsC200 = _xlapi_dll.xlCanSetChannelParamsC200 +xlCanSetChannelParamsC200.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_ubyte, + ctypes.c_ubyte, +] +xlCanSetChannelParams.restype = xlclass.XLstatus +xlCanSetChannelParams.errcheck = check_status_initialization xlCanTransmit = _xlapi_dll.xlCanTransmit xlCanTransmit.argtypes = [ diff --git a/test/test_vector.py b/test/test_vector.py index fd95cef8a..7694b31aa 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -193,12 +193,12 @@ def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None: fd=True, bitrate=500_000, data_bitrate=2_000_000, - sjw_abr=10, - tseg1_abr=11, - tseg2_abr=12, - sjw_dbr=13, - tseg1_dbr=14, - tseg2_dbr=15, + sjw_abr=16, + tseg1_abr=127, + tseg2_abr=32, + sjw_dbr=6, + tseg1_dbr=27, + tseg2_dbr=12, _testing=True, ) assert isinstance(bus, canlib.VectorBus) @@ -222,12 +222,12 @@ def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None: canFdConf = xlCanFdSetConfiguration_args[2] assert canFdConf.arbitrationBitRate == 500000 assert canFdConf.dataBitRate == 2000000 - assert canFdConf.sjwAbr == 10 - assert canFdConf.tseg1Abr == 11 - assert canFdConf.tseg2Abr == 12 - assert canFdConf.sjwDbr == 13 - assert canFdConf.tseg1Dbr == 14 - assert canFdConf.tseg2Dbr == 15 + assert canFdConf.sjwAbr == 16 + assert canFdConf.tseg1Abr == 127 + assert canFdConf.tseg2Abr == 32 + assert canFdConf.sjwDbr == 6 + assert canFdConf.tseg1Dbr == 27 + assert canFdConf.tseg2Dbr == 12 @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") @@ -239,12 +239,12 @@ def test_bus_creation_fd_bitrate_timings() -> None: fd=True, bitrate=500_000, data_bitrate=2_000_000, - sjw_abr=10, - tseg1_abr=11, - tseg2_abr=12, - sjw_dbr=13, - tseg1_dbr=14, - tseg2_dbr=15, + sjw_abr=16, + tseg1_abr=127, + tseg2_abr=32, + sjw_dbr=6, + tseg1_dbr=27, + tseg2_dbr=12, ) xl_channel_config = _find_xl_channel_config( @@ -259,18 +259,45 @@ def test_bus_creation_fd_bitrate_timings() -> None: & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD ) assert xl_channel_config.busParams.data.canFD.arbitrationBitRate == 500_000 - assert xl_channel_config.busParams.data.canFD.sjwAbr == 10 - assert xl_channel_config.busParams.data.canFD.tseg1Abr == 11 - assert xl_channel_config.busParams.data.canFD.tseg2Abr == 12 - assert xl_channel_config.busParams.data.canFD.sjwDbr == 13 - assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 14 - assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 15 + assert xl_channel_config.busParams.data.canFD.sjwAbr == 16 + assert xl_channel_config.busParams.data.canFD.tseg1Abr == 127 + assert xl_channel_config.busParams.data.canFD.tseg2Abr == 32 + assert xl_channel_config.busParams.data.canFD.sjwDbr == 6 + assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 27 + assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 12 assert xl_channel_config.busParams.data.canFD.dataBitRate == 2_000_000 bus.shutdown() -def test_bus_creation_timing_mocked(mock_xldriver) -> None: +def test_bus_creation_timing_8mhz_mocked(mock_xldriver) -> None: + timing = can.BitTiming.from_bitrate_and_segments( + f_clock=8_000_000, + bitrate=125_000, + tseg1=13, + tseg2=2, + sjw=1, + ) + bus = can.Bus(channel=0, interface="vector", timing=timing, _testing=True) + assert isinstance(bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value + assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelParamsC200.assert_called() + btr0, btr1 = ( + can.interfaces.vector.canlib.xldriver.xlCanSetChannelParamsC200.call_args[0] + )[2:] + assert btr0 == timing.btr0 + assert btr1 == timing.btr1 + + +def test_bus_creation_timing_16mhz_mocked(mock_xldriver) -> None: timing = can.BitTiming.from_bitrate_and_segments( f_clock=16_000_000, bitrate=125_000, @@ -302,30 +329,31 @@ def test_bus_creation_timing_mocked(mock_xldriver) -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_bus_creation_timing() -> None: - timing = can.BitTiming.from_bitrate_and_segments( - f_clock=16_000_000, - bitrate=125_000, - tseg1=13, - tseg2=2, - sjw=1, - ) - bus = can.Bus( - channel=0, - serial=_find_virtual_can_serial(), - interface="vector", - timing=timing, - ) - assert isinstance(bus, canlib.VectorBus) - - xl_channel_config = _find_xl_channel_config( - serial=_find_virtual_can_serial(), channel=0 - ) - assert xl_channel_config.busParams.data.can.bitRate == 125_000 - assert xl_channel_config.busParams.data.can.sjw == 1 - assert xl_channel_config.busParams.data.can.tseg1 == 13 - assert xl_channel_config.busParams.data.can.tseg2 == 2 - - bus.shutdown() + for f_clock in [8_000_000, 16_000_000]: + timing = can.BitTiming.from_bitrate_and_segments( + f_clock=f_clock, + bitrate=125_000, + tseg1=13, + tseg2=2, + sjw=1, + ) + bus = can.Bus( + channel=0, + serial=_find_virtual_can_serial(), + interface="vector", + timing=timing, + ) + assert isinstance(bus, canlib.VectorBus) + + xl_channel_config = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert xl_channel_config.busParams.data.can.bitRate == 125_000 + assert xl_channel_config.busParams.data.can.sjw == 1 + assert xl_channel_config.busParams.data.can.tseg1 == 13 + assert xl_channel_config.busParams.data.can.tseg2 == 2 + + bus.shutdown() def test_bus_creation_timingfd_mocked(mock_xldriver) -> None: From 814051e489f8008d475c6b6acaaabde2b8842da9 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 4 Feb 2023 01:06:34 +0100 Subject: [PATCH 1016/1235] Add BitTiming/BitTimingFd support to NiXNETcanBus (#1520) --- can/interfaces/nixnet.py | 295 +++++++++++++++++++++++++-------------- setup.py | 2 +- 2 files changed, 188 insertions(+), 109 deletions(-) diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index e304fbc1f..6c3e63697 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -10,21 +10,28 @@ import logging import os +import time +from queue import SimpleQueue from types import ModuleType -from typing import Optional +from typing import Optional, List, Union, Tuple, Any -from can import BusABC, Message -from ..exceptions import ( +import can.typechecking +from can import BusABC, Message, BitTiming, BitTimingFd +from can.exceptions import ( CanInitializationError, CanOperationError, CanInterfaceNotImplementedError, ) +from can.util import check_or_adjust_timing_clock, deprecated_args_alias logger = logging.getLogger(__name__) nixnet: Optional[ModuleType] = None try: import nixnet # type: ignore + import nixnet.constants # type: ignore + import nixnet.system # type: ignore + import nixnet.types # type: ignore except Exception as exc: logger.warning("Could not import nixnet: %s", exc) @@ -34,18 +41,25 @@ class NiXNETcanBus(BusABC): The CAN Bus implemented for the NI-XNET interface. """ + @deprecated_args_alias( + deprecation_start="4.2.0", + deprecation_end="5.0.0", + brs=None, + log_errors=None, + ) def __init__( self, - channel, - can_filters=None, - bitrate=None, - fd=False, - fd_bitrate=None, - brs=False, - can_termination=False, - log_errors=True, - **kwargs, - ): + channel: str = "CAN1", + bitrate: int = 500_000, + timing: Optional[Union[BitTiming, BitTimingFd]] = None, + can_filters: Optional[can.typechecking.CanFilters] = None, + receive_own_messages: bool = False, + can_termination: bool = False, + fd: bool = False, + fd_bitrate: Optional[int] = None, + poll_interval: float = 0.001, + **kwargs: Any, + ) -> None: """ :param str channel: Name of the object to open (e.g. 'CAN0') @@ -53,13 +67,21 @@ def __init__( :param int bitrate: Bitrate in bits/s + :param timing: + Optional :class:`~can.BitTiming` or :class:`~can.BitTimingFd` instance + to use for custom bit timing setting. The `f_clock` value of the timing + instance must be set to 40_000_000 (40MHz). + If this parameter is provided, it takes precedence over all other + timing-related parameters like `bitrate`, `fd_bitrate` and `fd`. + :param list can_filters: See :meth:`can.BusABC.set_filters`. - :param bool log_errors: - If True, communication errors will appear as CAN messages with - ``is_error_frame`` set to True and ``arbitration_id`` will identify - the error (default True) + :param receive_own_messages: + Enable self-reception of sent messages. + + :param poll_interval: + Poll interval in seconds. :raises ~can.exceptions.CanInitializationError: If starting communication fails @@ -73,52 +95,85 @@ def __init__( if nixnet is None: raise CanInterfaceNotImplementedError("The NI-XNET API has not been loaded") - self._rx_queue = [] + self.nixnet = nixnet + + self._rx_queue = SimpleQueue() # type: ignore[var-annotated] self.channel = channel self.channel_info = "NI-XNET: " + channel - # Set database for the initialization - if not fd: - database_name = ":memory:" - else: - if not brs: - database_name = ":can_fd:" - else: - database_name = ":can_fd_brs:" + self.poll_interval = poll_interval - try: + self.fd = isinstance(timing, BitTimingFd) if timing else fd - # We need two sessions for this application, one to send frames and another to receive them + # Set database for the initialization + database_name = ":can_fd_brs:" if self.fd else ":memory:" - self.__session_send = nixnet.session.FrameOutStreamSession( + try: + # We need two sessions for this application, + # one to send frames and another to receive them + self._session_send = nixnet.session.FrameOutStreamSession( channel, database_name=database_name ) - self.__session_receive = nixnet.session.FrameInStreamSession( + self._session_receive = nixnet.session.FrameInStreamSession( channel, database_name=database_name ) + self._interface = self._session_send.intf + + # set interface properties + self._interface.can_lstn_only = kwargs.get("listen_only", False) + self._interface.echo_tx = receive_own_messages + self._interface.bus_err_to_in_strm = True + + if isinstance(timing, BitTimingFd): + timing = check_or_adjust_timing_clock(timing, [40_000_000]) + custom_nom_baud_rate = ( # nxPropSession_IntfBaudRate64 + 0xA0000000 + + (timing.nom_tq << 32) + + (timing.nom_sjw - 1 << 16) + + (timing.nom_tseg1 - 1 << 8) + + (timing.nom_tseg2 - 1) + ) + custom_data_baud_rate = ( # nxPropSession_IntfCanFdBaudRate64 + 0xA0000000 + + (timing.data_tq << 13) + + (timing.data_tseg1 - 1 << 8) + + (timing.data_tseg2 - 1 << 4) + + (timing.data_sjw - 1) + ) + self._interface.baud_rate = custom_nom_baud_rate + self._interface.can_fd_baud_rate = custom_data_baud_rate + elif isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, [40_000_000]) + custom_baud_rate = ( # nxPropSession_IntfBaudRate64 + 0xA0000000 + + (timing.tq << 32) + + (timing.sjw - 1 << 16) + + (timing.tseg1 - 1 << 8) + + (timing.tseg2 - 1) + ) + self._interface.baud_rate = custom_baud_rate + else: + # See page 1017 of NI-XNET Hardware and Software Manual + # to set custom can configuration + if bitrate: + self._interface.baud_rate = bitrate + + if self.fd: + # See page 951 of NI-XNET Hardware and Software Manual + # to set custom can configuration + self._interface.can_fd_baud_rate = fd_bitrate or bitrate + + _can_termination = ( + nixnet.constants.CanTerm.ON + if can_termination + else nixnet.constants.CanTerm.OFF + ) + self._interface.can_term = _can_termination - # We stop the sessions to allow reconfiguration, as by default they autostart at creation - self.__session_send.stop() - self.__session_receive.stop() - - # See page 1017 of NI-XNET Hardware and Software Manual to set custom can configuration - if bitrate: - self.__session_send.intf.baud_rate = bitrate - self.__session_receive.intf.baud_rate = bitrate - - if fd_bitrate: - # See page 951 of NI-XNET Hardware and Software Manual to set custom can configuration - self.__session_send.intf.can_fd_baud_rate = fd_bitrate - self.__session_receive.intf.can_fd_baud_rate = fd_bitrate - - if can_termination: - self.__session_send.intf.can_term = nixnet.constants.CanTerm.ON - self.__session_receive.intf.can_term = nixnet.constants.CanTerm.ON - - self.__session_receive.queue_size = 512 - # Once that all the parameters have been restarted, we start the sessions - self.__session_send.start() - self.__session_receive.start() + # self._session_receive.queue_size = 512 + # Once that all the parameters have been set, we start the sessions + self._session_send.start() + self._session_receive.start() except nixnet.errors.XnetError as error: raise CanInitializationError( @@ -130,44 +185,65 @@ def __init__( channel=channel, can_filters=can_filters, bitrate=bitrate, - log_errors=log_errors, **kwargs, ) - def _recv_internal(self, timeout): - try: - if len(self._rx_queue) == 0: - fr = self.__session_receive.frames.read(4, timeout=0) - for f in fr: - self._rx_queue.append(f) - can_frame = self._rx_queue.pop(0) - - # Timestamp should be converted from raw frame format(100ns increment from(12:00 a.m. January 1 1601 Coordinated - # Universal Time (UTC)) to epoch time(number of seconds from January 1, 1970 (midnight UTC/GMT)) + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: + end_time = time.perf_counter() + timeout if timeout is not None else None + + while True: + # try to read all available frames + for frame in self._session_receive.frames.read(1024, timeout=0): + self._rx_queue.put_nowait(frame) + + if self._rx_queue.qsize(): + break + + # check for timeout + if end_time is not None and time.perf_counter() > end_time: + return None, False + + # Wait a short time until we try to read again + time.sleep(self.poll_interval) + + can_frame = self._rx_queue.get_nowait() + + # Timestamp should be converted from raw frame format(100ns increment + # from(12:00 a.m. January 1 1601 Coordinated Universal Time (UTC)) + # to epoch time(number of seconds from January 1, 1970 (midnight UTC/GMT)) + timestamp = can_frame.timestamp * 1e-7 - 11_644_473_600 + if can_frame.type is self.nixnet.constants.FrameType.CAN_BUS_ERROR: msg = Message( - timestamp=can_frame.timestamp / 10000000.0 - 11644473600, + timestamp=timestamp, channel=self.channel, - is_remote_frame=can_frame.type == nixnet.constants.FrameType.CAN_REMOTE, - is_error_frame=can_frame.type - == nixnet.constants.FrameType.CAN_BUS_ERROR, + is_error_frame=True, + ) + else: + msg = Message( + timestamp=timestamp, + channel=self.channel, + is_remote_frame=can_frame.type + is self.nixnet.constants.FrameType.CAN_REMOTE, + is_error_frame=False, is_fd=( - can_frame.type == nixnet.constants.FrameType.CANFD_DATA - or can_frame.type == nixnet.constants.FrameType.CANFDBRS_DATA + can_frame.type is self.nixnet.constants.FrameType.CANFD_DATA + or can_frame.type is self.nixnet.constants.FrameType.CANFDBRS_DATA + ), + bitrate_switch=( + can_frame.type is self.nixnet.constants.FrameType.CANFDBRS_DATA ), - bitrate_switch=can_frame.type - == nixnet.constants.FrameType.CANFDBRS_DATA, is_extended_id=can_frame.identifier.extended, # Get identifier from CanIdentifier structure arbitration_id=can_frame.identifier.identifier, dlc=len(can_frame.payload), data=can_frame.payload, + is_rx=not can_frame.echo, ) + return msg, False - return msg, self._filters is None - except Exception: - return None, self._filters is None - - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: """ Send a message using NI-XNET. @@ -182,80 +258,83 @@ def send(self, msg, timeout=None): It does not wait for message to be ACKed currently. """ if timeout is None: - timeout = nixnet.constants.TIMEOUT_INFINITE + timeout = self.nixnet.constants.TIMEOUT_INFINITE if msg.is_remote_frame: - type_message = nixnet.constants.FrameType.CAN_REMOTE + type_message = self.nixnet.constants.FrameType.CAN_REMOTE elif msg.is_error_frame: - type_message = nixnet.constants.FrameType.CAN_BUS_ERROR + type_message = self.nixnet.constants.FrameType.CAN_BUS_ERROR elif msg.is_fd: if msg.bitrate_switch: - type_message = nixnet.constants.FrameType.CANFDBRS_DATA + type_message = self.nixnet.constants.FrameType.CANFDBRS_DATA else: - type_message = nixnet.constants.FrameType.CANFD_DATA + type_message = self.nixnet.constants.FrameType.CANFD_DATA else: - type_message = nixnet.constants.FrameType.CAN_DATA + type_message = self.nixnet.constants.FrameType.CAN_DATA - can_frame = nixnet.types.CanFrame( - nixnet.types.CanIdentifier(msg.arbitration_id, msg.is_extended_id), + can_frame = self.nixnet.types.CanFrame( + self.nixnet.types.CanIdentifier(msg.arbitration_id, msg.is_extended_id), type=type_message, payload=msg.data, ) try: - self.__session_send.frames.write([can_frame], timeout) - except nixnet.errors.XnetError as error: + self._session_send.frames.write([can_frame], timeout) + except self.nixnet.errors.XnetError as error: raise CanOperationError( f"{error.args[0]} ({error.error_type})", error.error_code ) from None - def reset(self): + def reset(self) -> None: """ Resets network interface. Stops network interface, then resets the CAN chip to clear the CAN error counters (clear error passive state). Resetting includes clearing all entries from read and write queues. """ - self.__session_send.flush() - self.__session_receive.flush() + self._session_send.flush() + self._session_receive.flush() - self.__session_send.stop() - self.__session_receive.stop() + self._session_send.stop() + self._session_receive.stop() - self.__session_send.start() - self.__session_receive.start() + self._session_send.start() + self._session_receive.start() - def shutdown(self): + def shutdown(self) -> None: """Close object.""" super().shutdown() - self.__session_send.flush() - self.__session_receive.flush() - - self.__session_send.stop() - self.__session_receive.stop() + if hasattr(self, "_session_send"): + self._session_send.flush() + self._session_send.stop() + self._session_send.close() - self.__session_send.close() - self.__session_receive.close() + if hasattr(self, "_session_receive"): + self._session_receive.flush() + self._session_receive.stop() + self._session_receive.close() @staticmethod - def _detect_available_configs(): + def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: configs = [] try: - with nixnet.system.System() as nixnet_system: + with nixnet.system.System() as nixnet_system: # type: ignore[union-attr] for interface in nixnet_system.intf_refs_can: - cahnnel = str(interface) + channel = str(interface) logger.debug( - "Found channel index %d: %s", interface.port_num, cahnnel + "Found channel index %d: %s", interface.port_num, channel ) configs.append( { "interface": "nixnet", - "channel": cahnnel, + "channel": channel, "can_term_available": interface.can_term_cap - == nixnet.constants.CanTermCap.YES, + is nixnet.constants.CanTermCap.YES, # type: ignore[union-attr] + "supports_fd": interface.can_tcvr_cap + is nixnet.constants.CanTcvrCap.HS, # type: ignore[union-attr] } ) except Exception as error: logger.debug("An error occured while searching for configs: %s", str(error)) - return configs + return configs # type: ignore diff --git a/setup.py b/setup.py index bada45b77..8cabc8c02 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ "cantact": ["cantact>=0.0.7"], "cvector": ["python-can-cvector"], "gs_usb": ["gs_usb>=0.2.1"], - "nixnet": ["nixnet>=0.3.1"], + "nixnet": ["nixnet>=0.3.2"], "pcan": ["uptime~=3.0.1"], "remote": ["python-can-remote"], "sontheim": ["python-can-sontheim>=0.1.2"], From b16f8aa6529cacb4d57736dac77c214328953fca Mon Sep 17 00:00:00 2001 From: mikisama <41532794+mikisama@users.noreply.github.com> Date: Sat, 4 Feb 2023 21:10:45 +0800 Subject: [PATCH 1017/1235] Improve slcan.py (#1490) * Improve slcan.py 1. Improve receiving performance 2. Fix an issue that the first read may blocking even if the `timeout` parameter is not `None`. * use `read_all` to read out serial port data. * fix when the `timeout` parameter is 0, it cannot enter the while loop. * For performance reason, revert to using `read` to read serial data. * add `size=1` and renamed `new_data` to `new_byte` to make the code easier to understand and read. * fix the issue of returning `None` before receiving the full SLCAN message. * Due to the simplification of the control flow, the class variable `self._buffer` can be replaced by the local variable `buffer`. * Revert "Due to the simplification of the control flow," This reverts commit 3eb80b9f40d021bc8671274e4ad89845b03582f1. * Simplify the control flow for finding the end of a SLCAN message. * improve the `timeout` handling of slcan.py * Change the plain format calls to f-strings. * using `bytearray.fromhex` to parse the frame's data. * change `del self._buffer[:]` to `self._buffer.clear` since it's more readable. * Simplify the timeout handling * fix the issue when the returned string is `None`. * using `pyserial.reset_input_buffer` to discard the data in the input buffer. * fix failing PyPy test * fix failing PyPy test * improve the comments --- can/interfaces/slcan.py | 147 ++++++++++++++++------------------------ test/test_slcan.py | 33 ++++++--- 2 files changed, 81 insertions(+), 99 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 212c4c85c..bcb3121ca 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -64,6 +64,7 @@ def __init__( btr: Optional[str] = None, sleep_after_open: float = _SLEEP_AFTER_SERIAL_OPEN, rtscts: bool = False, + timeout: float = 0.001, **kwargs: Any, ) -> None: """ @@ -82,7 +83,8 @@ def __init__( Time to wait in seconds after opening serial connection :param rtscts: turn hardware handshake (RTS/CTS) on and off - + :param timeout: + Timeout for the serial or usb device in seconds (default 0.001) :raise ValueError: if both ``bitrate`` and ``btr`` are set or the channel is invalid :raise CanInterfaceNotImplementedError: if the serial module is missing :raise CanInitializationError: if the underlying serial connection could not be established @@ -98,7 +100,10 @@ def __init__( with error_check(exception_type=CanInitializationError): self.serialPortOrig = serial.serial_for_url( - channel, baudrate=ttyBaudrate, rtscts=rtscts + channel, + baudrate=ttyBaudrate, + rtscts=rtscts, + timeout=timeout, ) self._buffer = bytearray() @@ -150,46 +155,34 @@ def _write(self, string: str) -> None: self.serialPortOrig.flush() def _read(self, timeout: Optional[float]) -> Optional[str]: + _timeout = serial.Timeout(timeout) with error_check("Could not read from serial device"): - # first read what is already in receive buffer - while self.serialPortOrig.in_waiting: - self._buffer += self.serialPortOrig.read() - # if we still don't have a complete message, do a blocking read - start = time.time() - time_left = timeout - while not ( - ord(self._OK) in self._buffer or ord(self._ERROR) in self._buffer - ): - self.serialPortOrig.timeout = time_left - byte = self.serialPortOrig.read() - if byte: - self._buffer += byte - # if timeout is None, try indefinitely - if timeout is None: - continue - # try next one only if there still is time, and with - # reduced timeout - else: - time_left = timeout - (time.time() - start) - if time_left > 0: - continue + while True: + # Due to accessing `serialPortOrig.in_waiting` too often will reduce the performance. + # We read the `serialPortOrig.in_waiting` only once here. + in_waiting = self.serialPortOrig.in_waiting + for _ in range(max(1, in_waiting)): + new_byte = self.serialPortOrig.read(size=1) + if new_byte: + self._buffer.extend(new_byte) else: - return None + break + + if new_byte in (self._ERROR, self._OK): + string = self._buffer.decode() + self._buffer.clear() + return string + + if _timeout.expired(): + break - # return first message - for i in range(len(self._buffer)): - if self._buffer[i] == ord(self._OK) or self._buffer[i] == ord(self._ERROR): - string = self._buffer[: i + 1].decode() - del self._buffer[: i + 1] - break - return string + return None def flush(self) -> None: - del self._buffer[:] + self._buffer.clear() with error_check("Could not flush"): - while self.serialPortOrig.in_waiting: - self.serialPortOrig.read() + self.serialPortOrig.reset_input_buffer() def open(self) -> None: self._write("O") @@ -204,7 +197,7 @@ def _recv_internal( canId = None remote = False extended = False - frame = [] + data = None string = self._read(timeout) @@ -215,14 +208,12 @@ def _recv_internal( canId = int(string[1:9], 16) dlc = int(string[9]) extended = True - for i in range(0, dlc): - frame.append(int(string[10 + i * 2 : 12 + i * 2], 16)) + data = bytearray.fromhex(string[10 : 10 + dlc * 2]) elif string[0] == "t": # normal frame canId = int(string[1:4], 16) dlc = int(string[4]) - for i in range(0, dlc): - frame.append(int(string[5 + i * 2 : 7 + i * 2], 16)) + data = bytearray.fromhex(string[5 : 5 + dlc * 2]) elif string[0] == "r": # remote frame canId = int(string[1:4], 16) @@ -242,7 +233,7 @@ def _recv_internal( timestamp=time.time(), # Better than nothing... is_remote_frame=remote, dlc=dlc, - data=frame, + data=data, ) return msg, False return None, False @@ -252,15 +243,15 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: self.serialPortOrig.write_timeout = timeout if msg.is_remote_frame: if msg.is_extended_id: - sendStr = "R%08X%d" % (msg.arbitration_id, msg.dlc) + sendStr = f"R{msg.arbitration_id:08X}{msg.dlc:d}" else: - sendStr = "r%03X%d" % (msg.arbitration_id, msg.dlc) + sendStr = f"r{msg.arbitration_id:03X}{msg.dlc:d}" else: if msg.is_extended_id: - sendStr = "T%08X%d" % (msg.arbitration_id, msg.dlc) + sendStr = f"T{msg.arbitration_id:08X}{msg.dlc:d}" else: - sendStr = "t%03X%d" % (msg.arbitration_id, msg.dlc) - sendStr += "".join(["%02X" % b for b in msg.data]) + sendStr = f"t{msg.arbitration_id:03X}{msg.dlc:d}" + sendStr += msg.data.hex().upper() self._write(sendStr) def shutdown(self) -> None: @@ -295,29 +286,17 @@ def get_version( cmd = "V" self._write(cmd) - start = time.time() - time_left = timeout - while True: - string = self._read(time_left) - - if not string: - pass - elif string[0] == cmd and len(string) == 6: - # convert ASCII coded version - hw_version = int(string[1:3]) - sw_version = int(string[3:5]) - return hw_version, sw_version - # if timeout is None, try indefinitely - if timeout is None: - continue - # try next one only if there still is time, and with - # reduced timeout - else: - time_left = timeout - (time.time() - start) - if time_left > 0: - continue - else: - return None, None + string = self._read(timeout) + + if not string: + pass + elif string[0] == cmd and len(string) == 6: + # convert ASCII coded version + hw_version = int(string[1:3]) + sw_version = int(string[3:5]) + return hw_version, sw_version + + return None, None def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: """Get serial number of the slcan interface. @@ -331,24 +310,12 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: cmd = "N" self._write(cmd) - start = time.time() - time_left = timeout - while True: - string = self._read(time_left) - - if not string: - pass - elif string[0] == cmd and len(string) == 6: - serial_number = string[1:-1] - return serial_number - # if timeout is None, try indefinitely - if timeout is None: - continue - # try next one only if there still is time, and with - # reduced timeout - else: - time_left = timeout - (time.time() - start) - if time_left > 0: - continue - else: - return None + string = self._read(timeout) + + if not string: + pass + elif string[0] == cmd and len(string) == 6: + serial_number = string[1:-1] + return serial_number + + return None diff --git a/test/test_slcan.py b/test/test_slcan.py index aa97e518b..8db2d402a 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -2,11 +2,26 @@ import unittest import can +from .config import IS_PYPY + + +""" +Mentioned in #1010 & #1490 + +> PyPy works best with pure Python applications. Whenever you use a C extension module, +> it runs much slower than in CPython. The reason is that PyPy can't optimize C extension modules since they're not fully supported. +> In addition, PyPy has to emulate reference counting for that part of the code, making it even slower. + +https://realpython.com/pypy-faster-python/#it-doesnt-work-well-with-c-extensions +""" +TIMEOUT = 0.5 if IS_PYPY else 0.001 # 0.001 is the default set in slcanBus class slcanTestCase(unittest.TestCase): def setUp(self): - self.bus = can.Bus("loop://", interface="slcan", sleep_after_open=0) + self.bus = can.Bus( + "loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT + ) self.serial = self.bus.serialPortOrig self.serial.read(self.serial.in_waiting) @@ -15,7 +30,7 @@ def tearDown(self): def test_recv_extended(self): self.serial.write(b"T12ABCDEF2AA55\r") - msg = self.bus.recv(0) + msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) self.assertEqual(msg.is_extended_id, True) @@ -33,7 +48,7 @@ def test_send_extended(self): def test_recv_standard(self): self.serial.write(b"t4563112233\r") - msg = self.bus.recv(0) + msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x456) self.assertEqual(msg.is_extended_id, False) @@ -51,7 +66,7 @@ def test_send_standard(self): def test_recv_standard_remote(self): self.serial.write(b"r1238\r") - msg = self.bus.recv(0) + msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x123) self.assertEqual(msg.is_extended_id, False) @@ -68,7 +83,7 @@ def test_send_standard_remote(self): def test_recv_extended_remote(self): self.serial.write(b"R12ABCDEF6\r") - msg = self.bus.recv(0) + msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) self.assertEqual(msg.is_extended_id, True) @@ -85,11 +100,11 @@ def test_send_extended_remote(self): def test_partial_recv(self): self.serial.write(b"T12ABCDEF") - msg = self.bus.recv(0) + msg = self.bus.recv(TIMEOUT) self.assertIsNone(msg) self.serial.write(b"2AA55\rT12") - msg = self.bus.recv(0) + msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) self.assertEqual(msg.is_extended_id, True) @@ -97,11 +112,11 @@ def test_partial_recv(self): self.assertEqual(msg.dlc, 2) self.assertSequenceEqual(msg.data, [0xAA, 0x55]) - msg = self.bus.recv(0) + msg = self.bus.recv(TIMEOUT) self.assertIsNone(msg) self.serial.write(b"ABCDEF2AA55\r") - msg = self.bus.recv(0) + msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) def test_version(self): From 75fdfe45c43746532173152a05ac50c647d912bb Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Mon, 6 Feb 2023 09:23:15 +0100 Subject: [PATCH 1018/1235] Implement BitTiming/BitTimingFD handling for PCAN Bus (#1514) * add BitTiming parameter to PcanBus * Move valid CAN/FD clocks to pcan/basic * Add additional PCAN constructor tests * Add tests for BitTiming with PCAN constructor * Unify PCAN constructor code paths for FD with/without timing --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/pcan/basic.py | 10 +++++ can/interfaces/pcan/pcan.py | 78 +++++++++++++++++++++++------------ test/test_pcan.py | 79 +++++++++++++++++++++++++++++++++--- 3 files changed, 135 insertions(+), 32 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 5f161eecc..b2624802a 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -655,6 +655,16 @@ class TPCANChannelInformation(Structure): "PCAN_LANBUS16": PCAN_LANBUS16, } +VALID_PCAN_CAN_CLOCKS = [8_000_000] + +VALID_PCAN_FD_CLOCKS = [ + 20_000_000, + 24_000_000, + 30_000_000, + 40_000_000, + 60_000_000, + 80_000_000, +] # /////////////////////////////////////////////////////////// # PCAN-Basic API function declarations diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 01adbe0c2..2ed0ff445 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -5,20 +5,21 @@ import time from datetime import datetime import platform -from typing import Optional, List, Tuple +from typing import Optional, List, Tuple, Union, Any from packaging import version from can import ( BusABC, BusState, + BitTiming, + BitTimingFd, + Message, CanError, CanOperationError, CanInitializationError, - Message, ) -from can.util import len2dlc, dlc2len - +from can.util import check_or_adjust_timing_clock, dlc2len, len2dlc from .basic import ( PCAN_BITRATES, PCAN_FD_PARAMETER_LIST, @@ -61,8 +62,11 @@ FEATURE_FD_CAPABLE, PCAN_DICT_STATUS, PCAN_BUSOFF_AUTORESET, + TPCANBaudrate, PCAN_ATTACHED_CHANNELS, TPCANChannelInformation, + VALID_PCAN_FD_CLOCKS, + VALID_PCAN_CAN_CLOCKS, ) @@ -112,12 +116,12 @@ class PcanBus(BusABC): def __init__( self, - channel="PCAN_USBBUS1", - device_id=None, - state=BusState.ACTIVE, - bitrate=500000, - *args, - **kwargs, + channel: str = "PCAN_USBBUS1", + device_id: Optional[int] = None, + state: BusState = BusState.ACTIVE, + timing: Optional[Union[BitTiming, BitTimingFd]] = None, + bitrate: int = 500000, + **kwargs: Any, ): """A PCAN USB interface to CAN. @@ -142,6 +146,18 @@ def __init__( BusState of the channel. Default is ACTIVE + :param timing: + An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd` + to specify the bit timing parameters for the PCAN interface. If this parameter + is provided, it takes precedence over all other timing-related parameters. + If this parameter is not provided, the bit timing parameters can be specified + using the `bitrate` parameter for standard CAN or the `fd`, `f_clock`, + `f_clock_mhz`, `nom_brp`, `nom_tseg1`, `nom_tseg2`, `nom_sjw`, `data_brp`, + `data_tseg1`, `data_tseg2`, and `data_sjw` parameters for CAN FD. + Note that the `f_clock` value of the `timing` instance must be 8_000_000 + for standard CAN or any of the following values for CAN FD: 20_000_000, + 24_000_000, 30_000_000, 40_000_000, 60_000_000, 80_000_000. + :param int bitrate: Bitrate of channel in bit/s. Default is 500 kbit/s. @@ -231,8 +247,7 @@ def __init__( raise ValueError(err_msg) self.channel_info = str(channel) - self.fd = kwargs.get("fd", False) - pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K) + self.fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False) hwtype = PCAN_TYPE_ISA ioport = 0x02A0 @@ -245,30 +260,41 @@ def __init__( self.check_api_version() - if state is BusState.ACTIVE or state is BusState.PASSIVE: + if state in [BusState.ACTIVE, BusState.PASSIVE]: self.state = state else: raise ValueError("BusState must be Active or Passive") - if self.fd: - f_clock_val = kwargs.get("f_clock", None) - if f_clock_val is None: - f_clock = "{}={}".format("f_clock_mhz", kwargs.get("f_clock_mhz", None)) - else: - f_clock = "{}={}".format("f_clock", kwargs.get("f_clock", None)) - - fd_parameters_values = [f_clock] + [ - f"{key}={kwargs.get(key, None)}" - for key in PCAN_FD_PARAMETER_LIST - if kwargs.get(key, None) is not None + if isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, VALID_PCAN_CAN_CLOCKS) + pcan_bitrate = TPCANBaudrate(timing.btr0 << 8 | timing.btr1) + result = self.m_objPCANBasic.Initialize( + self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt + ) + elif self.fd: + if isinstance(timing, BitTimingFd): + timing = check_or_adjust_timing_clock( + timing, sorted(VALID_PCAN_FD_CLOCKS, reverse=True) + ) + # We dump the timing parameters into the kwargs because they have equal names + # as the kwargs parameters and this saves us one additional code path + kwargs.update(timing) + + clock_param = "f_clock" if "f_clock" in kwargs else "f_clock_mhz" + fd_parameters_values = [ + f"{key}={kwargs[key]}" + for key in (clock_param,) + PCAN_FD_PARAMETER_LIST + if key in kwargs ] - self.fd_bitrate = " ,".join(fd_parameters_values).encode("ascii") + self.fd_bitrate = ", ".join(fd_parameters_values).encode("ascii") result = self.m_objPCANBasic.InitializeFD( self.m_PcanHandle, self.fd_bitrate ) + else: + pcan_bitrate = PCAN_BITRATES.get(bitrate, PCAN_BAUD_500K) result = self.m_objPCANBasic.Initialize( self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt ) @@ -312,7 +338,7 @@ def __init__( if result != PCAN_ERROR_OK: raise PcanCanInitializationError(self._get_formatted_error(result)) - super().__init__(channel=channel, state=state, bitrate=bitrate, *args, **kwargs) + super().__init__(channel=channel, state=state, bitrate=bitrate, **kwargs) def _find_channel_by_dev_id(self, device_id): """ diff --git a/test/test_pcan.py b/test/test_pcan.py index 01dac848c..aa0988a48 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -3,20 +3,18 @@ """ import ctypes -import platform import unittest from unittest import mock from unittest.mock import Mock, patch - import pytest from parameterized import parameterized import can from can.bus import BusState from can.exceptions import CanInitializationError -from can.interfaces.pcan.basic import * from can.interfaces.pcan import PcanBus, PcanError +from can.interfaces.pcan.basic import * class TestPCANBus(unittest.TestCase): @@ -53,8 +51,10 @@ def _mockGetValue(self, channel, parameter): def test_bus_creation(self) -> None: self.bus = can.Bus(interface="pcan") + self.assertIsInstance(self.bus, PcanBus) self.MockPCANBasic.assert_called_once() + self.mock_pcan.Initialize.assert_called_once() self.mock_pcan.InitializeFD.assert_not_called() @@ -62,13 +62,41 @@ def test_bus_creation_state_error(self) -> None: with self.assertRaises(ValueError): can.Bus(interface="pcan", state=BusState.ERROR) - def test_bus_creation_fd(self) -> None: - self.bus = can.Bus(interface="pcan", fd=True) + @parameterized.expand([("f_clock", 8_000_000), ("f_clock_mhz", 8)]) + def test_bus_creation_fd(self, clock_param: str, clock_val: int) -> None: + self.bus = can.Bus( + interface="pcan", + fd=True, + nom_brp=1, + nom_tseg1=129, + nom_tseg2=30, + nom_sjw=1, + data_brp=1, + data_tseg1=9, + data_tseg2=6, + data_sjw=1, + channel="PCAN_USBBUS1", + **{clock_param: clock_val}, + ) + self.assertIsInstance(self.bus, PcanBus) self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_not_called() self.mock_pcan.InitializeFD.assert_called_once() + # Retrieve second argument of first call + bitrate_arg = self.mock_pcan.InitializeFD.call_args[0][-1] + + self.assertTrue(f"{clock_param}={clock_val}".encode("ascii") in bitrate_arg) + self.assertTrue(b"nom_brp=1" in bitrate_arg) + self.assertTrue(b"nom_tseg1=129" in bitrate_arg) + self.assertTrue(b"nom_tseg2=30" in bitrate_arg) + self.assertTrue(b"nom_sjw=1" in bitrate_arg) + self.assertTrue(b"data_brp=1" in bitrate_arg) + self.assertTrue(b"data_tseg1=9" in bitrate_arg) + self.assertTrue(b"data_tseg2=6" in bitrate_arg) + self.assertTrue(b"data_sjw=1" in bitrate_arg) + def test_api_version_low(self) -> None: self.PCAN_API_VERSION_SIM = "1.0" with self.assertLogs("can.pcan", level="WARNING") as cm: @@ -333,6 +361,11 @@ def test_state(self, name, bus_state: BusState, expected_parameter) -> None: (PCAN_USBBUS1, PCAN_LISTEN_ONLY, expected_parameter), ) + def test_state_constructor(self): + for state in [BusState.ACTIVE, BusState.PASSIVE]: + bus = can.Bus(interface="pcan", state=state) + assert bus.state == state + def test_detect_available_configs(self) -> None: if platform.system() == "Darwin": self.mock_pcan.GetValue = Mock( @@ -381,7 +414,8 @@ def get_value_side_effect(handle, param): self.mock_pcan.GetValue = Mock(side_effect=get_value_side_effect) if expected_result == "error": - self.assertRaises(ValueError, can.Bus, interface="pcan", device_id=dev_id) + with self.assertRaises(ValueError): + can.Bus(interface="pcan", device_id=dev_id) else: self.bus = can.Bus(interface="pcan", device_id=dev_id) self.assertEqual(expected_result, self.bus.channel_info) @@ -416,6 +450,39 @@ def test_peak_fd_bus_constructor_regression(self): can.Bus(**params) + def test_constructor_bit_timing(self): + timing = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x47, btr1=0x2F) + can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing) + + bitrate_arg = self.mock_pcan.Initialize.call_args[0][1] + self.assertEqual(bitrate_arg.value, 0x472F) + + def test_constructor_bit_timing_fd(self): + timing = can.BitTimingFd( + f_clock=40_000_000, + nom_brp=1, + nom_tseg1=129, + nom_tseg2=30, + nom_sjw=1, + data_brp=1, + data_tseg1=9, + data_tseg2=6, + data_sjw=1, + ) + can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing) + + bitrate_arg = self.mock_pcan.InitializeFD.call_args[0][-1] + + self.assertTrue(b"f_clock=40000000" in bitrate_arg) + self.assertTrue(b"nom_brp=1" in bitrate_arg) + self.assertTrue(b"nom_tseg1=129" in bitrate_arg) + self.assertTrue(b"nom_tseg2=30" in bitrate_arg) + self.assertTrue(b"nom_sjw=1" in bitrate_arg) + self.assertTrue(b"data_brp=1" in bitrate_arg) + self.assertTrue(b"data_tseg1=9" in bitrate_arg) + self.assertTrue(b"data_tseg2=6" in bitrate_arg) + self.assertTrue(b"data_sjw=1" in bitrate_arg) + if __name__ == "__main__": unittest.main() From 40968170de9ff909f401fb38a9978de162379692 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 7 Mar 2023 08:41:51 +0100 Subject: [PATCH 1019/1235] Update linters (#1534) * update black to 23.1.0 * update mypy to 1.0.1 * update pylint to 2.16.4 * remove useless suppression * fix warning --- .pylintrc | 4 ++-- can/bus.py | 2 -- can/interface.py | 1 - can/interfaces/__init__.py | 3 ++- can/interfaces/ics_neovi/neovi_bus.py | 1 - can/interfaces/iscan.py | 1 - can/interfaces/pcan/basic.py | 15 +-------------- can/interfaces/serial/serial_can.py | 1 - can/interfaces/slcan.py | 1 - can/interfaces/udp_multicast/bus.py | 1 - can/interfaces/usb2can/usb2canInterface.py | 2 -- can/interfaces/virtual.py | 1 - can/io/asc.py | 2 -- can/io/canutils.py | 1 - can/io/csv.py | 1 - can/io/logger.py | 2 +- can/io/player.py | 1 - can/logconvert.py | 1 - can/player.py | 1 - can/thread_safe_bus.py | 2 -- examples/print_notifier.py | 1 - examples/receive_all.py | 1 - examples/send_one.py | 1 - examples/serial_com.py | 1 - examples/simple_log_converter.py | 3 +-- examples/vcan_filtered.py | 1 - requirements-lint.txt | 6 +++--- setup.py | 2 -- test/back2back_test.py | 3 --- test/serial_test.py | 1 - test/simplecyclic_test.py | 1 - test/test_pcan.py | 3 +-- test/test_player.py | 1 - test/test_util.py | 1 - 34 files changed, 11 insertions(+), 59 deletions(-) diff --git a/.pylintrc b/.pylintrc index cc4c50d88..bdbf47613 100644 --- a/.pylintrc +++ b/.pylintrc @@ -498,5 +498,5 @@ min-public-methods=2 # Exceptions that will emit a warning when being caught. Defaults to # "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception +overgeneral-exceptions=builtins.BaseException, + builtins.Exception diff --git a/can/bus.py b/can/bus.py index f29b8ea6e..292c754fb 100644 --- a/can/bus.py +++ b/can/bus.py @@ -93,7 +93,6 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: time_left = timeout while True: - # try to get a message msg, already_filtered = self._recv_internal(timeout=time_left) @@ -109,7 +108,6 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: # try next one only if there still is time, and with # reduced timeout else: - time_left = timeout - (time() - start) if time_left > 0: diff --git a/can/interface.py b/can/interface.py index 04fc84ae9..47b44fed1 100644 --- a/can/interface.py +++ b/can/interface.py @@ -171,7 +171,6 @@ def detect_available_configs( result = [] for interface in interfaces: - try: bus_class = _get_class_for_interface(interface) except CanInterfaceNotImplementedError: diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 3065e9bfd..c9ca6ca55 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -35,7 +35,8 @@ if sys.version_info >= (3, 8): from importlib.metadata import entry_points - # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, "Compatibility Note". + # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, + # "Compatibility Note". if sys.version_info >= (3, 10): BACKENDS.update( { diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 4972ed479..c3abc9f67 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -40,7 +40,6 @@ try: from filelock import FileLock except ImportError as ie: - logger.warning( "Using ICS neoVI can backend without the " "filelock module installed may cause some issues!: %s", diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index e76d9d060..a27494b61 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -157,7 +157,6 @@ def shutdown(self) -> None: class IscanError(CanError): - ERROR_CODES = { 0: "Success", 1: "No access to device", diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index b2624802a..d1b2121cb 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -670,6 +670,7 @@ class TPCANChannelInformation(Structure): # PCAN-Basic API function declarations # /////////////////////////////////////////////////////////// + # PCAN-Basic API class implementation # class PCANBasic: @@ -719,7 +720,6 @@ def Initialize( IOPort=c_uint(0), Interrupt=c_ushort(0), ): - """Initializes a PCAN Channel Parameters: @@ -744,7 +744,6 @@ def Initialize( # Initializes a FD capable PCAN Channel # def InitializeFD(self, Channel, BitrateFD): - """Initializes a FD capable PCAN Channel Parameters: @@ -775,7 +774,6 @@ def InitializeFD(self, Channel, BitrateFD): # Uninitializes one or all PCAN Channels initialized by CAN_Initialize # def Uninitialize(self, Channel): - """Uninitializes one or all PCAN Channels initialized by CAN_Initialize Remarks: @@ -797,7 +795,6 @@ def Uninitialize(self, Channel): # Resets the receive and transmit queues of the PCAN Channel # def Reset(self, Channel): - """Resets the receive and transmit queues of the PCAN Channel Remarks: @@ -819,7 +816,6 @@ def Reset(self, Channel): # Gets the current status of a PCAN Channel # def GetStatus(self, Channel): - """Gets the current status of a PCAN Channel Parameters: @@ -838,7 +834,6 @@ def GetStatus(self, Channel): # Reads a CAN message from the receive queue of a PCAN Channel # def Read(self, Channel): - """Reads a CAN message from the receive queue of a PCAN Channel Remarks: @@ -867,7 +862,6 @@ def Read(self, Channel): # Reads a CAN message from the receive queue of a FD capable PCAN Channel # def ReadFD(self, Channel): - """Reads a CAN message from the receive queue of a FD capable PCAN Channel Remarks: @@ -896,7 +890,6 @@ def ReadFD(self, Channel): # Transmits a CAN message # def Write(self, Channel, MessageBuffer): - """Transmits a CAN message Parameters: @@ -916,7 +909,6 @@ def Write(self, Channel, MessageBuffer): # Transmits a CAN message over a FD capable PCAN Channel # def WriteFD(self, Channel, MessageBuffer): - """Transmits a CAN message over a FD capable PCAN Channel Parameters: @@ -936,7 +928,6 @@ def WriteFD(self, Channel, MessageBuffer): # Configures the reception filter # def FilterMessages(self, Channel, FromID, ToID, Mode): - """Configures the reception filter Remarks: @@ -963,7 +954,6 @@ def FilterMessages(self, Channel, FromID, ToID, Mode): # Retrieves a PCAN Channel value # def GetValue(self, Channel, Parameter): - """Retrieves a PCAN Channel value Remarks: @@ -1026,7 +1016,6 @@ def GetValue(self, Channel, Parameter): # error code, in any desired language # def SetValue(self, Channel, Parameter, Buffer): - """Returns a descriptive text of a given TPCANStatus error code, in any desired language @@ -1069,7 +1058,6 @@ def SetValue(self, Channel, Parameter, Buffer): raise def GetErrorText(self, Error, Language=0): - """Configures or sets a PCAN Channel value Remarks: @@ -1098,7 +1086,6 @@ def GetErrorText(self, Error, Language=0): raise def LookUpChannel(self, Parameters): - """Finds a PCAN-Basic channel that matches with the given parameters Remarks: diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index c1507b4fa..d0df88fcd 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -175,7 +175,6 @@ def _recv_internal( try: rx_byte = self._ser.read() if rx_byte and ord(rx_byte) == 0xAA: - s = self._ser.read(4) timestamp = struct.unpack(" None: def _recv_internal( self, timeout: Optional[float] ) -> Tuple[Optional[Message], bool]: - canId = None remote = False extended = False diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 2ba1205b1..5c7bee3e8 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -239,7 +239,6 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: # configure the socket try: - # set hop limit / TTL ttl_as_binary = struct.pack("@I", self.hop_limit) if self.ip_version == 4: diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 504b61c7b..bca40f8d3 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -99,7 +99,6 @@ def __init__( serial: Optional[str] = None, **kwargs, ): - self.can = Usb2CanAbstractionLayer(dll) # get the serial number of the device @@ -134,7 +133,6 @@ def send(self, msg, timeout=None): raise CanOperationError("could not send message", error_code=status) def _recv_internal(self, timeout): - messagerx = CanalMsg() if timeout == 0: diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 25b7abfb0..ad8774147 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -74,7 +74,6 @@ def __init__( self._open = True with channels_lock: - # Create a new channel if one does not exist if self.channel_id not in channels: channels[self.channel_id] = [] diff --git a/can/io/asc.py b/can/io/asc.py index eb59c0471..a380f6b16 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -174,7 +174,6 @@ def _process_data_string( def _process_classic_can_frame( self, line: str, msg_kwargs: Dict[str, Any] ) -> Message: - # CAN error frame if line.strip()[0:10].lower() == "errorframe": # Error Frame @@ -423,7 +422,6 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: self.file.write(line) def on_message_received(self, msg: Message) -> None: - if msg.is_error_frame: self.log_event(f"{self.channel} ErrorFrame", msg.timestamp) return diff --git a/can/io/canutils.py b/can/io/canutils.py index c57a6ca97..17d7a193f 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -48,7 +48,6 @@ def __init__( def __iter__(self) -> Generator[Message, None, None]: for line in self.file: - # skip empty lines temp = line.strip() if not temp: diff --git a/can/io/csv.py b/can/io/csv.py index ecfc5de35..7570d4f30 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -49,7 +49,6 @@ def __iter__(self) -> Generator[Message, None, None]: return for line in self.file: - timestamp, arbitration_id, extended, remote, error, dlc, data = line.split( "," ) diff --git a/can/io/logger.py b/can/io/logger.py index b6ea23380..0477fa065 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -237,7 +237,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: elif isinstance(logger, Printer) and logger.file is not None: return cast(FileIOMessageWriter, logger) - raise Exception( + raise ValueError( f'The log format "{suffix}" ' f"is not supported by {self.__class__.__name__}. " f"{self.__class__.__name__} supports the following formats: " diff --git a/can/io/player.py b/can/io/player.py index 0e062ecb7..13a9ce60e 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -139,7 +139,6 @@ def __iter__(self) -> typing.Generator[Message, None, None]: t_skipped = 0.0 for message in self.raw_messages: - # Work out the correct wait time if self.timestamps: if recorded_start_time is None: diff --git a/can/logconvert.py b/can/logconvert.py index d89155758..7a34deb61 100644 --- a/can/logconvert.py +++ b/can/logconvert.py @@ -46,7 +46,6 @@ def main(): args = parser.parse_args() with LogReader(args.input) as reader: - if args.file_size: logger = SizedRotatingLogger( base_filename=args.output, max_bytes=args.file_size diff --git a/can/player.py b/can/player.py index c029981be..fab271824 100644 --- a/can/player.py +++ b/can/player.py @@ -87,7 +87,6 @@ def main() -> None: with _create_bus(results, **additional_config) as bus: with LogReader(results.infile, **additional_config) as reader: - in_sync = MessageSync( cast(Iterable[Message], reader), timestamps=results.timestamps, diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 6cf28fd99..6f16b8b4d 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -58,9 +58,7 @@ def __init__(self, *args, **kwargs): # now, BusABC.send_periodic() does not need a lock anymore, but the # implementation still requires a context manager - # pylint: disable=protected-access self.__wrapped__._lock_send_periodic = nullcontext() - # pylint: enable=protected-access # init locks for sending and receiving separately self._lock_send = RLock() diff --git a/examples/print_notifier.py b/examples/print_notifier.py index b6554ccd2..cb4a02799 100755 --- a/examples/print_notifier.py +++ b/examples/print_notifier.py @@ -5,7 +5,6 @@ def main(): - with can.Bus(receive_own_messages=True) as bus: print_listener = can.Printer() can.Notifier(bus, [print_listener]) diff --git a/examples/receive_all.py b/examples/receive_all.py index d8d8714fc..e9410e49f 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -14,7 +14,6 @@ def receive_all(): # this uses the default configuration (for example from environment variables, or a # config file) see https://python-can.readthedocs.io/en/stable/configuration.html with can.Bus() as bus: - # set to read-only, only supported on some interfaces try: bus.state = BusState.PASSIVE diff --git a/examples/send_one.py b/examples/send_one.py index 7e3fb8a4c..41b3a3cd0 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -13,7 +13,6 @@ def send_one(): # this uses the default configuration (for example from the config file) # see https://python-can.readthedocs.io/en/stable/configuration.html with can.Bus() as bus: - # Using specific buses works similar: # bus = can.Bus(interface='socketcan', channel='vcan0', bitrate=250000) # bus = can.Bus(interface='pcan', channel='PCAN_USBBUS1', bitrate=250000) diff --git a/examples/serial_com.py b/examples/serial_com.py index 76b95c3e7..538c8d12f 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -50,7 +50,6 @@ def main(): """Controls the sender and receiver.""" with can.Bus(interface="serial", channel="/dev/ttyS10") as server: with can.Bus(interface="serial", channel="/dev/ttyS11") as client: - tx_msg = can.Message( arbitration_id=0x01, data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], diff --git a/examples/simple_log_converter.py b/examples/simple_log_converter.py index e82669f54..20e8fba75 100755 --- a/examples/simple_log_converter.py +++ b/examples/simple_log_converter.py @@ -17,8 +17,7 @@ def main(): with can.LogReader(sys.argv[1]) as reader: with can.Logger(sys.argv[2]) as writer: - - for msg in reader: # pylint: disable=not-an-iterable + for msg in reader: writer.on_message_received(msg) diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index d48a9d8cb..f022759fa 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -12,7 +12,6 @@ def main(): """Send some messages to itself and apply filtering.""" with can.Bus(interface="virtual", receive_own_messages=True) as bus: - can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}] bus.set_filters(can_filters) diff --git a/requirements-lint.txt b/requirements-lint.txt index 28bcf2aa2..f1070e1b9 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,5 +1,5 @@ -pylint==2.15.9 -black~=22.10.0 -mypy==0.991 +pylint==2.16.4 +black~=23.1.0 +mypy==1.0.1 mypy-extensions==0.4.3 types-setuptools diff --git a/setup.py b/setup.py index 8cabc8c02..96cbc0c77 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,6 @@ Learn more at https://github.com/hardbyte/python-can/ """ -# pylint: disable=invalid-name - from os import listdir from os.path import isfile, join import re diff --git a/test/back2back_test.py b/test/back2back_test.py index 54d619878..ce09b6179 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -275,7 +275,6 @@ def test_sub_second_timestamp_resolution(self): @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class BasicTestSocketCan(Back2BackTestCase): - INTERFACE_1 = "socketcan" CHANNEL_1 = "vcan0" INTERFACE_2 = "socketcan" @@ -289,7 +288,6 @@ class BasicTestSocketCan(Back2BackTestCase): "only supported on Unix systems (but not on macOS at Travis CI and GitHub Actions)", ) class BasicTestUdpMulticastBusIPv4(Back2BackTestCase): - INTERFACE_1 = "udp_multicast" CHANNEL_1 = UdpMulticastBus.DEFAULT_GROUP_IPv4 INTERFACE_2 = "udp_multicast" @@ -329,7 +327,6 @@ def test_unique_message_instances(self): @unittest.skipUnless(TEST_INTERFACE_ETAS, "skip testing of etas interface") class BasicTestEtas(Back2BackTestCase): - if TEST_INTERFACE_ETAS: configs = can.interface.detect_available_configs(interfaces="etas") diff --git a/test/serial_test.py b/test/serial_test.py index aa6c71994..e1df96435 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -44,7 +44,6 @@ def reset(self): class SimpleSerialTestBase(ComparingMessagesTestCase): - MAX_TIMESTAMP = 0xFFFFFFFF / 1000 def __init__(self): diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 639694bfa..4454cbd27 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -33,7 +33,6 @@ def test_cycle_time(self): with can.interface.Bus(interface="virtual") as bus1: with can.interface.Bus(interface="virtual") as bus2: - # disabling the garbage collector makes the time readings more reliable gc.disable() diff --git a/test/test_pcan.py b/test/test_pcan.py index aa0988a48..93a5f5ff4 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -19,7 +19,6 @@ class TestPCANBus(unittest.TestCase): def setUp(self) -> None: - patcher = mock.patch("can.interfaces.pcan.pcan.PCANBasic", spec=True) self.MockPCANBasic = patcher.start() self.addCleanup(patcher.stop) @@ -62,7 +61,7 @@ def test_bus_creation_state_error(self) -> None: with self.assertRaises(ValueError): can.Bus(interface="pcan", state=BusState.ERROR) - @parameterized.expand([("f_clock", 8_000_000), ("f_clock_mhz", 8)]) + @parameterized.expand([("f_clock", 80_000_000), ("f_clock_mhz", 80)]) def test_bus_creation_fd(self, clock_param: str, clock_val: int) -> None: self.bus = can.Bus( interface="pcan", diff --git a/test/test_player.py b/test/test_player.py index c15bf82e2..9bdd484b8 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -15,7 +15,6 @@ class TestPlayerScriptModule(unittest.TestCase): - logfile = os.path.join(os.path.dirname(__file__), "data", "test_CanMessage.asc") def setUp(self) -> None: diff --git a/test/test_util.py b/test/test_util.py index b6c261602..e77401688 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -21,7 +21,6 @@ class RenameKwargsTest(unittest.TestCase): expected_kwargs = dict(a=1, b=2, c=3, d=4) def _test(self, start: str, end: str, kwargs, aliases): - # Test that we do get the DeprecationWarning when called with deprecated kwargs with self.assertWarnsRegex( DeprecationWarning, "is deprecated.*?" + start + ".*?" + end From fa5b133a40b7ee87386234249e18c82f1c789672 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 16 Mar 2023 11:50:48 +0100 Subject: [PATCH 1020/1235] improve ThreadBasedCyclicSendTask timing (#1539) Co-authored-by: zariiii9003 --- can/broadcastmanager.py | 53 +++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index cfa4c658d..d795eb1fa 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -5,30 +5,39 @@ :meth:`can.BusABC.send_periodic`. """ +import abc +import logging +import sys +import threading +import time from typing import Optional, Sequence, Tuple, Union, Callable, TYPE_CHECKING +from typing_extensions import Final + from can import typechecking +from can.message import Message if TYPE_CHECKING: from can.bus import BusABC -from can.message import Message - -import abc -import logging -import threading -import time # try to import win32event for event-based cyclic send task (needs the pywin32 package) +USE_WINDOWS_EVENTS = False try: import win32event - HAS_EVENTS = True + # Python 3.11 provides a more precise sleep implementation on Windows, so this is not necessary. + # Put version check here, so mypy does not complain about `win32event` not being defined. + if sys.version_info < (3, 11): + USE_WINDOWS_EVENTS = True except ImportError: - HAS_EVENTS = False + pass log = logging.getLogger("can.bcm") +NANOSECONDS_IN_SECOND: Final[int] = 1_000_000_000 +NANOSECONDS_IN_MILLISECOND: Final[int] = 1_000_000 + class CyclicTask(abc.ABC): """ @@ -64,6 +73,7 @@ def __init__( # Take the Arbitration ID of the first element self.arbitration_id = messages[0].arbitration_id self.period = period + self.period_ns = int(round(period * 1e9)) self.messages = messages @staticmethod @@ -246,7 +256,7 @@ def __init__( ) self.on_error = on_error - if HAS_EVENTS: + if USE_WINDOWS_EVENTS: self.period_ms = int(round(period * 1000, 0)) try: self.event = win32event.CreateWaitableTimerEx( @@ -261,7 +271,7 @@ def __init__( self.start() def stop(self) -> None: - if HAS_EVENTS: + if USE_WINDOWS_EVENTS: win32event.CancelWaitableTimer(self.event.handle) self.stopped = True @@ -272,7 +282,7 @@ def start(self) -> None: self.thread = threading.Thread(target=self._run, name=name) self.thread.daemon = True - if HAS_EVENTS: + if USE_WINDOWS_EVENTS: win32event.SetWaitableTimer( self.event.handle, 0, self.period_ms, None, None, False ) @@ -281,10 +291,11 @@ def start(self) -> None: def _run(self) -> None: msg_index = 0 + msg_due_time_ns = time.perf_counter_ns() + while not self.stopped: # Prevent calling bus.send from multiple threads with self.send_lock: - started = time.perf_counter() try: self.bus.send(self.messages[msg_index]) except Exception as exc: # pylint: disable=broad-except @@ -294,13 +305,19 @@ def _run(self) -> None: break else: break + msg_due_time_ns += self.period_ns if self.end_time is not None and time.perf_counter() >= self.end_time: break msg_index = (msg_index + 1) % len(self.messages) - 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)) + # Compensate for the time it takes to send the message + delay_ns = msg_due_time_ns - time.perf_counter_ns() + + if delay_ns > 0: + if USE_WINDOWS_EVENTS: + win32event.WaitForSingleObject( + self.event.handle, + int(round(delay_ns / NANOSECONDS_IN_MILLISECOND)), + ) + else: + time.sleep(delay_ns / NANOSECONDS_IN_SECOND) From 740c50c275ef7ec6d867cc7557b055c6eb12dc84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=BCller?= Date: Thu, 30 Mar 2023 18:19:07 +0200 Subject: [PATCH 1021/1235] Improve support for TRC files (#1530) * Improve support for TRC files * Add support for Version 2.0 * Add support for $STARTTIME in Version 1.1 and Version 2.1 * Add support for $COLUMNS in Version 2.1 * Add type annotations * Fix mypy findings * remove assert --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/io/trc.py | 114 +++++++++++++++++++++++++--------- test/data/test_CanMessage.trc | 2 +- test/logformats_test.py | 16 +++-- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index ec08d1af1..d1ee2b72d 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -7,15 +7,15 @@ Version 1.1 will be implemented as it is most commonly used """ # noqa -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from enum import Enum import io import os import logging -from typing import Generator, Optional, Union, TextIO, Callable, List +from typing import Generator, Optional, Union, TextIO, Callable, List, Dict from ..message import Message -from ..util import channel2int +from ..util import channel2int, len2dlc, dlc2len from .generic import FileIOMessageWriter, MessageReader from ..typechecking import StringPathLike @@ -32,6 +32,11 @@ class TRCFileVersion(Enum): V2_0 = 200 V2_1 = 201 + def __ge__(self, other): + if self.__class__ is other.__class__: + return self.value >= other.value + return NotImplemented + class TRCReader(MessageReader): """ @@ -51,6 +56,8 @@ def __init__( """ super().__init__(file, mode="r") self.file_version = TRCFileVersion.UNKNOWN + self.start_time: Optional[datetime] = None + self.columns: Dict[str, int] = {} if not self.file: raise ValueError("The given file cannot be None") @@ -67,17 +74,42 @@ def _extract_header(self): file_version = line.split("=")[1] if file_version == "1.1": self.file_version = TRCFileVersion.V1_1 + elif file_version == "2.0": + self.file_version = TRCFileVersion.V2_0 elif file_version == "2.1": self.file_version = TRCFileVersion.V2_1 else: self.file_version = TRCFileVersion.UNKNOWN except IndexError: logger.debug("TRCReader: Failed to parse version") + elif line.startswith(";$STARTTIME"): + logger.debug("TRCReader: Found start time '%s'", line) + try: + self.start_time = datetime( + 1899, 12, 30, tzinfo=timezone.utc + ) + timedelta(days=float(line.split("=")[1])) + except IndexError: + logger.debug("TRCReader: Failed to parse start time") + elif line.startswith(";$COLUMNS"): + logger.debug("TRCReader: Found columns '%s'", line) + try: + columns = line.split("=")[1].split(",") + self.columns = {column: columns.index(column) for column in columns} + except IndexError: + logger.debug("TRCReader: Failed to parse columns") elif line.startswith(";"): continue else: break + if self.file_version >= TRCFileVersion.V1_1: + if self.start_time is None: + raise ValueError("File has no start time information") + + if self.file_version >= TRCFileVersion.V2_0: + if not self.columns: + raise ValueError("File has no column information") + if self.file_version == TRCFileVersion.UNKNOWN: logger.info( "TRCReader: No file version was found, so version 1.0 is assumed" @@ -87,8 +119,8 @@ def _extract_header(self): self._parse_cols = self._parse_msg_V1_0 elif self.file_version == TRCFileVersion.V1_1: self._parse_cols = self._parse_cols_V1_1 - elif self.file_version == TRCFileVersion.V2_1: - self._parse_cols = self._parse_cols_V2_1 + elif self.file_version in [TRCFileVersion.V2_0, TRCFileVersion.V2_1]: + self._parse_cols = self._parse_cols_V2_x else: raise NotImplementedError("File version not fully implemented for reading") @@ -113,7 +145,12 @@ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]: arbit_id = cols[3] msg = Message() - msg.timestamp = float(cols[1]) / 1000 + if isinstance(self.start_time, datetime): + msg.timestamp = ( + self.start_time + timedelta(milliseconds=float(cols[1])) + ).timestamp() + else: + msg.timestamp = float(cols[1]) / 1000 msg.arbitration_id = int(arbit_id, 16) msg.is_extended_id = len(arbit_id) > 4 msg.channel = 1 @@ -122,15 +159,38 @@ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]: msg.is_rx = cols[2] == "Rx" return msg - def _parse_msg_V2_1(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_V2_x(self, cols: List[str]) -> Optional[Message]: + type_ = cols[self.columns["T"]] + bus = self.columns.get("B", None) + + if "l" in self.columns: + length = int(cols[self.columns["l"]]) + dlc = len2dlc(length) + elif "L" in self.columns: + dlc = int(cols[self.columns["L"]]) + length = dlc2len(dlc) + else: + raise ValueError("No length/dlc columns present.") + msg = Message() - msg.timestamp = float(cols[1]) / 1000 - msg.arbitration_id = int(cols[4], 16) - msg.is_extended_id = len(cols[4]) > 4 - msg.channel = int(cols[3]) - msg.dlc = int(cols[7]) - msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) - msg.is_rx = cols[5] == "Rx" + if isinstance(self.start_time, datetime): + msg.timestamp = ( + self.start_time + timedelta(milliseconds=float(cols[self.columns["O"]])) + ).timestamp() + else: + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(cols[self.columns["I"]], 16) + msg.is_extended_id = len(cols[self.columns["I"]]) > 4 + msg.channel = int(cols[bus]) if bus is not None else 1 + msg.dlc = dlc + msg.data = bytearray( + [int(cols[i + self.columns["D"]], 16) for i in range(length)] + ) + msg.is_rx = cols[self.columns["d"]] == "Rx" + msg.is_fd = type_ in ["FD", "FB", "FE", "BI"] + msg.bitrate_switch = type_ in ["FB", " FE"] + msg.error_state_indicator = type_ in ["FE", "BI"] + return msg def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]: @@ -141,10 +201,10 @@ def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_V2_1(self, cols: List[str]) -> Optional[Message]: - dtype = cols[2] - if dtype == "DT": - return self._parse_msg_V2_1(cols) + def _parse_cols_V2_x(self, cols: List[str]) -> Optional[Message]: + dtype = cols[self.columns["T"]] + if dtype in ["DT", "FD", "FB"]: + return self._parse_msg_V2_x(cols) else: logger.info("TRCReader: Unsupported type '%s'", dtype) return None @@ -228,7 +288,7 @@ def __init__( self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 self._format_message = self._format_message_init - def _write_header_V1_0(self, start_time: timedelta) -> None: + def _write_header_V1_0(self, start_time: datetime) -> None: lines = [ ";##########################################################################", f"; {self.filepath}", @@ -249,13 +309,11 @@ def _write_header_V1_0(self, start_time: timedelta) -> None: ] self.file.writelines(line + "\n" for line in lines) - def _write_header_V2_1(self, header_time: timedelta, start_time: datetime) -> None: - milliseconds = int( - (header_time.seconds * 1000) + (header_time.microseconds / 1000) - ) + def _write_header_V2_1(self, start_time: datetime) -> None: + header_time = start_time - datetime(year=1899, month=12, day=30) lines = [ ";$FILEVERSION=2.1", - f";$STARTTIME={header_time.days}.{milliseconds}", + f";$STARTTIME={header_time/timedelta(days=1)}", ";$COLUMNS=N,O,T,B,I,d,R,L,D", ";", f"; {self.filepath}", @@ -308,14 +366,12 @@ def _format_message_init(self, msg, channel): def write_header(self, timestamp: float) -> None: # write start of file header - ref_time = datetime(year=1899, month=12, day=30) - start_time = datetime.now() + timedelta(seconds=timestamp) - header_time = start_time - ref_time + start_time = datetime.utcfromtimestamp(timestamp) if self.file_version == TRCFileVersion.V1_0: - self._write_header_V1_0(header_time) + self._write_header_V1_0(start_time) elif self.file_version == TRCFileVersion.V2_1: - self._write_header_V2_1(header_time, start_time) + self._write_header_V2_1(start_time) else: raise NotImplementedError("File format is not supported") self.header_written = True diff --git a/test/data/test_CanMessage.trc b/test/data/test_CanMessage.trc index 215997b57..8b1361808 100644 --- a/test/data/test_CanMessage.trc +++ b/test/data/test_CanMessage.trc @@ -1,5 +1,5 @@ ;$FILEVERSION=2.1 -;$STARTTIME=0 +;$STARTTIME=43008.920986006946 ;$COLUMNS=N,O,T,B,I,d,R,L,D ; ; C:\Users\User\Desktop\python-can\test\data\test_CanMessage.trc diff --git a/test/logformats_test.py b/test/logformats_test.py index 05c8b986f..3486827a9 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -810,9 +810,10 @@ class TestTrcFileFormatGen(TestTrcFileFormatBase): """Generic tests for can.TRCWriter and can.TRCReader with different file versions""" def test_can_message(self): + start_time = 1506809173.191 # 30.09.2017 22:06:13.191.000 as timestamp expected_messages = [ can.Message( - timestamp=2.5010, + timestamp=start_time + 2.5010, arbitration_id=0xC8, is_extended_id=False, is_rx=False, @@ -821,7 +822,7 @@ def test_can_message(self): data=[9, 8, 7, 6, 5, 4, 3, 2], ), can.Message( - timestamp=17.876708, + timestamp=start_time + 17.876708, arbitration_id=0x6F9, is_extended_id=False, channel=0, @@ -841,10 +842,17 @@ def test_can_message(self): ) def test_can_message_versions(self, name, filename, is_rx_support): with self.subTest(name): + if name == "V1_0": + # Version 1.0 does not support start time + start_time = 0 + else: + start_time = ( + 1639837687.062001 # 18.12.2021 14:28:07.062.001 as timestamp + ) def msg_std(timestamp): msg = can.Message( - timestamp=timestamp, + timestamp=timestamp + start_time, arbitration_id=0x000, is_extended_id=False, channel=1, @@ -857,7 +865,7 @@ def msg_std(timestamp): def msg_ext(timestamp): msg = can.Message( - timestamp=timestamp, + timestamp=timestamp + start_time, arbitration_id=0x100, is_extended_id=True, channel=1, From 7855da1b4bb32f7bb3a1e733d59be40571fd4f8b Mon Sep 17 00:00:00 2001 From: Teejay Date: Thu, 30 Mar 2023 09:58:18 -0700 Subject: [PATCH 1022/1235] Add `__del__` method to `BusABC` (#1489) * Add del method * Add unittest * Satisfy black formatter * Satisfy pylint linter * PR feedback Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> * PR feedback Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> * Move to cls attribute * Add unittest * Call parent shutdown from socketcand * Wrap del in try except * Call parent shutdown from ixxat * Black & pylint * PR feedback Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> * Remove try/except & fix ordering * Fix unittest * Call parent shutdown from etas * Add warning filter * Make multicast_udp back2back test more specific * clean up test_interface_canalystii.py * carry over from #1519 * fix AttributeError --------- Co-authored-by: TJ Bruno Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/bus.py | 28 ++++++++++++++--- can/interfaces/canalystii.py | 21 +++++++------ can/interfaces/etas/__init__.py | 1 + can/interfaces/ixxat/canlib.py | 1 + can/interfaces/socketcand/socketcand.py | 2 +- test/back2back_test.py | 7 +++-- test/test_bus.py | 9 ++++++ test/test_interface.py | 42 +++++++++++++++++++++++++ test/test_interface_canalystii.py | 6 +--- 9 files changed, 94 insertions(+), 23 deletions(-) create mode 100644 test/test_interface.py diff --git a/can/bus.py b/can/bus.py index 292c754fb..32a26ebcb 100644 --- a/can/bus.py +++ b/can/bus.py @@ -1,7 +1,7 @@ """ Contains the ABC bus implementation and its documentation. """ - +import contextlib from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union import can.typechecking @@ -44,12 +44,14 @@ class BusABC(metaclass=ABCMeta): #: Log level for received messages RECV_LOGGING_LEVEL = 9 + _is_shutdown: bool = False + @abstractmethod def __init__( self, channel: Any, can_filters: Optional[can.typechecking.CanFilters] = None, - **kwargs: object + **kwargs: object, ): """Construct and open a CAN bus instance of the specified type. @@ -301,6 +303,10 @@ def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None: :param remove_tasks: Stop tracking the stopped tasks. """ + if not hasattr(self, "_periodic_tasks"): + # avoid AttributeError for partially initialized BusABC instance + return + for task in self._periodic_tasks: # we cannot let `task.stop()` modify `self._periodic_tasks` while we are # iterating over it (#634) @@ -415,9 +421,15 @@ def flush_tx_buffer(self) -> None: def shutdown(self) -> None: """ - Called to carry out any interface specific cleanup required - in shutting down a bus. + Called to carry out any interface specific cleanup required in shutting down a bus. + + This method can be safely called multiple times. """ + if self._is_shutdown: + LOG.debug("%s is already shut down", self.__class__) + return + + self._is_shutdown = True self.stop_all_periodic_tasks() def __enter__(self): @@ -426,6 +438,14 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.shutdown() + def __del__(self) -> None: + if not self._is_shutdown: + LOG.warning("%s was not properly shut down", self.__class__) + # We do some best-effort cleanup if the user + # forgot to properly close the bus instance + with contextlib.suppress(AttributeError): + self.shutdown() + @property def state(self) -> BusState: """ diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 7150a60bd..1e6a7fea4 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,11 +1,11 @@ -import collections +from collections import deque from ctypes import c_ubyte import logging import time from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union from can import BitTiming, BusABC, Message, BitTimingFd -from can.exceptions import CanTimeoutError, CanInitializationError +from can.exceptions import CanTimeoutError from can.typechecking import CanFilters from can.util import deprecated_args_alias, check_or_adjust_timing_clock @@ -50,11 +50,12 @@ def __init__( If set, software received message queue can only grow to this many messages (for all channels) before older messages are dropped """ - super().__init__(channel=channel, can_filters=can_filters, **kwargs) - if not (bitrate or timing): raise ValueError("Either bitrate or timing argument is required") + # Do this after the error handling + super().__init__(channel=channel, can_filters=can_filters, **kwargs) + if isinstance(channel, str): # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(",")] @@ -63,23 +64,23 @@ def __init__( else: # Sequence[int] self.channels = list(channel) - self.rx_queue = collections.deque( - maxlen=rx_queue_size - ) # type: Deque[Tuple[int, driver.Message]] + self.rx_queue: Deque[Tuple[int, driver.Message]] = deque(maxlen=rx_queue_size) self.channel_info = f"CANalyst-II: device {device}, channels {self.channels}" self.device = driver.CanalystDevice(device_index=device) - for channel in self.channels: + for single_channel in self.channels: if isinstance(timing, BitTiming): timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000]) - self.device.init(channel, timing0=timing.btr0, timing1=timing.btr1) + self.device.init( + single_channel, timing0=timing.btr0, timing1=timing.btr1 + ) elif isinstance(timing, BitTimingFd): raise NotImplementedError( f"CAN FD is not supported by {self.__class__.__name__}." ) else: - self.device.init(channel, bitrate=bitrate) + self.device.init(single_channel, bitrate=bitrate) # Delay to use between each poll for new messages # diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 3a203a50d..03dc42bc1 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -248,6 +248,7 @@ def flush_tx_buffer(self) -> None: OCI_ResetQueue(self.txQueue) def shutdown(self) -> None: + super().shutdown() # Cleanup TX if self.txQueue: OCI_DestroyCANTxQueue(self.txQueue) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index a20e4f59b..01f754115 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -147,6 +147,7 @@ def _send_periodic_internal(self, msgs, period, duration=None): return self.bus._send_periodic_internal(msgs, period, duration) def shutdown(self) -> None: + super().shutdown() self.bus.shutdown() @property diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 32b9a0edf..3f4e2ac86 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -183,5 +183,5 @@ def send(self, msg, timeout=None): self._tcp_send(ascii_msg) def shutdown(self): - self.stop_all_periodic_tasks() + super().shutdown() self.__socket.close() diff --git a/test/back2back_test.py b/test/back2back_test.py index ce09b6179..48c98bf59 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -12,6 +12,7 @@ import pytest import can +from can import CanInterfaceNotImplementedError from can.interfaces.udp_multicast import UdpMulticastBus from .config import ( @@ -294,7 +295,7 @@ class BasicTestUdpMulticastBusIPv4(Back2BackTestCase): CHANNEL_2 = UdpMulticastBus.DEFAULT_GROUP_IPv4 def test_unique_message_instances(self): - with self.assertRaises(NotImplementedError): + with self.assertRaises(CanInterfaceNotImplementedError): super().test_unique_message_instances() @@ -313,7 +314,7 @@ class BasicTestUdpMulticastBusIPv6(Back2BackTestCase): CHANNEL_2 = HOST_LOCAL_MCAST_GROUP_IPv6 def test_unique_message_instances(self): - with self.assertRaises(NotImplementedError): + with self.assertRaises(CanInterfaceNotImplementedError): super().test_unique_message_instances() @@ -321,7 +322,7 @@ def test_unique_message_instances(self): try: bus_class = can.interface._get_class_for_interface("etas") TEST_INTERFACE_ETAS = True -except can.exceptions.CanInterfaceNotImplementedError: +except CanInterfaceNotImplementedError: pass diff --git a/test/test_bus.py b/test/test_bus.py index e11d829d3..24421b2fd 100644 --- a/test/test_bus.py +++ b/test/test_bus.py @@ -1,3 +1,4 @@ +import gc from unittest.mock import patch import can @@ -12,3 +13,11 @@ def test_bus_ignore_config(): _ = can.Bus(interface="virtual") assert can.util.load_config.called + + +@patch.object(can.bus.BusABC, "shutdown") +def test_bus_attempts_self_cleanup(mock_shutdown): + bus = can.Bus(interface="virtual") + del bus + gc.collect() + mock_shutdown.assert_called() diff --git a/test/test_interface.py b/test/test_interface.py new file mode 100644 index 000000000..271e90b1b --- /dev/null +++ b/test/test_interface.py @@ -0,0 +1,42 @@ +import importlib +from unittest.mock import patch + +import pytest + +import can +from can.interfaces import BACKENDS + + +@pytest.fixture(params=(BACKENDS.keys())) +def constructor(request): + mod, cls = BACKENDS[request.param] + + try: + module = importlib.import_module(mod) + constructor = getattr(module, cls) + except: + pytest.skip("Unable to load interface") + + return constructor + + +@pytest.fixture +def interface(constructor): + class MockInterface(constructor): + def __init__(self): + pass + + def __del__(self): + pass + + return MockInterface() + + +@patch.object(can.bus.BusABC, "shutdown") +def test_interface_calls_parent_shutdown(mock_shutdown, interface): + try: + interface.shutdown() + except: + pass + finally: + mock_shutdown.assert_called() diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py index 4d1d3eb84..4f3033e10 100755 --- a/test/test_interface_canalystii.py +++ b/test/test_interface_canalystii.py @@ -1,11 +1,7 @@ #!/usr/bin/env python -""" -""" - -import time import unittest -from unittest.mock import Mock, patch, call +from unittest.mock import patch, call from ctypes import c_ubyte import canalystii as driver # low-level driver module, mock out this layer From 1188c57a43be6bfa48490c4d2e5f3ea472a5e637 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 1 Apr 2023 23:08:15 +0200 Subject: [PATCH 1023/1235] Export symbols to satisfy type checkers (#1551) * use ruff with isort, export symbols according to PEP484 * fix CI * fix CI * use __all__ --- .github/workflows/ci.yml | 3 + .pylintrc | 2 + can/__init__.py | 115 +++++++++++++----- can/bit_timing.py | 4 +- can/broadcastmanager.py | 2 +- can/bus.py | 15 ++- can/ctypesutil.py | 1 - can/exceptions.py | 4 +- can/interface.py | 4 +- can/interfaces/__init__.py | 2 +- can/interfaces/canalystii.py | 14 +-- can/interfaces/cantact.py | 9 +- can/interfaces/etas/__init__.py | 4 +- can/interfaces/gs_usb.py | 10 +- can/interfaces/ics_neovi/__init__.py | 12 +- can/interfaces/ics_neovi/neovi_bus.py | 9 +- can/interfaces/iscan.py | 7 +- can/interfaces/ixxat/__init__.py | 11 +- can/interfaces/ixxat/canlib.py | 5 +- can/interfaces/ixxat/canlib_vcinpl.py | 9 +- can/interfaces/ixxat/canlib_vcinpl2.py | 10 +- can/interfaces/kvaser/canlib.py | 10 +- can/interfaces/neousys/__init__.py | 2 + can/interfaces/neousys/neousys.py | 15 ++- can/interfaces/nican.py | 8 +- can/interfaces/nixnet.py | 6 +- can/interfaces/pcan/__init__.py | 5 + can/interfaces/pcan/basic.py | 5 +- can/interfaces/pcan/pcan.py | 86 +++++++------ can/interfaces/robotell.py | 3 +- can/interfaces/seeedstudio/__init__.py | 2 + can/interfaces/seeedstudio/seeedstudio.py | 2 +- can/interfaces/serial/__init__.py | 4 +- can/interfaces/serial/serial_can.py | 7 +- can/interfaces/slcan.py | 12 +- can/interfaces/socketcan/__init__.py | 8 +- can/interfaces/socketcan/socketcan.py | 17 ++- can/interfaces/socketcan/utils.py | 2 +- can/interfaces/socketcand/__init__.py | 2 + can/interfaces/socketcand/socketcand.py | 7 +- can/interfaces/systec/__init__.py | 2 + can/interfaces/systec/constants.py | 4 +- can/interfaces/systec/exceptions.py | 4 +- can/interfaces/systec/structures.py | 16 ++- can/interfaces/systec/ucan.py | 4 +- can/interfaces/systec/ucanbus.py | 4 +- can/interfaces/udp_multicast/__init__.py | 2 + can/interfaces/udp_multicast/bus.py | 3 +- can/interfaces/udp_multicast/utils.py | 7 +- can/interfaces/usb2can/__init__.py | 7 +- can/interfaces/usb2can/usb2canInterface.py | 11 +- .../usb2can/usb2canabstractionlayer.py | 3 +- can/interfaces/vector/__init__.py | 20 ++- can/interfaces/vector/canlib.py | 30 ++--- can/interfaces/vector/xldefine.py | 1 - can/interfaces/vector/xldriver.py | 3 +- can/interfaces/virtual.py | 9 +- can/io/__init__.py | 34 +++++- can/io/asc.py | 12 +- can/io/blf.py | 11 +- can/io/canutils.py | 5 +- can/io/csv.py | 7 +- can/io/generic.py | 38 +++--- can/io/logger.py | 15 ++- can/io/player.py | 6 +- can/io/printer.py | 5 +- can/io/sqlite.py | 9 +- can/io/trc.py | 13 +- can/listener.py | 6 +- can/logconvert.py | 4 +- can/logger.py | 7 +- can/message.py | 5 +- can/notifier.py | 2 +- can/player.py | 6 +- can/thread_safe_bus.py | 1 - can/util.py | 12 +- can/viewer.py | 9 +- doc/conf.py | 4 +- pyproject.toml | 13 ++ requirements-lint.txt | 1 + setup.py | 7 +- test/back2back_test.py | 10 +- test/contextmanager_test.py | 1 + test/listener_test.py | 8 +- test/logformats_test.py | 17 +-- test/network_test.py | 6 +- test/notifier_test.py | 4 +- test/serial_test.py | 3 +- test/simplecyclic_test.py | 4 +- test/test_cyclic_socketcan.py | 2 +- test/test_interface_canalystii.py | 6 +- test/test_interface_ixxat.py | 1 + test/test_interface_ixxat_fd.py | 1 + test/test_kvaser.py | 3 +- test/test_logger.py | 6 +- test/test_message_class.py | 13 +- test/test_message_filtering.py | 1 - test/test_message_sync.py | 13 +- test/test_neousys.py | 12 +- test/test_neovi.py | 1 + test/test_player.py | 7 +- test/test_robotell.py | 1 + test/test_rotating_loggers.py | 1 + test/test_scripts.py | 4 +- test/test_slcan.py | 3 +- test/test_socketcan.py | 7 +- test/test_socketcan_helpers.py | 6 +- test/test_util.py | 4 +- test/test_vector.py | 14 ++- test/test_viewer.py | 3 +- test/zero_dlc_test.py | 3 +- 111 files changed, 567 insertions(+), 415 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f96578e5..465d5959b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,9 @@ jobs: - name: mypy 3.11 run: | mypy --python-version 3.11 . + - name: ruff + run: | + ruff check can - name: pylint run: | pylint --rcfile=.pylintrc \ diff --git a/.pylintrc b/.pylintrc index bdbf47613..de2e82a0d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -438,6 +438,8 @@ known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant +# Allow explicit reexports by alias from a package __init__ +allow-reexport-from-package=no [CLASSES] diff --git a/can/__init__.py b/can/__init__.py index d9ff5ffcd..5cc054c19 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -6,47 +6,104 @@ """ import logging -from typing import Dict, Any +from typing import Any, Dict __version__ = "4.1.0" +__all__ = [ + "ASCReader", + "ASCWriter", + "AsyncBufferedReader", + "BitTiming", + "BitTimingFd", + "BLFReader", + "BLFWriter", + "broadcastmanager", + "BufferedReader", + "Bus", + "BusABC", + "BusState", + "CanError", + "CanInitializationError", + "CanInterfaceNotImplementedError", + "CanOperationError", + "CanTimeoutError", + "CanutilsLogReader", + "CanutilsLogWriter", + "CSVReader", + "CSVWriter", + "CyclicSendTaskABC", + "detect_available_configs", + "interface", + "LimitedDurationCyclicSendTaskABC", + "Listener", + "Logger", + "LogReader", + "ModifiableCyclicTaskABC", + "Message", + "MessageSync", + "Notifier", + "Printer", + "RedirectReader", + "RestartableCyclicTaskABC", + "set_logging_level", + "SizedRotatingLogger", + "SqliteReader", + "SqliteWriter", + "ThreadSafeBus", + "typechecking", + "TRCFileVersion", + "TRCReader", + "TRCWriter", + "util", + "VALID_INTERFACES", +] log = logging.getLogger("can") rc: Dict[str, Any] = {} -from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader - +from . import typechecking # isort:skip +from . import util # isort:skip +from . import broadcastmanager, interface +from .bit_timing import BitTiming, BitTimingFd +from .broadcastmanager import ( + CyclicSendTaskABC, + LimitedDurationCyclicSendTaskABC, + ModifiableCyclicTaskABC, + RestartableCyclicTaskABC, +) +from .bus import BusABC, BusState from .exceptions import ( CanError, - CanInterfaceNotImplementedError, CanInitializationError, + CanInterfaceNotImplementedError, CanOperationError, CanTimeoutError, ) - -from .util import set_logging_level - -from .message import Message -from .bus import BusABC, BusState -from .thread_safe_bus import ThreadSafeBus -from .notifier import Notifier -from .interfaces import VALID_INTERFACES -from . import interface from .interface import Bus, detect_available_configs -from .bit_timing import BitTiming, BitTimingFd - -from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync -from .io import ASCWriter, ASCReader -from .io import BLFReader, BLFWriter -from .io import CanutilsLogReader, CanutilsLogWriter -from .io import CSVWriter, CSVReader -from .io import SqliteWriter, SqliteReader -from .io import TRCReader, TRCWriter, TRCFileVersion - -from .broadcastmanager import ( - CyclicSendTaskABC, - LimitedDurationCyclicSendTaskABC, - ModifiableCyclicTaskABC, - MultiRateCyclicSendTaskABC, - RestartableCyclicTaskABC, +from .interfaces import VALID_INTERFACES +from .io import ( + ASCReader, + ASCWriter, + BLFReader, + BLFWriter, + CanutilsLogReader, + CanutilsLogWriter, + CSVReader, + CSVWriter, + Logger, + LogReader, + MessageSync, + Printer, + SizedRotatingLogger, + SqliteReader, + SqliteWriter, + TRCFileVersion, + TRCReader, + TRCWriter, ) +from .listener import AsyncBufferedReader, BufferedReader, Listener, RedirectReader +from .message import Message +from .notifier import Notifier +from .thread_safe_bus import ThreadSafeBus +from .util import set_logging_level diff --git a/can/bit_timing.py b/can/bit_timing.py index 4fada145b..bf76c08af 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -1,8 +1,8 @@ # pylint: disable=too-many-lines import math -from typing import List, Mapping, Iterator, cast +from typing import Iterator, List, Mapping, cast -from can.typechecking import BitTimingFdDict, BitTimingDict +from can.typechecking import BitTimingDict, BitTimingFdDict class BitTiming(Mapping): diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index d795eb1fa..07d93e296 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -10,7 +10,7 @@ import sys import threading import time -from typing import Optional, Sequence, Tuple, Union, Callable, TYPE_CHECKING +from typing import TYPE_CHECKING, Callable, Optional, Sequence, Tuple, Union from typing_extensions import Final diff --git a/can/bus.py b/can/bus.py index 32a26ebcb..3964215d3 100644 --- a/can/bus.py +++ b/can/bus.py @@ -1,19 +1,18 @@ """ Contains the ABC bus implementation and its documentation. """ -import contextlib -from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union - -import can.typechecking -from abc import ABC, ABCMeta, abstractmethod -import can +import contextlib import logging import threading -from time import time +from abc import ABC, ABCMeta, abstractmethod from enum import Enum, auto +from time import time +from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union, cast -from can.broadcastmanager import ThreadBasedCyclicSendTask, CyclicSendTaskABC +import can +import can.typechecking +from can.broadcastmanager import CyclicSendTaskABC, ThreadBasedCyclicSendTask from can.message import Message LOG = logging.getLogger(__name__) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 4cfebb5b8..e624db6d2 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -5,7 +5,6 @@ import ctypes import logging import sys - from typing import Any, Callable, Optional, Tuple, Union log = logging.getLogger("can.ctypesutil") diff --git a/can/exceptions.py b/can/exceptions.py index 57130082a..e1c970e27 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -17,9 +17,7 @@ import sys from contextlib import contextmanager - -from typing import Optional -from typing import Type +from typing import Optional, Type if sys.version_info >= (3, 9): from collections.abc import Generator diff --git a/can/interface.py b/can/interface.py index 47b44fed1..4c59dab8b 100644 --- a/can/interface.py +++ b/can/interface.py @@ -6,12 +6,12 @@ import importlib import logging -from typing import Any, cast, Iterable, Type, Optional, Union, List +from typing import Any, Iterable, List, Optional, Type, Union, cast from . import util from .bus import BusABC -from .interfaces import BACKENDS from .exceptions import CanInterfaceNotImplementedError +from .interfaces import BACKENDS from .typechecking import AutoDetectedConfig, Channel log = logging.getLogger("can.interface") diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index c9ca6ca55..5089dbf47 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -3,7 +3,7 @@ """ import sys -from typing import cast, Dict, Tuple +from typing import Dict, Tuple, cast # interface_name => (module, classname) BACKENDS: Dict[str, Tuple[str, str]] = { diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 1e6a7fea4..dd65f5ba0 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,15 +1,15 @@ -from collections import deque -from ctypes import c_ubyte import logging import time -from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union +from collections import deque +from ctypes import c_ubyte +from typing import Any, Deque, Dict, Optional, Sequence, Tuple, Union -from can import BitTiming, BusABC, Message, BitTimingFd +import canalystii as driver + +from can import BitTiming, BitTimingFd, BusABC, Message from can.exceptions import CanTimeoutError from can.typechecking import CanFilters -from can.util import deprecated_args_alias, check_or_adjust_timing_clock - -import canalystii as driver +from can.util import check_or_adjust_timing_clock, deprecated_args_alias logger = logging.getLogger(__name__) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 20e4d0cb7..75e1adbaa 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -2,18 +2,19 @@ Interface for CANtact devices from Linklayer Labs """ -import time import logging -from typing import Optional, Union, Any +import time +from typing import Any, Optional, Union from unittest.mock import Mock -from can import BusABC, Message, BitTiming, BitTimingFd +from can import BitTiming, BitTimingFd, BusABC, Message + from ..exceptions import ( CanInitializationError, CanInterfaceNotImplementedError, error_check, ) -from ..util import deprecated_args_alias, check_or_adjust_timing_clock +from ..util import check_or_adjust_timing_clock, deprecated_args_alias logger = logging.getLogger(__name__) diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 03dc42bc1..5f768b3e5 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -3,7 +3,8 @@ from typing import Dict, List, Optional, Tuple import can -from ...exceptions import CanInitializationError +from can.exceptions import CanInitializationError + from .boa import * @@ -290,6 +291,7 @@ def state(self, new_state: can.BusState) -> None: # raise CanOperationError(f"OCI_AdaptCANConfiguration failed with error 0x{ec:X}") raise NotImplementedError("Setting state is not implemented.") + @staticmethod def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX) tree = ctypes.POINTER(CSI_Tree)() diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 185d28acf..fb5ce1d80 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -1,15 +1,15 @@ +import logging from typing import Optional, Tuple +import usb +from gs_usb.constants import CAN_EFF_FLAG, CAN_ERR_FLAG, CAN_MAX_DLC, CAN_RTR_FLAG from gs_usb.gs_usb import GsUsb -from gs_usb.gs_usb_frame import GsUsbFrame, GS_USB_NONE_ECHO_ID -from gs_usb.constants import CAN_ERR_FLAG, CAN_RTR_FLAG, CAN_EFF_FLAG, CAN_MAX_DLC +from gs_usb.gs_usb_frame import GS_USB_NONE_ECHO_ID, GsUsbFrame + import can -import usb -import logging from ..exceptions import CanInitializationError, CanOperationError - logger = logging.getLogger(__name__) diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py index 548e3ea3f..e221e0840 100644 --- a/can/interfaces/ics_neovi/__init__.py +++ b/can/interfaces/ics_neovi/__init__.py @@ -1,7 +1,11 @@ """ """ -from .neovi_bus import NeoViBus -from .neovi_bus import ICSApiError -from .neovi_bus import ICSInitializationError -from .neovi_bus import ICSOperationError +__all__ = [ + "ICSApiError", + "ICSInitializationError", + "ICSOperationError", + "NeoViBus", +] + +from .neovi_bus import ICSApiError, ICSInitializationError, ICSOperationError, NeoViBus diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index c3abc9f67..848cefcc8 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -11,17 +11,18 @@ import logging import os import tempfile -from collections import deque, defaultdict, Counter +from collections import Counter, defaultdict, deque from itertools import cycle from threading import Event from warnings import warn -from can import Message, BusABC +from can import BusABC, Message + from ...exceptions import ( CanError, - CanTimeoutError, - CanOperationError, CanInitializationError, + CanOperationError, + CanTimeoutError, ) logger = logging.getLogger(__name__) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index a27494b61..ef3a48215 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -3,16 +3,17 @@ """ import ctypes -import time import logging +import time from typing import Optional, Tuple, Union -from can import BusABC, Message from can import ( + BusABC, CanError, - CanInterfaceNotImplementedError, CanInitializationError, + CanInterfaceNotImplementedError, CanOperationError, + Message, ) logger = logging.getLogger(__name__) diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index 1419d97a1..bc871b372 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -4,7 +4,12 @@ Copyright (C) 2016-2021 Giuseppe Corbelli """ +__all__ = [ + "get_ixxat_hwids", + "IXXATBus", +] + from can.interfaces.ixxat.canlib import IXXATBus -from can.interfaces.ixxat.canlib_vcinpl import ( - get_ixxat_hwids, -) # import this and not the one from vcinpl2 for backward compatibility + +# import this and not the one from vcinpl2 for backward compatibility +from can.interfaces.ixxat.canlib_vcinpl import get_ixxat_hwids diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 01f754115..3db719f96 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,11 +1,10 @@ +from typing import Optional + import can.interfaces.ixxat.canlib_vcinpl as vcinpl import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 - from can import BusABC, Message from can.bus import BusState -from typing import Optional - class IXXATBus(BusABC): """The CAN Bus implemented for the IXXAT interface. diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 8304a6dd7..550484f3e 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -13,16 +13,17 @@ import functools import logging import sys -from typing import Optional, Callable, Tuple +from typing import Callable, Optional, Tuple from can import BusABC, Message -from can.bus import BusState -from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC, ) -from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT +from can.bus import BusState +from can.ctypesutil import HANDLE, PHANDLE, CLibrary +from can.ctypesutil import HRESULT as ctypes_HRESULT +from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError from can.util import deprecated_args_alias from . import constants, structures diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index b8ed916dc..3e7f2ff91 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -13,17 +13,17 @@ import functools import logging import sys -from typing import Optional, Callable, Tuple +from typing import Callable, Optional, Tuple +import can.util from can import BusABC, Message -from can.exceptions import CanInterfaceNotImplementedError, CanInitializationError from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC, ) -from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT - -import can.util +from can.ctypesutil import HANDLE, PHANDLE, CLibrary +from can.ctypesutil import HRESULT as ctypes_HRESULT +from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError from can.util import deprecated_args_alias from . import constants, structures diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 2bbf8f0bf..f6b92ccef 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -6,15 +6,15 @@ Copyright (C) 2010 Dynamic Controls """ +import ctypes +import logging import sys import time -import logging -import ctypes -from can import BusABC -from ...exceptions import CanError, CanInitializationError, CanOperationError -from can import Message +from can import BusABC, Message from can.util import time_perfcounter_correlation + +from ...exceptions import CanError, CanInitializationError, CanOperationError from . import constants as canstat from . import structures diff --git a/can/interfaces/neousys/__init__.py b/can/interfaces/neousys/__init__.py index 3aa87332c..6bd503f35 100644 --- a/can/interfaces/neousys/__init__.py +++ b/can/interfaces/neousys/__init__.py @@ -1,3 +1,5 @@ """ Neousys CAN bus driver """ +__all__ = ["NeousysBus"] + from can.interfaces.neousys.neousys import NeousysBus diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index 57f947aa4..d57234ddd 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -14,21 +14,20 @@ # pylint: disable=too-many-instance-attributes # pylint: disable=wrong-import-position -import queue import logging import platform -from time import time - +import queue from ctypes import ( - byref, CFUNCTYPE, + POINTER, + Structure, + byref, c_ubyte, c_uint, c_ushort, - POINTER, sizeof, - Structure, ) +from time import time try: from ctypes import WinDLL @@ -36,13 +35,13 @@ from ctypes import CDLL from can import BusABC, Message + from ...exceptions import ( CanInitializationError, - CanOperationError, CanInterfaceNotImplementedError, + CanOperationError, ) - logger = logging.getLogger(__name__) diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index ea13e28e8..8457a1a38 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -16,17 +16,17 @@ import ctypes import logging import sys +from typing import Optional, Tuple, Type -from can import BusABC, Message import can.typechecking +from can import BusABC, Message + from ..exceptions import ( CanError, + CanInitializationError, CanInterfaceNotImplementedError, CanOperationError, - CanInitializationError, ) -from typing import Optional, Tuple, Type - logger = logging.getLogger(__name__) diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index 6c3e63697..4ddb52455 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -13,14 +13,14 @@ import time from queue import SimpleQueue from types import ModuleType -from typing import Optional, List, Union, Tuple, Any +from typing import Any, List, Optional, Tuple, Union import can.typechecking -from can import BusABC, Message, BitTiming, BitTimingFd +from can import BitTiming, BitTimingFd, BusABC, Message from can.exceptions import ( CanInitializationError, - CanOperationError, CanInterfaceNotImplementedError, + CanOperationError, ) from can.util import check_or_adjust_timing_clock, deprecated_args_alias diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py index 0f28b0ffe..b46ccb050 100644 --- a/can/interfaces/pcan/__init__.py +++ b/can/interfaces/pcan/__init__.py @@ -1,4 +1,9 @@ """ """ +__all__ = [ + "PcanBus", + "PcanError", +] + from can.interfaces.pcan.pcan import PcanBus, PcanError diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index d1b2121cb..a60b96079 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -15,11 +15,10 @@ # more Info at http://www.peak-system.com # Module Imports +import logging +import platform from ctypes import * from ctypes.util import find_library -import platform - -import logging PLATFORM = platform.system() IS_WINDOWS = PLATFORM == "Windows" diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 2ed0ff445..61d19d1b4 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -2,74 +2,74 @@ Enable basic CAN over a PCAN USB device. """ import logging +import platform import time from datetime import datetime -import platform -from typing import Optional, List, Tuple, Union, Any +from typing import Any, List, Optional, Tuple, Union from packaging import version from can import ( - BusABC, - BusState, BitTiming, BitTimingFd, - Message, + BusABC, + BusState, CanError, - CanOperationError, CanInitializationError, + CanOperationError, + Message, ) from can.util import check_or_adjust_timing_clock, dlc2len, len2dlc + from .basic import ( - PCAN_BITRATES, - PCAN_FD_PARAMETER_LIST, - PCAN_CHANNEL_NAMES, - PCAN_NONEBUS, - PCAN_BAUD_500K, - PCAN_TYPE_ISA, - PCANBasic, - PCAN_ERROR_OK, + FEATURE_FD_CAPABLE, + IS_LINUX, + IS_WINDOWS, PCAN_ALLOW_ERROR_FRAMES, - PCAN_PARAMETER_ON, - PCAN_RECEIVE_EVENT, PCAN_API_VERSION, + PCAN_ATTACHED_CHANNELS, + PCAN_BAUD_500K, + PCAN_BITRATES, + PCAN_BUSOFF_AUTORESET, + PCAN_CHANNEL_AVAILABLE, + PCAN_CHANNEL_CONDITION, + PCAN_CHANNEL_FEATURES, + PCAN_CHANNEL_IDENTIFYING, + PCAN_CHANNEL_NAMES, PCAN_DEVICE_NUMBER, - PCAN_ERROR_QRCVEMPTY, - PCAN_ERROR_BUSLIGHT, + PCAN_DICT_STATUS, PCAN_ERROR_BUSHEAVY, - PCAN_MESSAGE_EXTENDED, - PCAN_MESSAGE_RTR, - PCAN_MESSAGE_FD, + PCAN_ERROR_BUSLIGHT, + PCAN_ERROR_OK, + PCAN_ERROR_QRCVEMPTY, + PCAN_FD_PARAMETER_LIST, + PCAN_LANBUS1, + PCAN_LISTEN_ONLY, PCAN_MESSAGE_BRS, - PCAN_MESSAGE_ESI, PCAN_MESSAGE_ERRFRAME, + PCAN_MESSAGE_ESI, + PCAN_MESSAGE_EXTENDED, + PCAN_MESSAGE_FD, + PCAN_MESSAGE_RTR, PCAN_MESSAGE_STANDARD, - TPCANMsgFD, - TPCANMsg, - PCAN_CHANNEL_IDENTIFYING, - PCAN_LISTEN_ONLY, + PCAN_NONEBUS, PCAN_PARAMETER_OFF, - TPCANHandle, - IS_LINUX, - IS_WINDOWS, + PCAN_PARAMETER_ON, + PCAN_PCCBUS1, PCAN_PCIBUS1, + PCAN_RECEIVE_EVENT, + PCAN_TYPE_ISA, PCAN_USBBUS1, - PCAN_PCCBUS1, - PCAN_LANBUS1, - PCAN_CHANNEL_CONDITION, - PCAN_CHANNEL_AVAILABLE, - PCAN_CHANNEL_FEATURES, - FEATURE_FD_CAPABLE, - PCAN_DICT_STATUS, - PCAN_BUSOFF_AUTORESET, + VALID_PCAN_CAN_CLOCKS, + VALID_PCAN_FD_CLOCKS, + PCANBasic, TPCANBaudrate, - PCAN_ATTACHED_CHANNELS, TPCANChannelInformation, - VALID_PCAN_FD_CLOCKS, - VALID_PCAN_CAN_CLOCKS, + TPCANHandle, + TPCANMsg, + TPCANMsgFD, ) - # Set up logging log = logging.getLogger("can.pcan") @@ -96,7 +96,7 @@ try: # Try builtin Python 3 Windows API from _overlapped import CreateEvent - from _winapi import WaitForSingleObject, WAIT_OBJECT_0, INFINITE + from _winapi import INFINITE, WAIT_OBJECT_0, WaitForSingleObject HAS_EVENTS = True except ImportError: @@ -104,8 +104,6 @@ elif IS_LINUX: try: - import errno - import os import select HAS_EVENTS = True diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 4d82c1922..4d038a38a 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -3,11 +3,12 @@ """ import io -import time import logging +import time from typing import Optional from can import BusABC, Message + from ..exceptions import CanInterfaceNotImplementedError, CanOperationError logger = logging.getLogger(__name__) diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py index cb1c17f1d..2fc348a17 100644 --- a/can/interfaces/seeedstudio/__init__.py +++ b/can/interfaces/seeedstudio/__init__.py @@ -1,4 +1,6 @@ """ """ +__all__ = ["SeeedBus"] + from can.interfaces.seeedstudio.seeedstudio import SeeedBus diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 4d09ca0cd..7d7a1e687 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -6,9 +6,9 @@ SKU 114991193 """ +import io import logging import struct -import io from time import time import can diff --git a/can/interfaces/serial/__init__.py b/can/interfaces/serial/__init__.py index bd6a45b9c..1b1d63c49 100644 --- a/can/interfaces/serial/__init__.py +++ b/can/interfaces/serial/__init__.py @@ -1,4 +1,6 @@ """ """ -from can.interfaces.serial.serial_can import SerialBus as Bus +__all__ = ["SerialBus"] + +from can.interfaces.serial.serial_can import SerialBus diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index d0df88fcd..eb336feba 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -10,14 +10,15 @@ import io import logging import struct -from typing import Any, List, Tuple, Optional +from typing import Any, List, Optional, Tuple -from can import BusABC, Message from can import ( - CanInterfaceNotImplementedError, + BusABC, CanInitializationError, + CanInterfaceNotImplementedError, CanOperationError, CanTimeoutError, + Message, ) from can.typechecking import AutoDetectedConfig diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index eed67a5f8..21306f28f 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -2,21 +2,19 @@ Interface for slcan compatible interfaces (win32/linux). """ -from typing import Any, Optional, Tuple - import io -import time import logging +import time +from typing import Any, Optional, Tuple + +from can import BusABC, Message, typechecking -from can import BusABC, Message from ..exceptions import ( - CanInterfaceNotImplementedError, CanInitializationError, + CanInterfaceNotImplementedError, CanOperationError, error_check, ) -from can import typechecking - logger = logging.getLogger(__name__) diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index e08c18f50..0fbdede58 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -2,4 +2,10 @@ See: https://www.kernel.org/doc/Documentation/networking/can.txt """ -from .socketcan import SocketcanBus, CyclicSendTask, MultiRateCyclicSendTask +__all__ = [ + "CyclicSendTask", + "MultiRateCyclicSendTask", + "SocketcanBus", +] + +from .socketcan import CyclicSendTask, MultiRateCyclicSendTask, SocketcanBus diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 74fbe8197..bdf39f0ab 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -5,17 +5,16 @@ At the end of the file the usage of the internal methods is shown. """ -from typing import Dict, List, Optional, Sequence, Tuple, Type, Union - -import logging import ctypes import ctypes.util +import errno +import logging import select import socket import struct -import time import threading -import errno +import time +from typing import Dict, List, Optional, Sequence, Tuple, Type, Union log = logging.getLogger(__name__) log_tx = log.getChild("tx") @@ -31,15 +30,15 @@ import can -from can import Message, BusABC +from can import BusABC, Message from can.broadcastmanager import ( + LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC, - LimitedDurationCyclicSendTaskABC, ) -from can.typechecking import CanFilters from can.interfaces.socketcan import constants -from can.interfaces.socketcan.utils import pack_filters, find_available_interfaces +from can.interfaces.socketcan.utils import find_available_interfaces, pack_filters +from can.typechecking import CanFilters # Setup BCM struct diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 7a8538135..8b2114692 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -8,7 +8,7 @@ import os import struct import subprocess -from typing import cast, Optional, List +from typing import List, Optional, cast from can import typechecking from can.interfaces.socketcan.constants import CAN_EFF_FLAG diff --git a/can/interfaces/socketcand/__init__.py b/can/interfaces/socketcand/__init__.py index 442c06d8b..e6b106918 100644 --- a/can/interfaces/socketcand/__init__.py +++ b/can/interfaces/socketcand/__init__.py @@ -6,4 +6,6 @@ http://www.domologic.de """ +__all__ = ["SocketCanDaemonBus"] + from .socketcand import SocketCanDaemonBus diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 3f4e2ac86..0c6c06ccf 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -7,14 +7,15 @@ Copyright (C) 2021 DOMOLOGIC GmbH http://www.domologic.de """ -import can -import socket -import select import logging +import select +import socket import time import traceback from collections import deque +import can + log = logging.getLogger(__name__) diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py index be7004b6a..9a97b3054 100644 --- a/can/interfaces/systec/__init__.py +++ b/can/interfaces/systec/__init__.py @@ -1 +1,3 @@ +__all__ = ["UcanBus"] + from can.interfaces.systec.ucanbus import UcanBus diff --git a/can/interfaces/systec/constants.py b/can/interfaces/systec/constants.py index 96952c17e..8caf9eab4 100644 --- a/can/interfaces/systec/constants.py +++ b/can/interfaces/systec/constants.py @@ -1,4 +1,6 @@ -from ctypes import c_ubyte as BYTE, c_ushort as WORD, c_ulong as DWORD +from ctypes import c_ubyte as BYTE +from ctypes import c_ulong as DWORD +from ctypes import c_ushort as WORD #: Maximum number of modules that are supported. MAX_MODULES = 64 diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index 733326194..9f7d4e2e5 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -1,9 +1,9 @@ +from abc import ABC, abstractmethod from typing import Dict -from abc import ABC, abstractmethod +from can import CanError from .constants import ReturnCode -from can import CanError class UcanException(CanError, ABC): diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index 841474b80..699763989 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -1,12 +1,20 @@ -from ctypes import Structure, POINTER, sizeof +import os +from ctypes import POINTER, Structure, sizeof +from ctypes import ( + c_long as BOOL, +) from ctypes import ( c_ubyte as BYTE, - c_ushort as WORD, +) +from ctypes import ( c_ulong as DWORD, - c_long as BOOL, +) +from ctypes import ( + c_ushort as WORD, +) +from ctypes import ( c_void_p as LPVOID, ) -import os # Workaround for Unix based platforms to be able to load structures for testing, etc... if os.name == "nt": diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index a6de4e9f5..bbc484314 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -1,14 +1,12 @@ import logging import sys - from ctypes import byref from ctypes import c_wchar_p as LPWSTR from ...exceptions import CanInterfaceNotImplementedError - from .constants import * -from .structures import * from .exceptions import * +from .structures import * log = logging.getLogger("can.systec") diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 7d8b6133a..da05b38b1 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -2,11 +2,11 @@ from threading import Event from can import BusABC, BusState, Message -from ...exceptions import CanError, CanInitializationError, CanOperationError +from ...exceptions import CanError, CanInitializationError, CanOperationError from .constants import * -from .structures import * from .exceptions import UcanException +from .structures import * from .ucan import UcanServer log = logging.getLogger("can.systec") diff --git a/can/interfaces/udp_multicast/__init__.py b/can/interfaces/udp_multicast/__init__.py index 0ce1ce389..6e11a02c5 100644 --- a/can/interfaces/udp_multicast/__init__.py +++ b/can/interfaces/udp_multicast/__init__.py @@ -1,3 +1,5 @@ """A module to allow CAN over UDP on IPv4/IPv6 multicast.""" +__all__ = ["UdpMulticastBus"] + from .bus import UdpMulticastBus diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 5c7bee3e8..00cbd32c8 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -17,8 +17,7 @@ from can import BusABC from can.typechecking import AutoDetectedConfig -from .utils import pack_message, unpack_message, check_msgpack_installed - +from .utils import check_msgpack_installed, pack_message, unpack_message # see socket.getaddrinfo() IPv4_ADDRESS_INFO = Tuple[str, int] # address, port diff --git a/can/interfaces/udp_multicast/utils.py b/can/interfaces/udp_multicast/utils.py index 2658bf0a9..35a0df185 100644 --- a/can/interfaces/udp_multicast/utils.py +++ b/can/interfaces/udp_multicast/utils.py @@ -2,12 +2,9 @@ Defines common functions. """ -from typing import Any -from typing import Dict -from typing import Optional +from typing import Any, Dict, Optional -from can import Message -from can import CanInterfaceNotImplementedError +from can import CanInterfaceNotImplementedError, Message from can.typechecking import ReadableBytesLike try: diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index 4ccff1cb0..17f5583f3 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -1,5 +1,10 @@ """ """ -from .usb2canInterface import Usb2canBus +__all__ = [ + "Usb2CanAbstractionLayer", + "Usb2canBus", +] + from .usb2canabstractionlayer import Usb2CanAbstractionLayer +from .usb2canInterface import Usb2canBus diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index bca40f8d3..2c0a0d00f 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -6,14 +6,17 @@ from ctypes import byref from typing import Optional -from can import BusABC, Message, CanInitializationError, CanOperationError -from .usb2canabstractionlayer import Usb2CanAbstractionLayer, CanalMsg, CanalError +from can import BusABC, CanInitializationError, CanOperationError, Message + +from .serial_selector import find_serial_devices from .usb2canabstractionlayer import ( IS_ERROR_FRAME, - IS_REMOTE_FRAME, IS_ID_TYPE, + IS_REMOTE_FRAME, + CanalError, + CanalMsg, + Usb2CanAbstractionLayer, ) -from .serial_selector import find_serial_devices # Set up logging log = logging.getLogger("can.usb2can") diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index a6708cb42..a894c3953 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -3,11 +3,12 @@ Socket CAN is recommended under Unix/Linux systems. """ +import logging from ctypes import * from enum import IntEnum -import logging import can + from ...exceptions import error_check from ...typechecking import StringPathLike diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index c5eae7140..e0c34d88f 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,12 +1,24 @@ """ """ +__all__ = [ + "get_channel_configs", + "VectorBus", + "VectorBusParams", + "VectorCanFdParams", + "VectorCanParams", + "VectorChannelConfig", + "VectorError", + "VectorInitializationError", + "VectorOperationError", +] + from .canlib import ( VectorBus, - get_channel_configs, - VectorChannelConfig, VectorBusParams, - VectorCanParams, VectorCanFdParams, + VectorCanParams, + VectorChannelConfig, + get_channel_configs, ) -from .exceptions import VectorError, VectorOperationError, VectorInitializationError +from .exceptions import VectorError, VectorInitializationError, VectorOperationError diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index d53b1418d..e60d20b0d 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -8,19 +8,19 @@ # ============================== import ctypes import logging -import time import os +import time from types import ModuleType from typing import ( + Any, + Callable, + Dict, List, NamedTuple, Optional, - Tuple, Sequence, + Tuple, Union, - Any, - Dict, - Callable, cast, ) @@ -28,7 +28,7 @@ INFINITE: Optional[int] try: # Try builtin Python 3 Windows API - from _winapi import WaitForSingleObject, INFINITE # type: ignore + from _winapi import INFINITE, WaitForSingleObject # type: ignore HAS_EVENTS = True except ImportError: @@ -38,21 +38,21 @@ # Import Modules # ============== from can import ( - BusABC, - Message, - CanInterfaceNotImplementedError, - CanInitializationError, BitTiming, BitTimingFd, + BusABC, + CanInitializationError, + CanInterfaceNotImplementedError, + Message, ) +from can.typechecking import AutoDetectedConfig, CanFilters from can.util import ( - len2dlc, - dlc2len, + check_or_adjust_timing_clock, deprecated_args_alias, + dlc2len, + len2dlc, time_perfcounter_correlation, - check_or_adjust_timing_clock, ) -from can.typechecking import AutoDetectedConfig, CanFilters # Define Module Logger # ==================== @@ -60,8 +60,8 @@ # Import Vector API modules # ========================= +from . import xlclass, xldefine from .exceptions import VectorError, VectorInitializationError, VectorOperationError -from . import xldefine, xlclass # Import safely Vector API module for Travis tests xldriver: Optional[ModuleType] = None diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index e2fd288b9..ebc0971c1 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -6,7 +6,6 @@ # ============================== from enum import IntEnum, IntFlag - MAX_MSG_LEN = 8 XL_CAN_MAX_DATA_LEN = 64 XL_INVALID_PORTHANDLE = -1 diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 29791e32f..f84b3cf1d 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -10,7 +10,8 @@ import ctypes import logging import platform -from .exceptions import VectorOperationError, VectorInitializationError + +from .exceptions import VectorInitializationError, VectorOperationError # Define Module Logger # ==================== diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index ad8774147..3eaefc230 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -6,14 +6,13 @@ and reside in the same process will receive the same messages. """ -from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING - -from copy import deepcopy import logging -import time import queue -from threading import RLock +import time +from copy import deepcopy from random import randint +from threading import RLock +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple from can import CanOperationError from can.bus import BusABC diff --git a/can/io/__init__.py b/can/io/__init__.py index 6dc9ac1af..12cebd52c 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -3,15 +3,39 @@ and Writers based off the file extension. """ +__all__ = [ + "ASCReader", + "ASCWriter", + "BaseRotatingLogger", + "BLFReader", + "BLFWriter", + "CanutilsLogReader", + "CanutilsLogWriter", + "CSVReader", + "CSVWriter", + "Logger", + "LogReader", + "MessageSync", + "Printer", + "SizedRotatingLogger", + "SqliteReader", + "SqliteWriter", + "TRCFileVersion", + "TRCReader", + "TRCWriter", +] + # Generic -from .logger import Logger, BaseRotatingLogger, SizedRotatingLogger +from .logger import BaseRotatingLogger, Logger, SizedRotatingLogger from .player import LogReader, MessageSync +# isort: split + # Format specific -from .asc import ASCWriter, ASCReader +from .asc import ASCReader, ASCWriter from .blf import BLFReader, BLFWriter from .canutils import CanutilsLogReader, CanutilsLogWriter -from .csv import CSVWriter, CSVReader -from .sqlite import SqliteReader, SqliteWriter +from .csv import CSVReader, CSVWriter from .printer import Printer -from .trc import TRCReader, TRCWriter, TRCFileVersion +from .sqlite import SqliteReader, SqliteWriter +from .trc import TRCFileVersion, TRCReader, TRCWriter diff --git a/can/io/asc.py b/can/io/asc.py index a380f6b16..b8054ecfc 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -5,18 +5,16 @@ - https://bitbucket.org/tobylorenz/vector_asc/src/master/src/Vector/ASC/tests/unittests/data/ - under `test/data/logfile.asc` """ +import logging import re -from typing import Any, Generator, List, Optional, Dict, Union, TextIO - -from datetime import datetime import time -import logging +from datetime import datetime +from typing import Any, Dict, Generator, List, Optional, TextIO, Union from ..message import Message -from ..util import channel2int, len2dlc, dlc2len -from .generic import FileIOMessageWriter, MessageReader from ..typechecking import StringPathLike - +from ..util import channel2int, dlc2len, len2dlc +from .generic import FileIOMessageWriter, MessageReader CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF diff --git a/can/io/blf.py b/can/io/blf.py index 8d5ade8c8..e9dd8380f 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -12,19 +12,18 @@ objects types. """ -import struct -import zlib import datetime -import time import logging -from typing import List, BinaryIO, Generator, Union, Tuple, Optional, cast, Any +import struct +import time +import zlib +from typing import Any, BinaryIO, Generator, List, Optional, Tuple, Union, cast from ..message import Message -from ..util import len2dlc, dlc2len, channel2int from ..typechecking import StringPathLike +from ..util import channel2int, dlc2len, len2dlc from .generic import FileIOMessageWriter, MessageReader - TSystemTime = Tuple[int, int, int, int, int, int, int, int] diff --git a/can/io/canutils.py b/can/io/canutils.py index 17d7a193f..a9dced6a1 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -5,11 +5,12 @@ """ import logging -from typing import Generator, TextIO, Union, Any +from typing import Any, Generator, TextIO, Union from can.message import Message -from .generic import FileIOMessageWriter, MessageReader + from ..typechecking import StringPathLike +from .generic import FileIOMessageWriter, MessageReader log = logging.getLogger("can.io.canutils") diff --git a/can/io/csv.py b/can/io/csv.py index 7570d4f30..b96e69342 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -9,12 +9,13 @@ of a CSV file. """ -from base64 import b64encode, b64decode -from typing import TextIO, Generator, Union, Any +from base64 import b64decode, b64encode +from typing import Any, Generator, TextIO, Union from can.message import Message -from .generic import FileIOMessageWriter, MessageReader + from ..typechecking import StringPathLike +from .generic import FileIOMessageWriter, MessageReader class CSVReader(MessageReader): diff --git a/can/io/generic.py b/can/io/generic.py index 77bba4501..193ec3df2 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,19 +1,21 @@ """Contains generic base classes for file IO.""" import locale from abc import ABCMeta +from types import TracebackType from typing import ( - Optional, - cast, + Any, + ContextManager, Iterable, + Optional, Type, - ContextManager, - Any, + cast, ) + from typing_extensions import Literal -from types import TracebackType -import can -import can.typechecking +from .. import typechecking +from ..listener import Listener +from ..message import Message class BaseIOHandler(ContextManager, metaclass=ABCMeta): @@ -26,11 +28,11 @@ class BaseIOHandler(ContextManager, metaclass=ABCMeta): was opened """ - file: Optional[can.typechecking.FileLike] + file: Optional[typechecking.FileLike] def __init__( self, - file: Optional[can.typechecking.AcceptedIOType], + file: Optional[typechecking.AcceptedIOType], mode: str = "rt", **kwargs: Any, ) -> None: @@ -42,7 +44,7 @@ def __init__( """ if file is None or (hasattr(file, "read") and hasattr(file, "write")): # file is None or some file-like object - self.file = cast(Optional[can.typechecking.FileLike], file) + self.file = cast(Optional[typechecking.FileLike], file) else: encoding: Optional[str] = ( None @@ -52,10 +54,8 @@ def __init__( # pylint: disable=consider-using-with # file is some path-like object self.file = cast( - can.typechecking.FileLike, - open( - cast(can.typechecking.StringPathLike, file), mode, encoding=encoding - ), + typechecking.FileLike, + open(cast(typechecking.StringPathLike, file), mode, encoding=encoding), ) # for multiple inheritance @@ -80,19 +80,19 @@ def stop(self) -> None: self.file.close() -class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): +class MessageWriter(BaseIOHandler, Listener, metaclass=ABCMeta): """The base class for all writers.""" - file: Optional[can.typechecking.FileLike] + file: Optional[typechecking.FileLike] class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): """A specialized base class for all writers with file descriptors.""" - file: can.typechecking.FileLike + file: typechecking.FileLike def __init__( - self, file: can.typechecking.AcceptedIOType, mode: str = "wt", **kwargs: Any + self, file: typechecking.AcceptedIOType, mode: str = "wt", **kwargs: Any ) -> None: # Not possible with the type signature, but be verbose for user-friendliness if file is None: @@ -105,5 +105,5 @@ def file_size(self) -> int: return self.file.tell() -class MessageReader(BaseIOHandler, Iterable[can.Message], metaclass=ABCMeta): +class MessageReader(BaseIOHandler, Iterable[Message], metaclass=ABCMeta): """The base class for all readers.""" diff --git a/can/io/logger.py b/can/io/logger.py index 0477fa065..de538e866 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -2,29 +2,28 @@ See the :class:`Logger` class. """ +import gzip import os import pathlib from abc import ABC, abstractmethod from datetime import datetime -import gzip -from typing import Any, Optional, Callable, Type, Tuple, cast, Dict, Set - from types import TracebackType +from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, cast -from typing_extensions import Literal from pkg_resources import iter_entry_points +from typing_extensions import Literal -from ..message import Message from ..listener import Listener -from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter +from ..message import Message +from ..typechecking import AcceptedIOType, FileLike, StringPathLike from .asc import ASCWriter from .blf import BLFWriter from .canutils import CanutilsLogWriter from .csv import CSVWriter -from .sqlite import SqliteWriter +from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter from .printer import Printer +from .sqlite import SqliteWriter from .trc import TRCWriter -from ..typechecking import StringPathLike, FileLike, AcceptedIOType class Logger(MessageWriter): diff --git a/can/io/player.py b/can/io/player.py index 13a9ce60e..022ed503b 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -10,15 +10,15 @@ from pkg_resources import iter_entry_points -from .generic import MessageReader +from ..message import Message +from ..typechecking import AcceptedIOType, FileLike, StringPathLike from .asc import ASCReader from .blf import BLFReader from .canutils import CanutilsLogReader from .csv import CSVReader +from .generic import MessageReader from .sqlite import SqliteReader from .trc import TRCReader -from ..typechecking import StringPathLike, FileLike, AcceptedIOType -from ..message import Message class LogReader(MessageReader): diff --git a/can/io/printer.py b/can/io/printer.py index 01da12e84..d0df71db8 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -3,12 +3,11 @@ """ import logging - -from typing import Optional, TextIO, Union, Any, cast +from typing import Any, Optional, TextIO, Union, cast from ..message import Message -from .generic import MessageWriter from ..typechecking import StringPathLike +from .generic import MessageWriter log = logging.getLogger("can.io.printer") diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 33f5d293f..43fd761e9 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -4,16 +4,17 @@ .. note:: The database schema is given in the documentation of the loggers. """ -import time -import threading import logging import sqlite3 -from typing import Generator, Any +import threading +import time +from typing import Any, Generator from can.listener import BufferedReader from can.message import Message -from .generic import MessageWriter, MessageReader + from ..typechecking import StringPathLike +from .generic import MessageReader, MessageWriter log = logging.getLogger("can.io.sqlite") diff --git a/can/io/trc.py b/can/io/trc.py index d1ee2b72d..75c81b502 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -7,18 +7,17 @@ Version 1.1 will be implemented as it is most commonly used """ # noqa -from datetime import datetime, timedelta, timezone -from enum import Enum import io -import os import logging -from typing import Generator, Optional, Union, TextIO, Callable, List, Dict +import os +from datetime import datetime, timedelta, timezone +from enum import Enum +from typing import Callable, Dict, Generator, List, Optional, TextIO, Union from ..message import Message -from ..util import channel2int, len2dlc, dlc2len -from .generic import FileIOMessageWriter, MessageReader from ..typechecking import StringPathLike - +from ..util import channel2int, dlc2len, len2dlc +from .generic import FileIOMessageWriter, MessageReader logger = logging.getLogger("can.io.trc") diff --git a/can/listener.py b/can/listener.py index e68d813d1..d6f252d17 100644 --- a/can/listener.py +++ b/can/listener.py @@ -2,15 +2,15 @@ This module contains the implementation of `can.Listener` and some readers. """ +import asyncio import sys import warnings -import asyncio from abc import ABCMeta, abstractmethod -from queue import SimpleQueue, Empty +from queue import Empty, SimpleQueue from typing import Any, AsyncIterator, Optional -from can.message import Message from can.bus import BusABC +from can.message import Message class Listener(metaclass=ABCMeta): diff --git a/can/logconvert.py b/can/logconvert.py index 7a34deb61..49cdaf4bb 100644 --- a/can/logconvert.py +++ b/can/logconvert.py @@ -2,11 +2,11 @@ Convert a log file from one format to another. """ -import sys import argparse import errno +import sys -from can import LogReader, Logger, SizedRotatingLogger +from can import Logger, LogReader, SizedRotatingLogger class ArgumentParser(argparse.ArgumentParser): diff --git a/can/logger.py b/can/logger.py index 9448fe6b4..42312324a 100644 --- a/can/logger.py +++ b/can/logger.py @@ -1,14 +1,15 @@ +import argparse +import errno import re import sys -import argparse from datetime import datetime -import errno -from typing import Any, Dict, List, Union, Sequence, Tuple +from typing import Any, Dict, List, Sequence, Tuple, Union import can from can.io import BaseRotatingLogger from can.io.generic import MessageWriter from can.util import cast_from_string + from . import Bus, BusState, Logger, SizedRotatingLogger from .typechecking import CanFilter, CanFilters diff --git a/can/message.py b/can/message.py index 48933b2da..05700ef72 100644 --- a/can/message.py +++ b/can/message.py @@ -6,13 +6,12 @@ starting with Python 3.7. """ +from copy import deepcopy +from math import isinf, isnan from typing import Optional from . import typechecking -from copy import deepcopy -from math import isinf, isnan - class Message: # pylint: disable=too-many-instance-attributes; OK for a dataclass """ diff --git a/can/notifier.py b/can/notifier.py index 2adae431e..fce210f49 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -6,7 +6,7 @@ import logging import threading import time -from typing import Callable, Iterable, List, Optional, Union, Awaitable +from typing import Awaitable, Callable, Iterable, List, Optional, Union from can.bus import BusABC from can.listener import Listener diff --git a/can/player.py b/can/player.py index fab271824..40e4cc43a 100644 --- a/can/player.py +++ b/can/player.py @@ -5,11 +5,11 @@ Similar to canplayer in the can-utils package. """ -import sys import argparse -from datetime import datetime import errno -from typing import cast, Iterable +import sys +from datetime import datetime +from typing import Iterable, cast from can import LogReader, Message, MessageSync diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 6f16b8b4d..4793ed1ff 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -12,7 +12,6 @@ from .interface import Bus - try: from contextlib import nullcontext diff --git a/can/util.py b/can/util.py index 42d99272e..2f6fb1957 100644 --- a/can/util.py +++ b/can/util.py @@ -11,24 +11,24 @@ import re import warnings from configparser import ConfigParser -from time import time, perf_counter, get_clock_info +from time import get_clock_info, perf_counter, time from typing import ( Any, Callable, - cast, Dict, Iterable, - Tuple, Optional, - Union, + Tuple, TypeVar, + Union, + cast, ) import can + from . import typechecking from .bit_timing import BitTiming, BitTimingFd -from .exceptions import CanInitializationError -from .exceptions import CanInterfaceNotImplementedError +from .exceptions import CanInitializationError, CanInterfaceNotImplementedError from .interfaces import VALID_INTERFACES log = logging.getLogger("can.util") diff --git a/can/viewer.py b/can/viewer.py index 5539ee3fb..be7f76b73 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -30,20 +30,21 @@ from typing import Dict, List, Tuple, Union from can import __version__ + from .logger import ( - _create_bus, - _parse_filters, _append_filter_argument, _create_base_argument_parser, + _create_bus, _parse_additional_config, + _parse_filters, ) - logger = logging.getLogger("can.viewer") try: import curses - from curses.ascii import ESC as KEY_ESC, SP as KEY_SPACE + from curses.ascii import ESC as KEY_ESC + from curses.ascii import SP as KEY_SPACE except ImportError: # Probably on Windows while windows-curses is not installed (e.g. in PyPy) logger.warning( diff --git a/doc/conf.py b/doc/conf.py index cea93440d..aa61f243b 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -6,9 +6,9 @@ # -- Imports ------------------------------------------------------------------- -import sys -import os import ctypes +import os +import sys from unittest.mock import MagicMock # If extensions (or modules to document with autodoc) are in another directory, diff --git a/pyproject.toml b/pyproject.toml index 17d87f033..af952e51e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,3 +4,16 @@ requires = [ "wheel", ] build-backend = "setuptools.build_meta" + +[tool.ruff] +select = [ + "F401", # unused-imports + "UP", # pyupgrade + "I", # isort +] + +# Assume Python 3.7. +target-version = "py37" + +[tool.ruff.isort] +known-first-party = ["can"] diff --git a/requirements-lint.txt b/requirements-lint.txt index f1070e1b9..829fe5663 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,4 +1,5 @@ pylint==2.16.4 +ruff==0.0.260 black~=23.1.0 mypy==1.0.1 mypy-extensions==0.4.3 diff --git a/setup.py b/setup.py index 96cbc0c77..b6dfab6f2 100644 --- a/setup.py +++ b/setup.py @@ -5,11 +5,12 @@ Learn more at https://github.com/hardbyte/python-can/ """ +import logging +import re from os import listdir from os.path import isfile, join -import re -import logging -from setuptools import setup, find_packages + +from setuptools import find_packages, setup logging.basicConfig(level=logging.WARNING) diff --git a/test/back2back_test.py b/test/back2back_test.py index 48c98bf59..52bfaf716 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -4,10 +4,10 @@ This module tests two buses attached to each other. """ +import random import unittest -from time import sleep, time from multiprocessing.dummy import Pool as ThreadPool -import random +from time import sleep, time import pytest @@ -17,12 +17,12 @@ from .config import ( IS_CI, - IS_UNIX, IS_OSX, + IS_PYPY, IS_TRAVIS, - TEST_INTERFACE_SOCKETCAN, + IS_UNIX, TEST_CAN_FD, - IS_PYPY, + TEST_INTERFACE_SOCKETCAN, ) diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py index 014dfb121..3adb1e7c6 100644 --- a/test/contextmanager_test.py +++ b/test/contextmanager_test.py @@ -5,6 +5,7 @@ """ import unittest + import can diff --git a/test/listener_test.py b/test/listener_test.py index 9b2e9e93b..b530afa60 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -3,13 +3,13 @@ """ """ import asyncio -import unittest -import random import logging -import tempfile import os +import random +import tempfile +import unittest import warnings -from os.path import join, dirname +from os.path import dirname, join import can diff --git a/test/logformats_test.py b/test/logformats_test.py index 3486827a9..d3b0eb015 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -12,23 +12,24 @@ TODO: correctly set preserves_channel and adds_default_channel """ import logging -import unittest -from parameterized import parameterized -import tempfile import os -from abc import abstractmethod, ABCMeta -from itertools import zip_longest +import tempfile +import unittest +from abc import ABCMeta, abstractmethod from datetime import datetime +from itertools import zip_longest + +from parameterized import parameterized import can from can.io import blf from .data.example_data import ( + TEST_COMMENTS, TEST_MESSAGES_BASE, - TEST_MESSAGES_REMOTE_FRAMES, - TEST_MESSAGES_ERROR_FRAMES, TEST_MESSAGES_CAN_FD, - TEST_COMMENTS, + TEST_MESSAGES_ERROR_FRAMES, + TEST_MESSAGES_REMOTE_FRAMES, sort_messages, ) from .message_helper import ComparingMessagesTestCase diff --git a/test/network_test.py b/test/network_test.py index 58c305a38..61690c1b4 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -1,10 +1,10 @@ #!/usr/bin/env python -import unittest -import threading -import random import logging +import random +import threading +import unittest logging.getLogger(__file__).setLevel(logging.WARNING) diff --git a/test/notifier_test.py b/test/notifier_test.py index ca2093f55..6982130cf 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -import unittest -import time import asyncio +import time +import unittest import can diff --git a/test/serial_test.py b/test/serial_test.py index e1df96435..d020d7232 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -12,9 +12,8 @@ import can from can.interfaces.serial.serial_can import SerialBus -from .message_helper import ComparingMessagesTestCase from .config import IS_PYPY - +from .message_helper import ComparingMessagesTestCase # Mentioned in #1010 TIMEOUT = 0.5 if IS_PYPY else 0.1 # 0.1 is the default set in SerialBus diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 4454cbd27..9e01be457 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -4,10 +4,10 @@ This module tests cyclic send tasks. """ -from time import sleep +import gc import unittest +from time import sleep from unittest.mock import MagicMock -import gc import can diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index 30c86d6a5..f19ce95b9 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -3,9 +3,9 @@ """ This module tests multiple message cyclic send tasks. """ +import time import unittest -import time import can from .config import TEST_INTERFACE_SOCKETCAN diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py index 4f3033e10..0a87f40f9 100755 --- a/test/test_interface_canalystii.py +++ b/test/test_interface_canalystii.py @@ -1,10 +1,14 @@ #!/usr/bin/env python +""" +""" + import unittest -from unittest.mock import patch, call from ctypes import c_ubyte +from unittest.mock import call, patch import canalystii as driver # low-level driver module, mock out this layer + import can from can.interfaces.canalystii import CANalystIIBus diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 484a88b58..2ff016d97 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -8,6 +8,7 @@ """ import unittest + import can diff --git a/test/test_interface_ixxat_fd.py b/test/test_interface_ixxat_fd.py index 80060a7ed..7274498aa 100644 --- a/test/test_interface_ixxat_fd.py +++ b/test/test_interface_ixxat_fd.py @@ -8,6 +8,7 @@ """ import unittest + import can diff --git a/test/test_kvaser.py b/test/test_kvaser.py index fda8b8316..6e7ccea38 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -10,8 +10,7 @@ import pytest import can -from can.interfaces.kvaser import canlib -from can.interfaces.kvaser import constants +from can.interfaces.kvaser import canlib, constants class KvaserTest(unittest.TestCase): diff --git a/test/test_logger.py b/test/test_logger.py index bb0015a89..083e4d19c 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -4,12 +4,12 @@ This module tests the functions inside of logger.py """ -import unittest -from unittest import mock -from unittest.mock import Mock import gzip import os import sys +import unittest +from unittest import mock +from unittest.mock import Mock import pytest diff --git a/test/test_message_class.py b/test/test_message_class.py index 4840402ff..8e2367034 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -1,22 +1,21 @@ #!/usr/bin/env python -import unittest +import pickle import sys -from math import isinf, isnan +import unittest from copy import copy, deepcopy -import pickle from datetime import timedelta +from math import isinf, isnan -from hypothesis import HealthCheck, given, settings import hypothesis.errors import hypothesis.strategies as st +import pytest +from hypothesis import HealthCheck, given, settings from can import Message +from .config import IS_GITHUB_ACTIONS, IS_PYPY, IS_WINDOWS from .message_helper import ComparingMessagesTestCase -from .config import IS_GITHUB_ACTIONS, IS_WINDOWS, IS_PYPY - -import pytest class TestMessageClass(unittest.TestCase): diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index e6fe16d46..a73e07aa2 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -10,7 +10,6 @@ from .data.example_data import TEST_ALL_MESSAGES - EXAMPLE_MSG = Message(arbitration_id=0x123, is_extended_id=True) HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, is_extended_id=True) diff --git a/test/test_message_sync.py b/test/test_message_sync.py index 7552915e7..90cbe372c 100644 --- a/test/test_message_sync.py +++ b/test/test_message_sync.py @@ -4,19 +4,18 @@ This module tests :class:`can.MessageSync`. """ -from copy import copy -import time import gc - +import time import unittest +from copy import copy + import pytest -from can import MessageSync, Message +from can import Message, MessageSync -from .config import IS_CI, IS_TRAVIS, IS_OSX, IS_GITHUB_ACTIONS, IS_LINUX -from .message_helper import ComparingMessagesTestCase +from .config import IS_CI, IS_GITHUB_ACTIONS, IS_LINUX, IS_OSX, IS_TRAVIS from .data.example_data import TEST_MESSAGES_BASE - +from .message_helper import ComparingMessagesTestCase TEST_FEWER_MESSAGES = TEST_MESSAGES_BASE[::2] diff --git a/test/test_neousys.py b/test/test_neousys.py index 26a220048..3acbf6389 100644 --- a/test/test_neousys.py +++ b/test/test_neousys.py @@ -1,20 +1,14 @@ #!/usr/bin/env python -import ctypes -import os -import pickle import unittest -from unittest.mock import Mock - from ctypes import ( + POINTER, byref, + c_ubyte, cast, - POINTER, sizeof, - c_ubyte, ) - -import pytest +from unittest.mock import Mock import can from can.interfaces.neousys import neousys diff --git a/test/test_neovi.py b/test/test_neovi.py index 181f92377..d8f54960a 100644 --- a/test/test_neovi.py +++ b/test/test_neovi.py @@ -4,6 +4,7 @@ """ import pickle import unittest + from can.interfaces.ics_neovi import ICSApiError diff --git a/test/test_player.py b/test/test_player.py index 9bdd484b8..5ad6e774c 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -4,12 +4,13 @@ This module tests the functions inside of player.py """ +import io +import os +import sys import unittest from unittest import mock from unittest.mock import Mock -import os -import sys -import io + import can import can.player diff --git a/test/test_robotell.py b/test/test_robotell.py index 64f4acaf1..c0658ef2c 100644 --- a/test/test_robotell.py +++ b/test/test_robotell.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import unittest + import can diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index ad4388bf7..8230168b9 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -9,6 +9,7 @@ from unittest.mock import Mock import can + from .data.example_data import generate_message diff --git a/test/test_scripts.py b/test/test_scripts.py index a22820bd8..e7bd7fd09 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -4,10 +4,10 @@ This module tests that the scripts are all callable. """ +import errno import subprocess -import unittest import sys -import errno +import unittest from abc import ABCMeta, abstractmethod from .config import * diff --git a/test/test_slcan.py b/test/test_slcan.py index 8db2d402a..774a2dec5 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -1,9 +1,10 @@ #!/usr/bin/env python import unittest + import can -from .config import IS_PYPY +from .config import IS_PYPY """ Mentioned in #1010 & #1490 diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 324890dad..90a143a36 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -8,8 +8,8 @@ import unittest import warnings from unittest.mock import patch -import can +import can from can.interfaces.socketcan.constants import ( CAN_BCM_TX_DELETE, CAN_BCM_TX_SETUP, @@ -18,13 +18,14 @@ TX_COUNTEVT, ) from can.interfaces.socketcan.socketcan import ( + BcmMsgHead, bcm_header_factory, build_bcm_header, - build_bcm_tx_delete_header, build_bcm_transmit_header, + build_bcm_tx_delete_header, build_bcm_update_header, - BcmMsgHead, ) + from .config import IS_LINUX, IS_PYPY diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index 29ceb11c0..0f4e1b4ea 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -5,13 +5,11 @@ """ import gzip -from base64 import b64decode import unittest +from base64 import b64decode from unittest import mock -from subprocess import CalledProcessError - -from can.interfaces.socketcan.utils import find_available_interfaces, error_code_to_str +from can.interfaces.socketcan.utils import error_code_to_str, find_available_interfaces from .config import IS_LINUX, TEST_INTERFACE_SOCKETCAN diff --git a/test/test_util.py b/test/test_util.py index e77401688..a4aacdf86 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -10,10 +10,10 @@ from can.util import ( _create_bus_config, _rename_kwargs, + cast_from_string, channel2int, - deprecated_args_alias, check_or_adjust_timing_clock, - cast_from_string, + deprecated_args_alias, ) diff --git a/test/test_vector.py b/test/test_vector.py index 7694b31aa..b6a0632a8 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -9,22 +9,24 @@ import pickle import sys import time +from test.config import IS_WINDOWS from unittest.mock import Mock import pytest import can from can.interfaces.vector import ( - canlib, - xldefine, - xlclass, + VectorBusParams, + VectorCanFdParams, + VectorCanParams, + VectorChannelConfig, VectorError, VectorInitializationError, VectorOperationError, - VectorChannelConfig, + canlib, + xlclass, + xldefine, ) -from can.interfaces.vector import VectorBusParams, VectorCanParams, VectorCanFdParams -from test.config import IS_WINDOWS XLDRIVER_FOUND = canlib.xldriver is not None diff --git a/test/test_viewer.py b/test/test_viewer.py index baef10bda..ecc594915 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -30,14 +30,13 @@ import time import unittest from collections import defaultdict -from typing import Dict, Tuple, Union +from test.config import IS_CI from unittest.mock import patch import pytest import can from can.viewer import CanViewer, parse_args -from test.config import IS_CI # Allow the curses module to be missing (e.g. on PyPy on Windows) try: diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py index cd5e7895e..4e7596caf 100644 --- a/test/zero_dlc_test.py +++ b/test/zero_dlc_test.py @@ -3,9 +3,8 @@ """ """ -from time import sleep -import unittest import logging +import unittest import can From ccfd7f30ec538f1fbaa676dd92e62c9b90ec0203 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:10:46 +0200 Subject: [PATCH 1024/1235] Test python 3.12 alpha (#1554) * test 3.12 * fix AttributeError on 3.12 --- .github/workflows/ci.yml | 5 +++++ test/test_neousys.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 465d5959b..4d0997473 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,11 @@ jobs: "pypy-3.8", "pypy-3.9", ] + include: + # Only test on a single configuration while there are just pre-releases + - os: ubuntu-latest + experimental: true + python-version: "3.12.0-alpha - 3.12.0" fail-fast: false steps: - uses: actions/checkout@v3 diff --git a/test/test_neousys.py b/test/test_neousys.py index 3acbf6389..080278d13 100644 --- a/test/test_neousys.py +++ b/test/test_neousys.py @@ -36,12 +36,12 @@ def tearDown(self) -> None: def test_bus_creation(self) -> None: self.assertIsInstance(self.bus, neousys.NeousysBus) - self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Setup.called) - self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Start.called) - self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_RegisterReceived.called) - self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_RegisterStatus.called) - self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Send.not_called) - self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Stop.not_called) + neousys.NEOUSYS_CANLIB.CAN_Setup.assert_called() + neousys.NEOUSYS_CANLIB.CAN_Start.assert_called() + neousys.NEOUSYS_CANLIB.CAN_RegisterReceived.assert_called() + neousys.NEOUSYS_CANLIB.CAN_RegisterStatus.assert_called() + neousys.NEOUSYS_CANLIB.CAN_Send.assert_not_called() + neousys.NEOUSYS_CANLIB.CAN_Stop.assert_not_called() CAN_Start_args = ( can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup.call_args[0] @@ -95,11 +95,11 @@ def test_send(self) -> None: arbitration_id=0x01, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=False ) self.bus.send(msg) - self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Send.called) + neousys.NEOUSYS_CANLIB.CAN_Send.assert_called() def test_shutdown(self) -> None: self.bus.shutdown() - self.assertTrue(neousys.NEOUSYS_CANLIB.CAN_Stop.called) + neousys.NEOUSYS_CANLIB.CAN_Stop.assert_called() if __name__ == "__main__": From d7d617b7700ef6940e48576bf91b1850a0a4ff0d Mon Sep 17 00:00:00 2001 From: Nick James Kirkby <20824939+driftregion@users.noreply.github.com> Date: Mon, 3 Apr 2023 21:20:07 +0800 Subject: [PATCH 1025/1235] Add MF4 support (#1289) * add extra dependency * add mf4 io writer * update asammdf requirement * changes after initial review * simplify append call * add MF4Reader class * start testing * passes tests * update documentation * update docs and CI scripts * retrigger build * update setup.py according to review * remove debug save file * changes after review * updates after review * add cython requirement * fixes after review: * fix documentation * fix item access on FD_DLC2LEN dict * add compression argument to MF4Writer stop method * add MF4Writer and MF4Reader to logger and player modules * reformat and change setup.py accordingly * cleanup test file * cleanup docs * cleanups and fix linter problems * re-add change to can.io.Logger; it somehow went lost while rebasing * remove leftover __future__ import * fix typing error in can.io's player.py and logger.py * remove diff noise, remove deprecated appveyor CI * run black * Fix import errors * add acquisition source needed by asammdf to extract bus logging * use correct source type, add md5 digest required by asammdf * refactor test_extension_matching tests to use explicit extensions * satiate mypy * update mf4 implementation * fix AttributeError * format black * refactoring * fix bugs and add direction support * add timestamps to avoid ambiguity * set allowed_timestamp_delta to 1e-4 * read into BytesIO * Add restriction to docstring * implement file_size * constrain mf4 dependencies to pass CI tests * format docstring * add mf4 extra * Update doc/listeners.rst * Remove platform specifiers for asammdf * Format code with black * remove unnecessary parenthesis * install asammdf only for CPython <= 3.12 * fix NameError --------- Co-authored-by: danielhrisca Co-authored-by: Felix Divo Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Co-authored-by: Brian Thorne Co-authored-by: Brian Thorne Co-authored-by: hardbyte --- .github/workflows/ci.yml | 2 +- .readthedocs.yml | 1 + README.rst | 2 +- can/__init__.py | 4 + can/io/__init__.py | 3 + can/io/logger.py | 18 +- can/io/mf4.py | 480 ++++++++++++++++++++++++++++++++++++++ can/io/player.py | 25 +- doc/listeners.rst | 31 +++ setup.cfg | 2 +- setup.py | 1 + test/data/example_data.py | 35 ++- test/logformats_test.py | 93 +++++--- tox.ini | 1 + 14 files changed, 649 insertions(+), 49 deletions(-) create mode 100644 can/io/mf4.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d0997473..949df6aab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,7 +141,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[canalystii,gs_usb] + pip install -e .[canalystii,gs_usb,mf4] pip install -r doc/doc-requirements.txt - name: Build documentation run: | diff --git a/.readthedocs.yml b/.readthedocs.yml index 74cb9dbdd..32be9c7b5 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -29,3 +29,4 @@ python: extra_requirements: - canalystii - gs_usb + - mf4 diff --git a/README.rst b/README.rst index 6e65e505c..3c951e866 100644 --- a/README.rst +++ b/README.rst @@ -78,7 +78,7 @@ Features - receiving, sending, and periodically sending messages - normal and extended arbitration IDs - `CAN FD `__ support -- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), TRC, CSV, SQLite, and Canutils log +- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), MF4 (Measurement Data Format v4 by ASAM), TRC, CSV, SQLite, and Canutils log - efficient in-kernel or in-hardware filtering of messages on supported interfaces - bus configuration reading from a file or from environment variables - command line tools for working with CAN buses (see the `docs `__) diff --git a/can/__init__.py b/can/__init__.py index 5cc054c19..28416fb61 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -41,6 +41,8 @@ "ModifiableCyclicTaskABC", "Message", "MessageSync", + "MF4Reader", + "MF4Writer", "Notifier", "Printer", "RedirectReader", @@ -94,6 +96,8 @@ Logger, LogReader, MessageSync, + MF4Reader, + MF4Writer, Printer, SizedRotatingLogger, SqliteReader, diff --git a/can/io/__init__.py b/can/io/__init__.py index 12cebd52c..05b8619f2 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -16,6 +16,8 @@ "Logger", "LogReader", "MessageSync", + "MF4Reader", + "MF4Writer", "Printer", "SizedRotatingLogger", "SqliteReader", @@ -36,6 +38,7 @@ from .blf import BLFReader, BLFWriter from .canutils import CanutilsLogReader, CanutilsLogWriter from .csv import CSVReader, CSVWriter +from .mf4 import MF4Reader, MF4Writer from .printer import Printer from .sqlite import SqliteReader, SqliteWriter from .trc import TRCFileVersion, TRCReader, TRCWriter diff --git a/can/io/logger.py b/can/io/logger.py index de538e866..07d288ba3 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -21,6 +21,7 @@ from .canutils import CanutilsLogWriter from .csv import CSVWriter from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter +from .mf4 import MF4Writer from .printer import Printer from .sqlite import SqliteWriter from .trc import TRCWriter @@ -38,6 +39,7 @@ class Logger(MessageWriter): * .log :class:`can.CanutilsLogWriter` * .trc :class:`can.TRCWriter` * .txt :class:`can.Printer` + * .mf4 :class:`can.MF4Writer` (optional, depends on asammdf) Any of these formats can be used with gzip compression by appending the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not @@ -59,6 +61,7 @@ class Logger(MessageWriter): ".csv": CSVWriter, ".db": SqliteWriter, ".log": CanutilsLogWriter, + ".mf4": MF4Writer, ".trc": TRCWriter, ".txt": Printer, } @@ -68,10 +71,12 @@ def __new__( # type: ignore cls: Any, filename: Optional[StringPathLike], **kwargs: Any ) -> MessageWriter: """ - :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 + :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 is None: return Printer(**kwargs) @@ -92,7 +97,10 @@ def __new__( # type: ignore suffix, file_or_filename = Logger.compress(filename, **kwargs) try: - return Logger.message_writers[suffix](file=file_or_filename, **kwargs) + LoggerType = Logger.message_writers[suffix] + if LoggerType is None: + raise ValueError(f'failed to import logger for extension "{suffix}"') + return LoggerType(file=file_or_filename, **kwargs) except KeyError: raise ValueError( f'No write support for this unknown log format "{suffix}"' diff --git a/can/io/mf4.py b/can/io/mf4.py new file mode 100644 index 000000000..faad9c37a --- /dev/null +++ b/can/io/mf4.py @@ -0,0 +1,480 @@ +""" +Contains handling of MF4 logging files. + +MF4 files represent Measurement Data Format (MDF) version 4 as specified by +the ASAM MDF standard (see https://www.asam.net/standards/detail/mdf/) +""" +import logging +from datetime import datetime +from hashlib import md5 +from io import BufferedIOBase, BytesIO +from pathlib import Path +from typing import Any, BinaryIO, Generator, Optional, Union, cast + +from ..message import Message +from ..typechecking import StringPathLike +from ..util import channel2int, dlc2len, len2dlc +from .generic import FileIOMessageWriter, MessageReader + +logger = logging.getLogger("can.io.mf4") + +try: + import asammdf + import numpy as np + from asammdf import Signal + from asammdf.blocks.mdf_v4 import MDF4 + from asammdf.blocks.v4_blocks import SourceInformation + from asammdf.blocks.v4_constants import BUS_TYPE_CAN, SOURCE_BUS + from asammdf.mdf import MDF + + STD_DTYPE = np.dtype( + [ + ("CAN_DataFrame.BusChannel", " None: + """ + :param file: + A path-like object or as file-like object to write to. + If this is a file-like object, is has to be opened in + binary write mode, not text write mode. + :param database: + optional path to a DBC or ARXML file that contains message description. + :param compression_level: + compression option as integer (default 2) + * 0 - no compression + * 1 - deflate (slower, but produces smaller files) + * 2 - transposition + deflate (slowest, but produces the smallest files) + """ + if asammdf is None: + raise NotImplementedError( + "The asammdf package was not found. Install python-can with " + "the optional dependency [mf4] to use the MF4Writer." + ) + + if kwargs.get("append", False): + raise ValueError( + f"{self.__class__.__name__} is currently not equipped to " + f"append messages to an existing file." + ) + + super().__init__(file, mode="w+b") + now = datetime.now() + self._mdf = cast(MDF4, MDF(version="4.10")) + self._mdf.header.start_time = now + self.last_timestamp = self._start_time = now.timestamp() + + self._compression_level = compression_level + + if database: + database = Path(database).resolve() + if database.exists(): + data = database.read_bytes() + attachment = data, database.name, md5(data).digest() + else: + attachment = None + else: + attachment = None + + acquisition_source = SourceInformation( + source_type=SOURCE_BUS, bus_type=BUS_TYPE_CAN + ) + + # standard frames group + self._mdf.append( + Signal( + name="CAN_DataFrame", + samples=np.array([], dtype=STD_DTYPE), + timestamps=np.array([], dtype=" int: + """Return an estimate of the current file size in bytes.""" + # TODO: find solution without accessing private attributes of asammdf + return cast(int, self._mdf._tempfile.tell()) # pylint: disable=protected-access + + def stop(self) -> None: + self._mdf.save(self.file, compression=self._compression_level) + self._mdf.close() + super().stop() + + def on_message_received(self, msg: Message) -> None: + channel = channel2int(msg.channel) + + timestamp = msg.timestamp + if timestamp is None: + timestamp = self.last_timestamp + else: + self.last_timestamp = max(self.last_timestamp, timestamp) + + timestamp -= self._start_time + + if msg.is_remote_frame: + if channel is not None: + self._rtr_buffer["CAN_RemoteFrame.BusChannel"] = channel + + self._rtr_buffer["CAN_RemoteFrame.ID"] = msg.arbitration_id + self._rtr_buffer["CAN_RemoteFrame.IDE"] = int(msg.is_extended_id) + self._rtr_buffer["CAN_RemoteFrame.Dir"] = 0 if msg.is_rx else 1 + self._rtr_buffer["CAN_RemoteFrame.DLC"] = msg.dlc + + sigs = [(np.array([timestamp]), None), (self._rtr_buffer, None)] + self._mdf.extend(2, sigs) + + elif msg.is_error_frame: + if channel is not None: + self._err_buffer["CAN_ErrorFrame.BusChannel"] = channel + + self._err_buffer["CAN_ErrorFrame.ID"] = msg.arbitration_id + self._err_buffer["CAN_ErrorFrame.IDE"] = int(msg.is_extended_id) + self._err_buffer["CAN_ErrorFrame.Dir"] = 0 if msg.is_rx else 1 + data = msg.data + size = len(data) + self._err_buffer["CAN_ErrorFrame.DataLength"] = size + self._err_buffer["CAN_ErrorFrame.DataBytes"][0, :size] = data + if msg.is_fd: + self._err_buffer["CAN_ErrorFrame.DLC"] = len2dlc(msg.dlc) + self._err_buffer["CAN_ErrorFrame.ESI"] = int(msg.error_state_indicator) + self._err_buffer["CAN_ErrorFrame.BRS"] = int(msg.bitrate_switch) + self._err_buffer["CAN_ErrorFrame.EDL"] = 1 + else: + self._err_buffer["CAN_ErrorFrame.DLC"] = msg.dlc + self._err_buffer["CAN_ErrorFrame.ESI"] = 0 + self._err_buffer["CAN_ErrorFrame.BRS"] = 0 + self._err_buffer["CAN_ErrorFrame.EDL"] = 0 + + sigs = [(np.array([timestamp]), None), (self._err_buffer, None)] + self._mdf.extend(1, sigs) + + else: + if channel is not None: + self._std_buffer["CAN_DataFrame.BusChannel"] = channel + + self._std_buffer["CAN_DataFrame.ID"] = msg.arbitration_id + self._std_buffer["CAN_DataFrame.IDE"] = int(msg.is_extended_id) + self._std_buffer["CAN_DataFrame.Dir"] = 0 if msg.is_rx else 1 + data = msg.data + size = len(data) + self._std_buffer["CAN_DataFrame.DataLength"] = size + self._std_buffer["CAN_DataFrame.DataBytes"][0, :size] = data + if msg.is_fd: + self._std_buffer["CAN_DataFrame.DLC"] = len2dlc(msg.dlc) + self._std_buffer["CAN_DataFrame.ESI"] = int(msg.error_state_indicator) + self._std_buffer["CAN_DataFrame.BRS"] = int(msg.bitrate_switch) + self._std_buffer["CAN_DataFrame.EDL"] = 1 + else: + self._std_buffer["CAN_DataFrame.DLC"] = msg.dlc + self._std_buffer["CAN_DataFrame.ESI"] = 0 + self._std_buffer["CAN_DataFrame.BRS"] = 0 + self._std_buffer["CAN_DataFrame.EDL"] = 0 + + sigs = [(np.array([timestamp]), None), (self._std_buffer, None)] + self._mdf.extend(0, sigs) + + # reset buffer structure + self._std_buffer = np.zeros(1, dtype=STD_DTYPE) + self._err_buffer = np.zeros(1, dtype=ERR_DTYPE) + self._rtr_buffer = np.zeros(1, dtype=RTR_DTYPE) + + +class MF4Reader(MessageReader): + """ + Iterator of CAN messages from a MF4 logging file. + + The MF4Reader only supports MF4 files that were recorded with python-can. + """ + + def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None: + """ + :param file: a path-like object or as file-like object to read from + If this is a file-like object, is has to be opened in + binary read mode, not text read mode. + """ + if asammdf is None: + raise NotImplementedError( + "The asammdf package was not found. Install python-can with " + "the optional dependency [mf4] to use the MF4Reader." + ) + + super().__init__(file, mode="rb") + + self._mdf: MDF4 + if isinstance(file, BufferedIOBase): + self._mdf = MDF(BytesIO(file.read())) + else: + self._mdf = MDF(file) + + self.start_timestamp = self._mdf.header.start_time.timestamp() + + masters = [self._mdf.get_master(i) for i in range(3)] + + masters = [ + np.core.records.fromarrays((master, np.ones(len(master)) * i)) + for i, master in enumerate(masters) + ] + + self.masters = np.sort(np.concatenate(masters)) + + def __iter__(self) -> Generator[Message, None, None]: + standard_counter = 0 + error_counter = 0 + rtr_counter = 0 + + for timestamp, group_index in self.masters: + # standard frames + if group_index == 0: + sample = self._mdf.get( + "CAN_DataFrame", + group=group_index, + raw=True, + record_offset=standard_counter, + record_count=1, + ) + + try: + channel = int(sample["CAN_DataFrame.BusChannel"][0]) + except ValueError: + channel = None + + if sample["CAN_DataFrame.EDL"] == 0: + is_extended_id = bool(sample["CAN_DataFrame.IDE"][0]) + arbitration_id = int(sample["CAN_DataFrame.ID"][0]) + is_rx = int(sample["CAN_DataFrame.Dir"][0]) == 0 + size = int(sample["CAN_DataFrame.DataLength"][0]) + dlc = int(sample["CAN_DataFrame.DLC"][0]) + data = sample["CAN_DataFrame.DataBytes"][0, :size].tobytes() + + msg = Message( + timestamp=timestamp + self.start_timestamp, + is_error_frame=False, + is_remote_frame=False, + is_fd=False, + is_extended_id=is_extended_id, + channel=channel, + is_rx=is_rx, + arbitration_id=arbitration_id, + data=data, + dlc=dlc, + ) + + else: + is_extended_id = bool(sample["CAN_DataFrame.IDE"][0]) + arbitration_id = int(sample["CAN_DataFrame.ID"][0]) + is_rx = int(sample["CAN_DataFrame.Dir"][0]) == 0 + size = int(sample["CAN_DataFrame.DataLength"][0]) + dlc = dlc2len(sample["CAN_DataFrame.DLC"][0]) + data = sample["CAN_DataFrame.DataBytes"][0, :size].tobytes() + error_state_indicator = bool(sample["CAN_DataFrame.ESI"][0]) + bitrate_switch = bool(sample["CAN_DataFrame.BRS"][0]) + + msg = Message( + timestamp=timestamp + self.start_timestamp, + is_error_frame=False, + is_remote_frame=False, + is_fd=True, + is_extended_id=is_extended_id, + channel=channel, + arbitration_id=arbitration_id, + is_rx=is_rx, + data=data, + dlc=dlc, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator, + ) + + yield msg + standard_counter += 1 + + # error frames + elif group_index == 1: + sample = self._mdf.get( + "CAN_ErrorFrame", + group=group_index, + raw=True, + record_offset=error_counter, + record_count=1, + ) + + try: + channel = int(sample["CAN_ErrorFrame.BusChannel"][0]) + except ValueError: + channel = None + + if sample["CAN_ErrorFrame.EDL"] == 0: + is_extended_id = bool(sample["CAN_ErrorFrame.IDE"][0]) + arbitration_id = int(sample["CAN_ErrorFrame.ID"][0]) + is_rx = int(sample["CAN_ErrorFrame.Dir"][0]) == 0 + size = int(sample["CAN_ErrorFrame.DataLength"][0]) + dlc = int(sample["CAN_ErrorFrame.DLC"][0]) + data = sample["CAN_ErrorFrame.DataBytes"][0, :size].tobytes() + + msg = Message( + timestamp=timestamp + self.start_timestamp, + is_error_frame=True, + is_remote_frame=False, + is_fd=False, + is_extended_id=is_extended_id, + channel=channel, + arbitration_id=arbitration_id, + is_rx=is_rx, + data=data, + dlc=dlc, + ) + + else: + is_extended_id = bool(sample["CAN_ErrorFrame.IDE"][0]) + arbitration_id = int(sample["CAN_ErrorFrame.ID"][0]) + is_rx = int(sample["CAN_ErrorFrame.Dir"][0]) == 0 + size = int(sample["CAN_ErrorFrame.DataLength"][0]) + dlc = dlc2len(sample["CAN_ErrorFrame.DLC"][0]) + data = sample["CAN_ErrorFrame.DataBytes"][0, :size].tobytes() + error_state_indicator = bool(sample["CAN_ErrorFrame.ESI"][0]) + bitrate_switch = bool(sample["CAN_ErrorFrame.BRS"][0]) + + msg = Message( + timestamp=timestamp + self.start_timestamp, + is_error_frame=True, + is_remote_frame=False, + is_fd=True, + is_extended_id=is_extended_id, + channel=channel, + arbitration_id=arbitration_id, + is_rx=is_rx, + data=data, + dlc=dlc, + bitrate_switch=bitrate_switch, + error_state_indicator=error_state_indicator, + ) + + yield msg + error_counter += 1 + + # remote frames + else: + sample = self._mdf.get( + "CAN_RemoteFrame", + group=group_index, + raw=True, + record_offset=rtr_counter, + record_count=1, + ) + + try: + channel = int(sample["CAN_RemoteFrame.BusChannel"][0]) + except ValueError: + channel = None + + is_extended_id = bool(sample["CAN_RemoteFrame.IDE"][0]) + arbitration_id = int(sample["CAN_RemoteFrame.ID"][0]) + is_rx = int(sample["CAN_RemoteFrame.Dir"][0]) == 0 + dlc = int(sample["CAN_RemoteFrame.DLC"][0]) + + msg = Message( + timestamp=timestamp + self.start_timestamp, + is_error_frame=False, + is_remote_frame=True, + is_fd=False, + is_extended_id=is_extended_id, + channel=channel, + arbitration_id=arbitration_id, + is_rx=is_rx, + dlc=dlc, + ) + + yield msg + + rtr_counter += 1 + + self.stop() + + def stop(self) -> None: + self._mdf.close() + super().stop() diff --git a/can/io/player.py b/can/io/player.py index 022ed503b..98a556b62 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -1,13 +1,14 @@ """ This module contains the generic :class:`LogReader` as well as :class:`MessageSync` which plays back messages -in the recorded order an time intervals. +in the recorded order and time intervals. """ import gzip import pathlib import time import typing +import typing_extensions from pkg_resources import iter_entry_points from ..message import Message @@ -20,6 +21,19 @@ from .sqlite import SqliteReader from .trc import TRCReader +MF4Reader: typing.Optional[typing.Type[MessageReader]] +try: + from .mf4 import MF4Reader +except ImportError: + MF4Reader = None + + +_OPTIONAL_READERS: typing_extensions.Final[ + typing.Dict[str, typing.Type[MessageReader]] +] = {} +if MF4Reader: + _OPTIONAL_READERS[".mf4"] = MF4Reader + class LogReader(MessageReader): """ @@ -31,6 +45,7 @@ class LogReader(MessageReader): * .csv * .db * .log + * .mf4 (optional, depends on asammdf) * .trc Gzip compressed files can be used as long as the original @@ -52,13 +67,14 @@ class LogReader(MessageReader): """ fetched_plugins = False - message_readers: typing.Dict[str, typing.Type[MessageReader]] = { + message_readers: typing.Dict[str, typing.Optional[typing.Type[MessageReader]]] = { ".asc": ASCReader, ".blf": BLFReader, ".csv": CSVReader, ".db": SqliteReader, ".log": CanutilsLogReader, ".trc": TRCReader, + **_OPTIONAL_READERS, } @staticmethod @@ -86,11 +102,14 @@ def __new__( # type: ignore if suffix == ".gz": suffix, file_or_filename = LogReader.decompress(filename) try: - return LogReader.message_readers[suffix](file=file_or_filename, **kwargs) + ReaderType = LogReader.message_readers[suffix] except KeyError: raise ValueError( f'No read support for this unknown log format "{suffix}"' ) from None + if ReaderType is None: + raise ImportError(f"failed to import reader for extension {suffix}") + return ReaderType(file=file_or_filename, **kwargs) @staticmethod def decompress( diff --git a/doc/listeners.rst b/doc/listeners.rst index 260854d2a..110e960d3 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -211,6 +211,37 @@ The following class can be used to read messages from BLF file: .. autoclass:: can.BLFReader :members: + +MF4 (Measurement Data Format v4) +-------------------------------- + +Implements support for MF4 (Measurement Data Format v4) which is a proprietary +format from ASAM (Association for Standardization of Automation and Measuring Systems), widely used in +many automotive software (Vector CANape, ETAS INCA, dSPACE ControlDesk, etc.). + +The data is stored in a compressed format which makes it compact. + +.. note:: MF4 support has to be installed as an extra with for example ``pip install python-can[mf4]``. + +.. note:: Channels will be converted to integers. + +.. note:: MF4Writer does not suppport the append mode. + + +.. autoclass:: can.MF4Writer + :members: + +The MDF format is very flexible regarding the internal structure and it is used to handle data from multiple sources, not just CAN bus logging. +MDF4Writer will always create a fixed internal file structure where there will be three channel groups (for standard, error and remote frames). +Using this fixed file structure allows for a simple implementation of MDF4Writer and MF4Reader classes. +Therefor MF4Reader can only replay files created with MF4Writer. + +The following class can be used to read messages from MF4 file: + +.. autoclass:: can.MF4Reader + :members: + + TRC ---- diff --git a/setup.cfg b/setup.cfg index 2f3ee032f..3e121b650 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,7 +8,7 @@ ignore_missing_imports = True no_implicit_optional = True disallow_incomplete_defs = True warn_redundant_casts = True -warn_unused_ignores = True +warn_unused_ignores = False exclude = (?x)( venv diff --git a/setup.py b/setup.py index b6dfab6f2..149ce230e 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ "viewer": [ 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"' ], + "mf4": ["asammdf>=6.0.0"], } setup( diff --git a/test/data/example_data.py b/test/data/example_data.py index 0fa70993a..592556926 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -33,48 +33,59 @@ def sort_messages(messages): [ Message( # empty + timestamp=1e-4, ), Message( # only data - data=[0x00, 0x42] + timestamp=2e-4, + data=[0x00, 0x42], ), Message( # no data + timestamp=3e-4, arbitration_id=0xAB, is_extended_id=False, ), Message( # no data + timestamp=4e-4, arbitration_id=0x42, is_extended_id=True, ), Message( # no data - arbitration_id=0xABCDEF + timestamp=5e-4, + arbitration_id=0xABCDEF, ), Message( # empty data - data=[] + timestamp=6e-4, + data=[], ), Message( # empty data - data=[0xFF, 0xFE, 0xFD] + timestamp=7e-4, + data=[0xFF, 0xFE, 0xFD], ), Message( # with channel as integer - channel=0 + timestamp=8e-4, + channel=0, ), Message( # with channel as integer - channel=42 + timestamp=9e-4, + channel=42, ), Message( # with channel as string - channel="vcan0" + timestamp=10e-4, + channel="vcan0", ), Message( # with channel as string - channel="awesome_channel" + timestamp=11e-4, + channel="awesome_channel", ), Message( arbitration_id=0xABCDEF, @@ -109,10 +120,10 @@ def sort_messages(messages): TEST_MESSAGES_CAN_FD = sort_messages( [ - Message(is_fd=True, data=range(64)), - Message(is_fd=True, data=range(8)), - Message(is_fd=True, data=range(8), bitrate_switch=True), - Message(is_fd=True, data=range(8), error_state_indicator=True), + Message(timestamp=12e-4, is_fd=True, data=range(64)), + Message(timestamp=13e-4, is_fd=True, data=range(8)), + Message(timestamp=14e-4, is_fd=True, data=range(8), bitrate_switch=True), + Message(timestamp=15e-4, is_fd=True, data=range(8), error_state_indicator=True), ] ) diff --git a/test/logformats_test.py b/test/logformats_test.py index d3b0eb015..31903f84b 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -36,36 +36,61 @@ logging.basicConfig(level=logging.DEBUG) +try: + import asammdf +except ModuleNotFoundError: + asammdf = None + class ReaderWriterExtensionTest(unittest.TestCase): - message_writers_and_readers = {} - for suffix, writer in can.Logger.message_writers.items(): - message_writers_and_readers[suffix] = ( - writer, - can.LogReader.message_readers.get(suffix), - ) + def _get_suffix_case_variants(self, suffix): + return [ + suffix.upper(), + suffix.lower(), + f"can.msg.ext{suffix}", + "".join([c.upper() if i % 2 else c for i, c in enumerate(suffix)]), + ] - def test_extension_matching(self): - for suffix, (writer, reader) in self.message_writers_and_readers.items(): - suffix_variants = [ - suffix.upper(), - suffix.lower(), - f"can.msg.ext{suffix}", - "".join([c.upper() if i % 2 else c for i, c in enumerate(suffix)]), - ] - for suffix_variant in suffix_variants: - tmp_file = tempfile.NamedTemporaryFile( - suffix=suffix_variant, delete=False - ) - tmp_file.close() - try: + def _test_extension(self, suffix): + WriterType = can.Logger.message_writers.get(suffix) + ReaderType = can.LogReader.message_readers.get(suffix) + for suffix_variant in self._get_suffix_case_variants(suffix): + tmp_file = tempfile.NamedTemporaryFile(suffix=suffix_variant, delete=False) + tmp_file.close() + try: + if WriterType: with can.Logger(tmp_file.name) as logger: - assert type(logger) == writer - if reader is not None: - with can.LogReader(tmp_file.name) as player: - assert type(player) == reader - finally: - os.remove(tmp_file.name) + assert type(logger) == WriterType + if ReaderType: + with can.LogReader(tmp_file.name) as player: + assert type(player) == ReaderType + finally: + os.remove(tmp_file.name) + + def test_extension_matching_asc(self): + self._test_extension(".asc") + + def test_extension_matching_blf(self): + self._test_extension(".blf") + + def test_extension_matching_csv(self): + self._test_extension(".csv") + + def test_extension_matching_db(self): + self._test_extension(".db") + + def test_extension_matching_log(self): + self._test_extension(".log") + + def test_extension_matching_txt(self): + self._test_extension(".txt") + + def test_extension_matching_mf4(self): + try: + self._test_extension(".mf4") + except NotImplementedError: + if asammdf is not None: + raise class ReaderWriterTest(unittest.TestCase, ComparingMessagesTestCase, metaclass=ABCMeta): @@ -708,6 +733,22 @@ def _setup_instance(self): ) +@unittest.skipIf(asammdf is None, "MF4 is unavailable") +class TestMF4FileFormat(ReaderWriterTest): + """Tests can.MF4Writer and can.MF4Reader""" + + def _setup_instance(self): + super()._setup_instance_helper( + can.MF4Writer, + can.MF4Reader, + binary_file=True, + check_comments=False, + preserves_channel=False, + allowed_timestamp_delta=1e-4, + adds_default_channel=0, + ) + + class TestSqliteDatabaseFormat(ReaderWriterTest): """Tests can.SqliteWriter and can.SqliteReader""" diff --git a/tox.ini b/tox.ini index 96cc82425..a41db4248 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ deps = hypothesis~=6.35.0 pyserial~=3.5 parameterized~=0.8 + asammdf>=6.0;platform_python_implementation=="CPython" and python_version < "3.12" commands = pytest {posargs} From 6e5df1ddafad8c1fb427ee92c900152b260d6628 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:34:12 +0200 Subject: [PATCH 1026/1235] mf4 followup (#1555) --- can/io/player.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/can/io/player.py b/can/io/player.py index 98a556b62..e4db0e167 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -8,7 +8,6 @@ import time import typing -import typing_extensions from pkg_resources import iter_entry_points from ..message import Message @@ -18,22 +17,10 @@ from .canutils import CanutilsLogReader from .csv import CSVReader from .generic import MessageReader +from .mf4 import MF4Reader from .sqlite import SqliteReader from .trc import TRCReader -MF4Reader: typing.Optional[typing.Type[MessageReader]] -try: - from .mf4 import MF4Reader -except ImportError: - MF4Reader = None - - -_OPTIONAL_READERS: typing_extensions.Final[ - typing.Dict[str, typing.Type[MessageReader]] -] = {} -if MF4Reader: - _OPTIONAL_READERS[".mf4"] = MF4Reader - class LogReader(MessageReader): """ @@ -73,8 +60,8 @@ class LogReader(MessageReader): ".csv": CSVReader, ".db": SqliteReader, ".log": CanutilsLogReader, + ".mf4": MF4Reader, ".trc": TRCReader, - **_OPTIONAL_READERS, } @staticmethod From e0155c781eb6b0ede5e52e939e2d2f006f100d8f Mon Sep 17 00:00:00 2001 From: Yannis Date: Tue, 4 Apr 2023 16:06:12 +0300 Subject: [PATCH 1027/1235] Optional dependency and docs for the CANine interface (#1556) * optional dependency and docs for the CANine interface * use alphabetical order --- doc/plugin-interface.rst | 3 +++ setup.py | 1 + 2 files changed, 4 insertions(+) diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index bab8c85a9..4a08ee9a7 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -65,6 +65,8 @@ The table below lists interface drivers that can be added by installing addition +----------------------------+-------------------------------------------------------+ | Name | Description | +============================+=======================================================+ +| `python-can-canine`_ | CAN Driver for the CANine CAN interface | ++----------------------------+-------------------------------------------------------+ | `python-can-cvector`_ | Cython based version of the 'VectorBus' | +----------------------------+-------------------------------------------------------+ | `python-can-remote`_ | CAN over network bridge | @@ -72,6 +74,7 @@ The table below lists interface drivers that can be added by installing addition | `python-can-sontheim`_ | CAN Driver for Sontheim CAN interfaces (e.g. CANfox) | +----------------------------+-------------------------------------------------------+ +.. _python-can-canine: https://github.com/tinymovr/python-can-canine .. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector .. _python-can-remote: https://github.com/christiansandberg/python-can-remote .. _python-can-sontheim: https://github.com/MattWoodhead/python-can-sontheim diff --git a/setup.py b/setup.py index 149ce230e..65298b072 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ "pcan": ["uptime~=3.0.1"], "remote": ["python-can-remote"], "sontheim": ["python-can-sontheim>=0.1.2"], + "canine": ["python-can-canine>=0.2.2"], "viewer": [ 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"' ], From fd8d0766d29c004865a18a12a2fb58cb52e7b435 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 9 Apr 2023 16:57:15 +0200 Subject: [PATCH 1028/1235] add modules to __all__ (#1558) --- can/__init__.py | 26 ++++++++++++++++++++------ can/interfaces/__init__.py | 28 ++++++++++++++++++++++++++++ can/io/__init__.py | 11 +++++++++++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 28416fb61..3983c7495 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -17,7 +17,6 @@ "BitTimingFd", "BLFReader", "BLFWriter", - "broadcastmanager", "BufferedReader", "Bus", "BusABC", @@ -32,8 +31,6 @@ "CSVReader", "CSVWriter", "CyclicSendTaskABC", - "detect_available_configs", - "interface", "LimitedDurationCyclicSendTaskABC", "Listener", "Logger", @@ -47,17 +44,34 @@ "Printer", "RedirectReader", "RestartableCyclicTaskABC", - "set_logging_level", "SizedRotatingLogger", "SqliteReader", "SqliteWriter", "ThreadSafeBus", - "typechecking", "TRCFileVersion", "TRCReader", "TRCWriter", - "util", "VALID_INTERFACES", + "bit_timing", + "broadcastmanager", + "bus", + "ctypesutil", + "detect_available_configs", + "exceptions", + "interface", + "interfaces", + "listener", + "logconvert", + "log", + "logger", + "message", + "notifier", + "player", + "set_logging_level", + "thread_safe_bus", + "typechecking", + "util", + "viewer", ] log = logging.getLogger("can") diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 5089dbf47..b14914230 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -5,6 +5,34 @@ import sys from typing import Dict, Tuple, cast +__all__ = [ + "BACKENDS", + "VALID_INTERFACES", + "canalystii", + "cantact", + "etas", + "gs_usb", + "ics_neovi", + "iscan", + "ixxat", + "kvaser", + "neousys", + "nican", + "nixnet", + "pcan", + "robotell", + "seeedstudio", + "serial", + "slcan", + "socketcan", + "socketcand", + "systec", + "udp_multicast", + "usb2can", + "vector", + "virtual", +] + # interface_name => (module, classname) BACKENDS: Dict[str, Tuple[str, str]] = { "kvaser": ("can.interfaces.kvaser", "KvaserBus"), diff --git a/can/io/__init__.py b/can/io/__init__.py index 05b8619f2..263bbe235 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -25,6 +25,17 @@ "TRCFileVersion", "TRCReader", "TRCWriter", + "asc", + "blf", + "canutils", + "csv", + "generic", + "logger", + "mf4", + "player", + "printer", + "sqlite", + "trc", ] # Generic From d62c97f3963a92effd2a17e763580569d6b4f3f2 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 9 Apr 2023 17:22:33 +0200 Subject: [PATCH 1029/1235] improve slcanTestCase robustness (#1559) --- test/test_slcan.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/test/test_slcan.py b/test/test_slcan.py index 774a2dec5..a2f8f5d15 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -1,6 +1,9 @@ #!/usr/bin/env python import unittest +from typing import cast + +import serial import can @@ -15,16 +18,17 @@ https://realpython.com/pypy-faster-python/#it-doesnt-work-well-with-c-extensions """ -TIMEOUT = 0.5 if IS_PYPY else 0.001 # 0.001 is the default set in slcanBus +TIMEOUT = 0.5 if IS_PYPY else 0.01 # 0.001 is the default set in slcanBus class slcanTestCase(unittest.TestCase): def setUp(self): - self.bus = can.Bus( - "loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT + self.bus = cast( + can.interfaces.slcan.slcanBus, + can.Bus("loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT), ) - self.serial = self.bus.serialPortOrig - self.serial.read(self.serial.in_waiting) + self.serial = cast(serial.Serial, self.bus.serialPortOrig) + self.serial.reset_input_buffer() def tearDown(self): self.bus.shutdown() @@ -44,8 +48,9 @@ def test_send_extended(self): arbitration_id=0x12ABCDEF, is_extended_id=True, data=[0xAA, 0x55] ) self.bus.send(msg) - data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b"T12ABCDEF2AA55\r") + expected = b"T12ABCDEF2AA55\r" + data = self.serial.read(len(expected)) + self.assertEqual(data, expected) def test_recv_standard(self): self.serial.write(b"t4563112233\r") @@ -62,8 +67,9 @@ def test_send_standard(self): arbitration_id=0x456, is_extended_id=False, data=[0x11, 0x22, 0x33] ) self.bus.send(msg) - data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b"t4563112233\r") + expected = b"t4563112233\r" + data = self.serial.read(len(expected)) + self.assertEqual(data, expected) def test_recv_standard_remote(self): self.serial.write(b"r1238\r") @@ -79,8 +85,9 @@ def test_send_standard_remote(self): arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, dlc=8 ) self.bus.send(msg) - data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b"r1238\r") + expected = b"r1238\r" + data = self.serial.read(len(expected)) + self.assertEqual(data, expected) def test_recv_extended_remote(self): self.serial.write(b"R12ABCDEF6\r") @@ -96,8 +103,9 @@ def test_send_extended_remote(self): arbitration_id=0x12ABCDEF, is_extended_id=True, is_remote_frame=True, dlc=6 ) self.bus.send(msg) - data = self.serial.read(self.serial.in_waiting) - self.assertEqual(data, b"R12ABCDEF6\r") + expected = b"R12ABCDEF6\r" + data = self.serial.read(len(expected)) + self.assertEqual(data, expected) def test_partial_recv(self): self.serial.write(b"T12ABCDEF") From 39a396541cfd7f0e160bc1ddaf3edde10bdc6928 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 9 Apr 2023 20:03:21 +0200 Subject: [PATCH 1030/1235] Update CHANGELOG.md for 4.2.0 (#1552) * update CHANGELOG.md * Commit suggestion 1 Co-authored-by: Brian Thorne * Commit suggestion 2 Co-authored-by: Brian Thorne * Commit suggestion 3 Co-authored-by: Brian Thorne * add new entry for MF4 support * update CHANGELOG.md --------- Co-authored-by: Brian Thorne --- CHANGELOG.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++- can/__init__.py | 2 +- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6ff2bbbf..c9767903f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,85 @@ +Version 4.2.0 +============= + +Breaking Changes +---------------- +* The ``can.BitTiming`` class was replaced with the new + ``can.BitTiming`` and `can.BitTimingFd` classes (#1468, #1515). + Early adopters of ``can.BitTiming`` will need to update their code. Check the + [documentation](https://python-can.readthedocs.io/en/develop/bit_timing.html) + for more information. Currently, the following interfaces support the new classes: + * canalystii (#1468) + * cantact (#1468) + * nixnet (#1520) + * pcan (#1514) + * vector (#1470, #1516) + + There are open pull requests for kvaser (#1510), slcan (#1512) and usb2can (#1511). Testing + and reviewing of these open PRs would be most appreciated. + +Features +-------- + +### IO +* Add support for MF4 files (#1289). +* Add support for version 2 TRC files and other TRC file enhancements (#1530). + +### Type Annotations +* Export symbols to satisfy type checkers (#1547, #1551, #1558). + +### Interface Improvements +* Add ``__del__`` method to ``can.BusABC`` to automatically release resources (#1489). +* pcan: Update PCAN Basic to 4.6.2.753 (#1481). +* pcan: Use select instead of polling on Linux (#1410). +* socketcan: Use ip link JSON output in ``find_available_interfaces`` (#1478). +* socketcan: Enable SocketCAN interface tests in GitHub CI (#1484). +* slcan: improve receiving performance (#1490). +* usb2can: Stop using root logger (#1483). +* usb2can: Faster channel detection on Windows (#1480). +* vector: Only check sample point instead of tseg & sjw (#1486). +* vector: add VN5611 hwtype (#1501). + +Documentation +------------- +* Add new section about related tools to documentation. Add a list of + plugin interface packages (#1457). + +Bug Fixes +--------- +* Automatic type conversion for config values (#1498, #1499). +* pcan: Fix ``Bus.__new__`` for CAN-FD interfaces (#1458, #1460). +* pcan: Fix Detection of Library on Windows on ARM (#1463). +* socketcand: extended ID bug fixes (#1504, #1508). +* vector: improve robustness against unknown HardwareType values (#1500, #1502). + +Deprecations +------------ +* The ``bustype`` parameter of ``can.Bus`` is deprecated and will be + removed in version 5.0, use ``interface`` instead. (#1462). +* The ``context`` parameter of ``can.Bus`` is deprecated and will be + removed in version 5.0, use ``config_context`` instead. (#1474). +* The ``bit_timing`` parameter of ``CantactBus`` is deprecated and will be + removed in version 5.0, use ``timing`` instead. (#1468). +* The ``bit_timing`` parameter of ``CANalystIIBus`` is deprecated and will be + removed in version 5.0, use ``timing`` instead. (#1468). +* The ``brs`` and ``log_errors`` parameters of `` NiXNETcanBus`` are deprecated +* and will be removed in version 5.0. (#1520). + +Miscellaneous +------------- +* Use high resolution timer on Windows to improve + timing precision for BroadcastManager (#1449). +* Improve ThreadBasedCyclicSendTask timing (#1539). +* Make code examples executable on Linux (#1452). +* Fix CanFilter type annotation (#1456). +* Fix ``The entry_points().get`` deprecation warning and improve + type annotation of ``can.interfaces.BACKENDS`` (#1465). +* Add ``ignore_config`` parameter to ``can.Bus`` (#1474). +* Add deprecation period to utility function ``deprecated_args_alias`` (#1477). +* Add `ruff` to the CI system (#1551) + Version 4.1.0 -==== +============= Breaking Changes ---------------- diff --git a/can/__init__.py b/can/__init__.py index 3983c7495..e8d7850ca 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.1.0" +__version__ = "4.2.0rc0" __all__ = [ "ASCReader", "ASCWriter", From f004edb4674ffa6308b0bc4f398f726f9ceb2c83 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 9 Apr 2023 21:33:27 +0200 Subject: [PATCH 1031/1235] Remove asterisk --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9767903f..33cfc4849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,7 +63,7 @@ Deprecations * The ``bit_timing`` parameter of ``CANalystIIBus`` is deprecated and will be removed in version 5.0, use ``timing`` instead. (#1468). * The ``brs`` and ``log_errors`` parameters of `` NiXNETcanBus`` are deprecated -* and will be removed in version 5.0. (#1520). + and will be removed in version 5.0. (#1520). Miscellaneous ------------- From e522e3914e54f019d17568643ad2ce4e2417bdc7 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 10 Apr 2023 17:43:25 +0200 Subject: [PATCH 1032/1235] Fix TRC test (#1561) --- can/io/trc.py | 5 ++--- test/logformats_test.py | 9 +++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 75c81b502..fc2a9e1f7 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -5,9 +5,8 @@ for file format description Version 1.1 will be implemented as it is most commonly used -""" # noqa +""" -import io import logging import os from datetime import datetime, timedelta, timezone @@ -274,7 +273,7 @@ def __init__( super().__init__(file, mode="w") self.channel = channel - if isinstance(self.file, io.TextIOWrapper): + if hasattr(self.file, "reconfigure"): self.file.reconfigure(newline="\r\n") else: raise TypeError("File must be opened in text mode.") diff --git a/test/logformats_test.py b/test/logformats_test.py index 31903f84b..d6bd13b41 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -934,10 +934,11 @@ def msg_ext(timestamp): self.assertMessagesEqual(actual, expected_messages) def test_not_supported_version(self): - with self.assertRaises(NotImplementedError): - writer = can.TRCWriter("test.trc") - writer.file_version = can.TRCFileVersion.UNKNOWN - writer.on_message_received(can.Message()) + with tempfile.NamedTemporaryFile(mode="w") as f: + with self.assertRaises(NotImplementedError): + writer = can.TRCWriter(f) + writer.file_version = can.TRCFileVersion.UNKNOWN + writer.on_message_received(can.Message()) class TestTrcFileFormatV1_0(TestTrcFileFormatBase): From ef6940031ae430d55a1257aae93880ff3ac77e36 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 10 Apr 2023 18:09:53 +0200 Subject: [PATCH 1033/1235] remove Travis CI (#1562) --- .travis.yml | 65 ----------------------------------------------------- README.rst | 6 +---- tox.ini | 7 ------ 3 files changed, 1 insertion(+), 77 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9bd1920e0..000000000 --- a/.travis.yml +++ /dev/null @@ -1,65 +0,0 @@ -language: python - -# Linux setup -dist: focal - -cache: - directories: - - "$HOME/.cache/pip" - -install: - - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - - travis_retry python setup.py install - -script: - - | - # install tox - travis_retry pip install tox - # Run the tests - tox -e travis - -jobs: - allow_failures: - # Allow arm64 builds to fail - - arch: arm64 - - include: - # Stages with the same name get run in parallel. - # Jobs within a stage can also be named. - - # testing socketcan on Trusty & Python 3.6, since it is not available on Xenial - - stage: test - name: Socketcan - os: linux - arch: amd64 - dist: trusty - python: "3.6" - sudo: required - env: TEST_SOCKETCAN=TRUE - - # arm64 builds - - stage: test - name: Linux arm64 - os: linux - arch: arm64 - language: generic - sudo: required - addons: - apt: - update: true - env: HOST_ARM64=TRUE - before_install: - - sudo apt install -y python3 python3-pip - # Travis doesn't seem to provide Python binaries yet for this arch - - sudo update-alternatives --install /usr/bin/python python $(which python3) 10 - - sudo update-alternatives --install /usr/bin/pip pip $(which pip3) 10 - # The below is the same as in the Socketcan job but with elevated privileges - install: - - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - - travis_retry sudo python setup.py install - script: - - | - # install tox - travis_retry sudo pip install tox - # Run the tests - sudo tox -e travis diff --git a/README.rst b/README.rst index 3c951e866..e7d40f590 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ python-can |pypi| |conda| |python_implementation| |downloads| |downloads_monthly| -|docs| |github-actions| |build_travis| |coverage| |mergify| |formatter| +|docs| |github-actions| |coverage| |mergify| |formatter| .. |pypi| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ @@ -37,10 +37,6 @@ python-can :target: https://github.com/hardbyte/python-can/actions/workflows/ci.yml :alt: Github Actions workflow status -.. |build_travis| image:: https://img.shields.io/travis/hardbyte/python-can/develop.svg?label=Travis%20CI - :target: https://app.travis-ci.com/github/hardbyte/python-can - :alt: Travis CI Server for develop branch - .. |coverage| image:: https://coveralls.io/repos/github/hardbyte/python-can/badge.svg?branch=develop :target: https://coveralls.io/github/hardbyte/python-can?branch=develop :alt: Test coverage reports on Coveralls.io diff --git a/tox.ini b/tox.ini index a41db4248..b48b5642f 100644 --- a/tox.ini +++ b/tox.ini @@ -29,13 +29,6 @@ passenv = PY_COLORS TEST_SOCKETCAN -[testenv:travis] -passenv = - CI - TRAVIS - TRAVIS_* - TEST_SOCKETCAN - [pytest] testpaths = test From 1f2f29c4eb149d94c071fa2b135c55883822790a Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:44:27 +0200 Subject: [PATCH 1034/1235] Improve __del__ error message (#1564) * avoid "Exception ignored in function BusABC.__del__" * improve error message --- can/bus.py | 2 +- can/interfaces/vector/canlib.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/can/bus.py b/can/bus.py index 3964215d3..5bd2d4e30 100644 --- a/can/bus.py +++ b/can/bus.py @@ -439,7 +439,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): def __del__(self) -> None: if not self._is_shutdown: - LOG.warning("%s was not properly shut down", self.__class__) + LOG.warning("%s was not properly shut down", self.__class__.__name__) # We do some best-effort cleanup if the user # forgot to properly close the bus instance with contextlib.suppress(AttributeError): diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index e60d20b0d..03a3d9b1f 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -6,6 +6,7 @@ # Import Standard Python Modules # ============================== +import contextlib import ctypes import logging import os @@ -869,9 +870,11 @@ def flush_tx_buffer(self) -> None: def shutdown(self) -> None: super().shutdown() - self.xldriver.xlDeactivateChannel(self.port_handle, self.mask) - self.xldriver.xlClosePort(self.port_handle) - self.xldriver.xlCloseDriver() + + with contextlib.suppress(VectorError): + self.xldriver.xlDeactivateChannel(self.port_handle, self.mask) + self.xldriver.xlClosePort(self.port_handle) + self.xldriver.xlCloseDriver() def reset(self) -> None: self.xldriver.xlDeactivateChannel(self.port_handle, self.mask) From af52622b0c62e64f0de99a129a15a74ca84bb4a3 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 16 Apr 2023 16:01:23 +0200 Subject: [PATCH 1035/1235] Add interface modules to __all__ (#1568) --- can/interfaces/ics_neovi/__init__.py | 1 + can/interfaces/ixxat/__init__.py | 8 +++++++- can/interfaces/kvaser/__init__.py | 10 ++++++++++ can/interfaces/neousys/__init__.py | 5 ++++- can/interfaces/pcan/__init__.py | 2 ++ can/interfaces/seeedstudio/__init__.py | 5 ++++- can/interfaces/serial/__init__.py | 5 ++++- can/interfaces/socketcan/__init__.py | 3 +++ can/interfaces/socketcand/__init__.py | 5 ++++- can/interfaces/systec/__init__.py | 9 ++++++++- can/interfaces/udp_multicast/__init__.py | 6 +++++- can/interfaces/usb2can/__init__.py | 3 +++ can/interfaces/vector/__init__.py | 7 ++++++- 13 files changed, 61 insertions(+), 8 deletions(-) diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py index e221e0840..0252c7345 100644 --- a/can/interfaces/ics_neovi/__init__.py +++ b/can/interfaces/ics_neovi/__init__.py @@ -6,6 +6,7 @@ "ICSInitializationError", "ICSOperationError", "NeoViBus", + "neovi_bus", ] from .neovi_bus import ICSApiError, ICSInitializationError, ICSOperationError, NeoViBus diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index bc871b372..6fe79adb8 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -5,8 +5,14 @@ """ __all__ = [ - "get_ixxat_hwids", "IXXATBus", + "canlib", + "canlib_vcinpl", + "canlib_vcinpl2", + "constants", + "exceptions", + "get_ixxat_hwids", + "structures", ] from can.interfaces.ixxat.canlib import IXXATBus diff --git a/can/interfaces/kvaser/__init__.py b/can/interfaces/kvaser/__init__.py index 36a21db9f..8e7c3feb9 100644 --- a/can/interfaces/kvaser/__init__.py +++ b/can/interfaces/kvaser/__init__.py @@ -1,4 +1,14 @@ """ """ +__all__ = [ + "CANLIBInitializationError", + "CANLIBOperationError", + "KvaserBus", + "canlib", + "constants", + "get_channel_info", + "structures", +] + from can.interfaces.kvaser.canlib import * diff --git a/can/interfaces/neousys/__init__.py b/can/interfaces/neousys/__init__.py index 6bd503f35..44bd3127f 100644 --- a/can/interfaces/neousys/__init__.py +++ b/can/interfaces/neousys/__init__.py @@ -1,5 +1,8 @@ """ Neousys CAN bus driver """ -__all__ = ["NeousysBus"] +__all__ = [ + "NeousysBus", + "neousys", +] from can.interfaces.neousys.neousys import NeousysBus diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py index b46ccb050..73e351665 100644 --- a/can/interfaces/pcan/__init__.py +++ b/can/interfaces/pcan/__init__.py @@ -4,6 +4,8 @@ __all__ = [ "PcanBus", "PcanError", + "basic", + "pcan", ] from can.interfaces.pcan.pcan import PcanBus, PcanError diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py index 2fc348a17..0b3c7b1c9 100644 --- a/can/interfaces/seeedstudio/__init__.py +++ b/can/interfaces/seeedstudio/__init__.py @@ -1,6 +1,9 @@ """ """ -__all__ = ["SeeedBus"] +__all__ = [ + "SeeedBus", + "seeedstudio", +] from can.interfaces.seeedstudio.seeedstudio import SeeedBus diff --git a/can/interfaces/serial/__init__.py b/can/interfaces/serial/__init__.py index 1b1d63c49..beb6457bb 100644 --- a/can/interfaces/serial/__init__.py +++ b/can/interfaces/serial/__init__.py @@ -1,6 +1,9 @@ """ """ -__all__ = ["SerialBus"] +__all__ = [ + "SerialBus", + "serial_can", +] from can.interfaces.serial.serial_can import SerialBus diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index 0fbdede58..6e279c2fe 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -6,6 +6,9 @@ "CyclicSendTask", "MultiRateCyclicSendTask", "SocketcanBus", + "constants", + "socketcan", + "utils", ] from .socketcan import CyclicSendTask, MultiRateCyclicSendTask, SocketcanBus diff --git a/can/interfaces/socketcand/__init__.py b/can/interfaces/socketcand/__init__.py index e6b106918..ce18441bc 100644 --- a/can/interfaces/socketcand/__init__.py +++ b/can/interfaces/socketcand/__init__.py @@ -6,6 +6,9 @@ http://www.domologic.de """ -__all__ = ["SocketCanDaemonBus"] +__all__ = [ + "SocketCanDaemonBus", + "socketcand", +] from .socketcand import SocketCanDaemonBus diff --git a/can/interfaces/systec/__init__.py b/can/interfaces/systec/__init__.py index 9a97b3054..e38a52fb1 100644 --- a/can/interfaces/systec/__init__.py +++ b/can/interfaces/systec/__init__.py @@ -1,3 +1,10 @@ -__all__ = ["UcanBus"] +__all__ = [ + "UcanBus", + "constants", + "exceptions", + "structures", + "ucan", + "ucanbus", +] from can.interfaces.systec.ucanbus import UcanBus diff --git a/can/interfaces/udp_multicast/__init__.py b/can/interfaces/udp_multicast/__init__.py index 6e11a02c5..d52c028f0 100644 --- a/can/interfaces/udp_multicast/__init__.py +++ b/can/interfaces/udp_multicast/__init__.py @@ -1,5 +1,9 @@ """A module to allow CAN over UDP on IPv4/IPv6 multicast.""" -__all__ = ["UdpMulticastBus"] +__all__ = [ + "UdpMulticastBus", + "bus", + "utils", +] from .bus import UdpMulticastBus diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index 17f5583f3..a2b587842 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -4,6 +4,9 @@ __all__ = [ "Usb2CanAbstractionLayer", "Usb2canBus", + "serial_selector", + "usb2canabstractionlayer", + "usb2canInterface", ] from .usb2canabstractionlayer import Usb2CanAbstractionLayer diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index e0c34d88f..3a5b13a1f 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -2,7 +2,6 @@ """ __all__ = [ - "get_channel_configs", "VectorBus", "VectorBusParams", "VectorCanFdParams", @@ -11,6 +10,12 @@ "VectorError", "VectorInitializationError", "VectorOperationError", + "canlib", + "exceptions", + "get_channel_configs", + "xlclass", + "xldefine", + "xldriver", ] from .canlib import ( From 6c820a137940c27620ab19bdaf1c38c8da6d65d4 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 26 Apr 2023 10:00:01 +1200 Subject: [PATCH 1036/1235] Release version 4.2.0 (#1576) * Set version to 4.2.0 * Update CHANGELOG.md --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- CHANGELOG.md | 4 ++-- can/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33cfc4849..66ce542ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,10 +25,10 @@ Features * Add support for version 2 TRC files and other TRC file enhancements (#1530). ### Type Annotations -* Export symbols to satisfy type checkers (#1547, #1551, #1558). +* Export symbols to satisfy type checkers (#1547, #1551, #1558, #1568). ### Interface Improvements -* Add ``__del__`` method to ``can.BusABC`` to automatically release resources (#1489). +* Add ``__del__`` method to ``can.BusABC`` to automatically release resources (#1489, #1564). * pcan: Update PCAN Basic to 4.6.2.753 (#1481). * pcan: Use select instead of polling on Linux (#1410). * socketcan: Use ip link JSON output in ``find_available_interfaces`` (#1478). diff --git a/can/__init__.py b/can/__init__.py index e8d7850ca..870bef73f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.2.0rc0" +__version__ = "4.2.0" __all__ = [ "ASCReader", "ASCWriter", From f2cb4cbc29b198d65fa78b52fd69db75119e9f56 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 11 May 2023 23:12:02 +0200 Subject: [PATCH 1037/1235] ASCWriter: use correct channel for error frame (#1583) * use correct channel for error frame * add test --- can/io/asc.py | 15 ++++++++------- can/message.py | 4 +++- test/logformats_test.py | 22 +++++++++++++++++++++- test/message_helper.py | 31 ++++++------------------------- 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index b8054ecfc..5169d7468 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -420,8 +420,15 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: self.file.write(line) def on_message_received(self, msg: Message) -> None: + channel = channel2int(msg.channel) + if channel is None: + channel = self.channel + else: + # Many interfaces start channel numbering at 0 which is invalid + channel += 1 + if msg.is_error_frame: - self.log_event(f"{self.channel} ErrorFrame", msg.timestamp) + self.log_event(f"{channel} ErrorFrame", msg.timestamp) return if msg.is_remote_frame: dtype = f"r {msg.dlc:x}" # New after v8.5 @@ -432,12 +439,6 @@ def on_message_received(self, msg: Message) -> None: arb_id = f"{msg.arbitration_id:X}" 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 if msg.is_fd: flags = 0 flags |= 1 << 12 diff --git a/can/message.py b/can/message.py index 05700ef72..02706583c 100644 --- a/can/message.py +++ b/can/message.py @@ -291,6 +291,7 @@ def equals( self, other: "Message", timestamp_delta: Optional[float] = 1.0e-6, + check_channel: bool = True, check_direction: bool = True, ) -> bool: """ @@ -299,6 +300,7 @@ def equals( :param other: the message to compare with :param timestamp_delta: the maximum difference in seconds at which two timestamps are still considered equal or `None` to not compare timestamps + :param check_channel: whether to compare the message channel :param check_direction: whether to compare the messages' directions (Tx/Rx) :return: True if and only if the given message equals this one @@ -322,7 +324,7 @@ def equals( and self.data == other.data and self.is_remote_frame == other.is_remote_frame and self.is_error_frame == other.is_error_frame - and self.channel == other.channel + and (self.channel == other.channel or not check_channel) and self.is_fd == other.is_fd and self.bitrate_switch == other.bitrate_switch and self.error_state_indicator == other.error_state_indicator diff --git a/test/logformats_test.py b/test/logformats_test.py index d6bd13b41..50f48c391 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -591,6 +591,26 @@ def test_no_triggerblock(self): def test_can_dlc_greater_than_8(self): _msg_list = self._read_log_file("issue_1299.asc") + def test_error_frame_channel(self): + # gh-issue 1578 + err_frame = can.Message(is_error_frame=True, channel=4) + + temp_file = tempfile.NamedTemporaryFile("w", delete=False) + temp_file.close() + + try: + with can.ASCWriter(temp_file.name) as writer: + writer.on_message_received(err_frame) + + with can.ASCReader(temp_file.name) as reader: + msg_list = list(reader) + assert len(msg_list) == 1 + assert err_frame.equals( + msg_list[0], check_channel=True + ), f"{err_frame!r}!={msg_list[0]!r}" + finally: + os.unlink(temp_file.name) + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. @@ -814,7 +834,7 @@ def test_not_crashes_with_stdout(self): printer(message) def test_not_crashes_with_file(self): - with tempfile.NamedTemporaryFile("w", delete=False) as temp_file: + with tempfile.NamedTemporaryFile("w") as temp_file: with can.Printer(temp_file) as printer: for message in self.messages: printer(message) diff --git a/test/message_helper.py b/test/message_helper.py index fe193097b..d10e9195f 100644 --- a/test/message_helper.py +++ b/test/message_helper.py @@ -4,8 +4,6 @@ This module contains a helper for writing test cases that need to compare messages. """ -from copy import copy - class ComparingMessagesTestCase: """ @@ -28,31 +26,14 @@ def assertMessageEqual(self, message_1, message_2): Checks that two messages are equal, according to the given rules. """ - if message_1.equals(message_2, timestamp_delta=self.allowed_timestamp_delta): - return - elif self.preserves_channel: + if not message_1.equals( + message_2, + check_channel=self.preserves_channel, + timestamp_delta=self.allowed_timestamp_delta, + ): print(f"Comparing: message 1: {message_1!r}") print(f" message 2: {message_2!r}") - self.fail( - "messages are unequal with allowed timestamp delta {}".format( - self.allowed_timestamp_delta - ) - ) - else: - message_2 = copy(message_2) # make sure this method is pure - message_2.channel = message_1.channel - if message_1.equals( - message_2, timestamp_delta=self.allowed_timestamp_delta - ): - return - else: - print(f"Comparing: message 1: {message_1!r}") - print(f" message 2: {message_2!r}") - self.fail( - "messages are unequal with allowed timestamp delta {} even when ignoring channels".format( - self.allowed_timestamp_delta - ) - ) + self.fail(f"messages are unequal: \n{message_1}\n{message_2}") def assertMessagesEqual(self, messages_1, messages_2): """ From efb301ad62ff27787863356ebcef8421c712abda Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 11 May 2023 23:13:30 +0200 Subject: [PATCH 1038/1235] Fix PCAN OSError (#1580) --- can/interfaces/pcan/basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index a60b96079..7c41e2816 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -690,13 +690,13 @@ def __init__(self): load_library_func = cdll.LoadLibrary if platform.system() == "Windows" or "CYGWIN" in platform.system(): - lib_name = "PCANBasic.dll" + lib_name = "PCANBasic" elif platform.system() == "Darwin": # PCBUSB library is a third-party software created # and maintained by the MacCAN project - lib_name = "libPCBUSB.dylib" + lib_name = "PCBUSB" else: - lib_name = "libpcanbasic.so" + lib_name = "pcanbasic" lib_path = find_library(lib_name) if not lib_path: From 1cfd87658e9b84796ffbec3527675cd26f9f6780 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 12 May 2023 12:17:58 -0400 Subject: [PATCH 1039/1235] With Windows event the first two periodic frames are sent back without delay (#1590) When the ThreadBasedCyclicSendTask thread starts, the timer event was already set before entering the first loop. This resulted in the first timer wait to not wait. --- can/broadcastmanager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 07d93e296..39dc1f0ba 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -293,6 +293,10 @@ def _run(self) -> None: msg_index = 0 msg_due_time_ns = time.perf_counter_ns() + if USE_WINDOWS_EVENTS: + # Make sure the timer is non-signaled before entering the loop + win32event.WaitForSingleObject(self.event.handle, 0) + while not self.stopped: # Prevent calling bus.send from multiple threads with self.send_lock: From 7eac6f723d660392ad522a6249f554c478725bdd Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 15 May 2023 11:49:52 +0200 Subject: [PATCH 1040/1235] update CHANGELOG.md for 4.2.1 (#1593) --- CHANGELOG.md | 10 ++++++++++ can/__init__.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ce542ea..a240cb650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +Version 4.2.1 +============= + +Bug Fixes +--------- +* The ASCWriter now logs the correct channel for error frames (#1578, #1583). +* Fix PCAN library detection (#1579, #1580). +* On Windows, the first two periodic frames were sent without delay (#1590). + + Version 4.2.0 ============= diff --git a/can/__init__.py b/can/__init__.py index 870bef73f..a14228f62 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.2.0" +__version__ = "4.2.1" __all__ = [ "ASCReader", "ASCWriter", From 25fe56663d9b5798e21bd177192fb2e9f7893838 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 15 May 2023 16:19:01 +0200 Subject: [PATCH 1041/1235] Improve can.Bus typing (#1557) --- can/interface.py | 79 ++++++++++++++++++++-------------------- can/logger.py | 4 +-- can/util.py | 86 ++++++++++++++++++++++++-------------------- doc/bus.rst | 13 +++---- doc/conf.py | 4 ++- doc/internal-api.rst | 25 ++++++++----- 6 files changed, 114 insertions(+), 97 deletions(-) diff --git a/can/interface.py b/can/interface.py index 4c59dab8b..9c828a608 100644 --- a/can/interface.py +++ b/can/interface.py @@ -55,8 +55,20 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]: return cast(Type[BusABC], bus_class) -class Bus(BusABC): # pylint: disable=abstract-method - """Bus wrapper with configuration loading. +@util.deprecated_args_alias( + deprecation_start="4.2.0", + deprecation_end="5.0.0", + bustype="interface", + context="config_context", +) +def Bus( + channel: Optional[Channel] = None, + interface: Optional[str] = None, + config_context: Optional[str] = None, + ignore_config: bool = False, + **kwargs: Any, +) -> BusABC: + """Create a new bus instance with configuration loading. Instantiates a CAN Bus of the given ``interface``, falls back to reading a configuration file from default locations. @@ -99,45 +111,30 @@ class Bus(BusABC): # pylint: disable=abstract-method if the ``channel`` could not be determined """ - @staticmethod - @util.deprecated_args_alias( - deprecation_start="4.2.0", - deprecation_end="5.0.0", - bustype="interface", - context="config_context", - ) - def __new__( # type: ignore - cls: Any, - channel: Optional[Channel] = None, - interface: Optional[str] = None, - config_context: Optional[str] = None, - ignore_config: bool = False, - **kwargs: Any, - ) -> BusABC: - # figure out the rest of the configuration; this might raise an error - if interface is not None: - kwargs["interface"] = interface - if channel is not None: - kwargs["channel"] = channel - - if not ignore_config: - kwargs = util.load_config(config=kwargs, context=config_context) - - # resolve the bus class to use for that interface - cls = _get_class_for_interface(kwargs["interface"]) - - # remove the "interface" key, so it doesn't get passed to the backend - del kwargs["interface"] - - # make sure the bus can handle this config format - channel = kwargs.pop("channel", channel) - if channel is None: - # Use the default channel for the backend - bus = cls(**kwargs) - else: - bus = cls(channel, **kwargs) + # figure out the rest of the configuration; this might raise an error + if interface is not None: + kwargs["interface"] = interface + if channel is not None: + kwargs["channel"] = channel + + if not ignore_config: + kwargs = util.load_config(config=kwargs, context=config_context) + + # resolve the bus class to use for that interface + cls = _get_class_for_interface(kwargs["interface"]) + + # remove the "interface" key, so it doesn't get passed to the backend + del kwargs["interface"] + + # make sure the bus can handle this config format + channel = kwargs.pop("channel", channel) + if channel is None: + # Use the default channel for the backend + bus = cls(**kwargs) + else: + bus = cls(channel, **kwargs) - return cast(BusABC, bus) + return bus def detect_available_configs( @@ -146,7 +143,7 @@ def detect_available_configs( """Detect all configurations/channels that the interfaces could currently connect with. - This might be quite time consuming. + This might be quite time-consuming. Automated configuration detection may not be implemented by every interface on every platform. This method will not raise diff --git a/can/logger.py b/can/logger.py index 42312324a..56f9156a8 100644 --- a/can/logger.py +++ b/can/logger.py @@ -85,7 +85,7 @@ def _append_filter_argument( ) -def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus: +def _create_bus(parsed_args: Any, **kwargs: Any) -> can.BusABC: logging_level_names = ["critical", "error", "warning", "info", "debug", "subdebug"] can.set_logging_level(logging_level_names[min(5, parsed_args.verbosity)]) @@ -99,7 +99,7 @@ def _create_bus(parsed_args: Any, **kwargs: Any) -> can.Bus: if parsed_args.data_bitrate: config["data_bitrate"] = parsed_args.data_bitrate - return Bus(parsed_args.channel, **config) # type: ignore + return Bus(parsed_args.channel, **config) def _parse_filters(parsed_args: Any) -> CanFilters: diff --git a/can/util.py b/can/util.py index 2f6fb1957..59abdd579 100644 --- a/can/util.py +++ b/can/util.py @@ -24,6 +24,8 @@ cast, ) +from typing_extensions import ParamSpec + import can from . import typechecking @@ -325,9 +327,15 @@ def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]: return None -def deprecated_args_alias( # type: ignore - deprecation_start: str, deprecation_end: Optional[str] = None, **aliases -): +P1 = ParamSpec("P1") +T1 = TypeVar("T1") + + +def deprecated_args_alias( + deprecation_start: str, + deprecation_end: Optional[str] = None, + **aliases: Optional[str], +) -> Callable[[Callable[P1, T1]], Callable[P1, T1]]: """Allows to rename/deprecate a function kwarg(s) and optionally have the deprecated kwarg(s) set as alias(es) @@ -356,9 +364,9 @@ def library_function(new_arg): """ - def deco(f): + def deco(f: Callable[P1, T1]) -> Callable[P1, T1]: @functools.wraps(f) - def wrapper(*args, **kwargs): + def wrapper(*args: P1.args, **kwargs: P1.kwargs) -> T1: _rename_kwargs( func_name=f.__name__, start=deprecation_start, @@ -373,10 +381,42 @@ def wrapper(*args, **kwargs): return deco -T = TypeVar("T", BitTiming, BitTimingFd) +def _rename_kwargs( + func_name: str, + start: str, + end: Optional[str], + kwargs: P1.kwargs, + aliases: Dict[str, Optional[str]], +) -> None: + """Helper function for `deprecated_args_alias`""" + for alias, new in aliases.items(): + if alias in kwargs: + deprecation_notice = ( + f"The '{alias}' argument is deprecated since python-can v{start}" + ) + if end: + deprecation_notice += ( + f", and scheduled for removal in python-can v{end}" + ) + deprecation_notice += "." + + value = kwargs.pop(alias) + if new is not None: + deprecation_notice += f" Use '{new}' instead." + + if new in kwargs: + raise TypeError( + f"{func_name} received both '{alias}' (deprecated) and '{new}'." + ) + kwargs[new] = value + + warnings.warn(deprecation_notice, DeprecationWarning) + +T2 = TypeVar("T2", BitTiming, BitTimingFd) -def check_or_adjust_timing_clock(timing: T, valid_clocks: Iterable[int]) -> T: + +def check_or_adjust_timing_clock(timing: T2, valid_clocks: Iterable[int]) -> T2: """Adjusts the given timing instance to have an *f_clock* value that is within the allowed values specified by *valid_clocks*. If the *f_clock* value of timing is already within *valid_clocks*, then *timing* is returned unchanged. @@ -416,38 +456,6 @@ def check_or_adjust_timing_clock(timing: T, valid_clocks: Iterable[int]) -> T: ) from None -def _rename_kwargs( - func_name: str, - start: str, - end: Optional[str], - kwargs: Dict[str, str], - aliases: Dict[str, str], -) -> None: - """Helper function for `deprecated_args_alias`""" - for alias, new in aliases.items(): - if alias in kwargs: - deprecation_notice = ( - f"The '{alias}' argument is deprecated since python-can v{start}" - ) - if end: - deprecation_notice += ( - f", and scheduled for removal in python-can v{end}" - ) - deprecation_notice += "." - - value = kwargs.pop(alias) - if new is not None: - deprecation_notice += f" Use '{new}' instead." - - if new in kwargs: - raise TypeError( - f"{func_name} received both '{alias}' (deprecated) and '{new}'." - ) - kwargs[new] = value - - warnings.warn(deprecation_notice, DeprecationWarning) - - def time_perfcounter_correlation() -> Tuple[float, float]: """Get the `perf_counter` value nearest to when time.time() is updated diff --git a/doc/bus.rst b/doc/bus.rst index 51ed0220b..e21a9e5f1 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -3,10 +3,10 @@ Bus --- -The :class:`~can.Bus` provides a wrapper around a physical or virtual CAN Bus. +The :class:`~can.BusABC` class provides a wrapper around a physical or virtual CAN Bus. -An interface specific instance is created by instantiating the :class:`~can.Bus` -class with a particular ``interface``, for example:: +An interface specific instance is created by calling the :func:`~can.Bus` +function with a particular ``interface``, for example:: vector_bus = can.Bus(interface='vector', ...) @@ -77,13 +77,14 @@ See :meth:`~can.BusABC.set_filters` for the implementation. Bus API ''''''' -.. autoclass:: can.Bus +.. autofunction:: can.Bus + +.. autoclass:: can.BusABC :class-doc-from: class - :show-inheritance: :members: :inherited-members: -.. autoclass:: can.bus.BusState +.. autoclass:: can.BusState :members: :undoc-members: diff --git a/doc/conf.py b/doc/conf.py index aa61f243b..4b490ee29 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -126,7 +126,9 @@ ("py:class", "can.typechecking.CanFilter"), ("py:class", "can.typechecking.CanFilterExtended"), ("py:class", "can.typechecking.AutoDetectedConfig"), - ("py:class", "can.util.T"), + ("py:class", "can.util.T1"), + ("py:class", "can.util.T2"), + ("py:class", "~P1"), # intersphinx fails to reference some builtins ("py:class", "asyncio.events.AbstractEventLoop"), ("py:class", "_thread.allocate_lock"), diff --git a/doc/internal-api.rst b/doc/internal-api.rst index b8c108fb5..f4b6f875a 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -57,23 +57,32 @@ They **might** implement the following: and thus might not provide message filtering: -Concrete instances are usually created by :class:`can.Bus` which takes the users +Concrete instances are usually created by :func:`can.Bus` which takes the users configuration into account. Bus Internals ~~~~~~~~~~~~~ -Several methods are not documented in the main :class:`can.Bus` +Several methods are not documented in the main :class:`can.BusABC` as they are primarily useful for library developers as opposed to -library users. This is the entire ABC bus class with all internal -methods: +library users. -.. autoclass:: can.BusABC - :members: - :private-members: - :special-members: +.. automethod:: can.BusABC.__init__ + +.. automethod:: can.BusABC.__iter__ + +.. automethod:: can.BusABC.__str__ + +.. autoattribute:: can.BusABC.__weakref__ + +.. automethod:: can.BusABC._recv_internal + +.. automethod:: can.BusABC._apply_filters + +.. automethod:: can.BusABC._send_periodic_internal +.. automethod:: can.BusABC._detect_available_configs About the IO module From 09213b101adc1775b35dc57788d43902d6de85c9 Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Mon, 15 May 2023 16:21:47 +0200 Subject: [PATCH 1042/1235] Add `protocol` property to BusABC to determine active CAN Protocol (#1532) * Implement is_fd property for BusABC and PCANBus * Implement enum to represent CAN protocol * Implement CANProtocol for VirtualBus * Implement CANProtocol for UDPMulticastBus * Implement CANProtocol for the CANalystIIBus * Implement CANProtocol for the slcanBus * Rename CANProtocol to CanProtocol * Reimplement PcanBus.fd attribute as read-only property The property is scheduled for removal in v5.0 * Reimplement UdpMulticastBus.is_fd attribute as read-only property The property is superseded by BusABC.protocol and scheduled for removal in version 5.0. * Implement CanProtocol for robotellBus * Implement CanProtocol for NicanBus * Implement CanProtocol for IscanBus * Implement CanProtocol for CantactBus * Fix sphinx reference to CanProtocol * Implement CanProtocol for GsUsbBus * Implement CanProtocol for NiXNETcanBus * Implement CanProtocol for EtasBus * Implement CanProtocol for IXXATBus * Implement CanProtocol for KvaserBus * Implement CanProtocol for the SerialBus * Implement CanProtocol for UcanBus * Implement CanProtocol for VectorBus * Implement CanProtocol for NeousysBus * Implement CanProtocol for Usb2canBus * Implement CanProtocol for NeoViBus * Implement CanProtocol for SocketcanBus * Permit passthrough of protocol field for SocketCanDaemonBus * Implement CanProtocol for SeeedBus * Remove CanProtocol attribute from BusABC constructor The attribute is now set as class attribute with default value and can be overridden in the subclass constructor. * Apply suggestions from code review Fix property access and enum comparison Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Fix syntax error * Fix more enum comparisons against BusABC.protocol --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/__init__.py | 3 +- can/bus.py | 18 +++++++++ can/interfaces/canalystii.py | 15 ++++--- can/interfaces/cantact.py | 8 +++- can/interfaces/etas/__init__.py | 8 ++-- can/interfaces/gs_usb.py | 7 +++- can/interfaces/ics_neovi/neovi_bus.py | 13 ++++-- can/interfaces/iscan.py | 7 +++- can/interfaces/ixxat/canlib.py | 3 ++ can/interfaces/ixxat/canlib_vcinpl.py | 3 +- can/interfaces/ixxat/canlib_vcinpl2.py | 10 ++--- can/interfaces/kvaser/canlib.py | 13 ++++-- can/interfaces/neousys/neousys.py | 8 ++-- can/interfaces/nican.py | 8 ++-- can/interfaces/nixnet.py | 19 +++++++-- can/interfaces/pcan/pcan.py | 29 +++++++++++--- can/interfaces/robotell.py | 3 +- can/interfaces/seeedstudio/seeedstudio.py | 4 +- can/interfaces/serial/serial_can.py | 2 + can/interfaces/slcan.py | 9 ++++- can/interfaces/socketcan/socketcan.py | 9 ++++- can/interfaces/socketcand/socketcand.py | 2 +- can/interfaces/systec/ucanbus.py | 19 +++++++-- can/interfaces/udp_multicast/bus.py | 34 +++++++++++----- can/interfaces/usb2can/usb2canInterface.py | 3 +- can/interfaces/vector/canlib.py | 26 +++++++++--- can/interfaces/virtual.py | 46 +++++++++++++++++++--- doc/bus.rst | 4 ++ test/serial_test.py | 3 ++ test/test_cantact.py | 5 +++ test/test_interface_canalystii.py | 7 ++++ test/test_kvaser.py | 7 +++- test/test_neousys.py | 3 ++ test/test_pcan.py | 15 +++++-- test/test_robotell.py | 3 ++ test/test_socketcan.py | 12 ++++++ test/test_systec.py | 2 + test/test_vector.py | 18 +++++++++ 38 files changed, 327 insertions(+), 81 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index a14228f62..a6691eecb 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -25,6 +25,7 @@ "CanInitializationError", "CanInterfaceNotImplementedError", "CanOperationError", + "CanProtocol", "CanTimeoutError", "CanutilsLogReader", "CanutilsLogWriter", @@ -88,7 +89,7 @@ ModifiableCyclicTaskABC, RestartableCyclicTaskABC, ) -from .bus import BusABC, BusState +from .bus import BusABC, BusState, CanProtocol from .exceptions import ( CanError, CanInitializationError, diff --git a/can/bus.py b/can/bus.py index 5bd2d4e30..b7a54dbb1 100644 --- a/can/bus.py +++ b/can/bus.py @@ -26,6 +26,14 @@ class BusState(Enum): ERROR = auto() +class CanProtocol(Enum): + """The CAN protocol type supported by a :class:`can.BusABC` instance""" + + CAN_20 = auto() + CAN_FD = auto() + CAN_XL = auto() + + class BusABC(metaclass=ABCMeta): """The CAN Bus Abstract Base Class that serves as the basis for all concrete interfaces. @@ -44,6 +52,7 @@ class BusABC(metaclass=ABCMeta): RECV_LOGGING_LEVEL = 9 _is_shutdown: bool = False + _can_protocol: CanProtocol = CanProtocol.CAN_20 @abstractmethod def __init__( @@ -459,6 +468,15 @@ def state(self, new_state: BusState) -> None: """ raise NotImplementedError("Property is not implemented.") + @property + def protocol(self) -> CanProtocol: + """Return the CAN protocol used by this bus instance. + + This value is set at initialization time and does not change + during the lifetime of a bus instance. + """ + return self._can_protocol + @staticmethod def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: """Detect all configurations/channels that this interface could diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index dd65f5ba0..2fef19497 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -6,7 +6,7 @@ import canalystii as driver -from can import BitTiming, BitTimingFd, BusABC, Message +from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message from can.exceptions import CanTimeoutError from can.typechecking import CanFilters from can.util import check_or_adjust_timing_clock, deprecated_args_alias @@ -54,8 +54,11 @@ def __init__( raise ValueError("Either bitrate or timing argument is required") # Do this after the error handling - super().__init__(channel=channel, can_filters=can_filters, **kwargs) - + super().__init__( + channel=channel, + can_filters=can_filters, + **kwargs, + ) if isinstance(channel, str): # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(",")] @@ -64,11 +67,11 @@ def __init__( else: # Sequence[int] self.channels = list(channel) - self.rx_queue: Deque[Tuple[int, driver.Message]] = deque(maxlen=rx_queue_size) - self.channel_info = f"CANalyst-II: device {device}, channels {self.channels}" - + self.rx_queue: Deque[Tuple[int, driver.Message]] = deque(maxlen=rx_queue_size) self.device = driver.CanalystDevice(device_index=device) + self._can_protocol = CanProtocol.CAN_20 + for single_channel in self.channels: if isinstance(timing, BitTiming): timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000]) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 75e1adbaa..963a9ee3b 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -7,7 +7,7 @@ from typing import Any, Optional, Union from unittest.mock import Mock -from can import BitTiming, BitTimingFd, BusABC, Message +from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message from ..exceptions import ( CanInitializationError, @@ -87,6 +87,7 @@ def __init__( self.channel = int(channel) self.channel_info = f"CANtact: ch:{channel}" + self._can_protocol = CanProtocol.CAN_20 # Configure the interface with error_check("Cannot setup the cantact.Interface", CanInitializationError): @@ -114,7 +115,10 @@ def __init__( self.interface.start() super().__init__( - channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs + channel=channel, + bitrate=bitrate, + poll_interval=poll_interval, + **kwargs, ) def _recv_internal(self, timeout): diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 5f768b3e5..62e060f48 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -1,4 +1,3 @@ -import ctypes import time from typing import Dict, List, Optional, Tuple @@ -17,9 +16,12 @@ def __init__( bitrate: int = 1000000, fd: bool = True, data_bitrate: int = 2000000, - **kwargs: object, + **kwargs: Dict[str, any], ): + super().__init__(channel=channel, **kwargs) + self.receive_own_messages = receive_own_messages + self._can_protocol = can.CanProtocol.CAN_FD if fd else can.CanProtocol.CAN_20 nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX) self.tree = ctypes.POINTER(CSI_Tree)() @@ -297,7 +299,7 @@ def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: tree = ctypes.POINTER(CSI_Tree)() CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(tree)) - nodes: Dict[str, str] = [] + nodes: List[Dict[str, str]] = [] def _findNodes(tree, prefix): uri = f"{prefix}/{tree.contents.item.uriName.decode()}" diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index fb5ce1d80..32ad54e75 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -52,11 +52,16 @@ def __init__( self.gs_usb = gs_usb self.channel_info = channel + self._can_protocol = can.CanProtocol.CAN_20 self.gs_usb.set_bitrate(bitrate) self.gs_usb.start() - super().__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__( + channel=channel, + can_filters=can_filters, + **kwargs, + ) def send(self, msg: can.Message, timeout: Optional[float] = None): """Transmit a message to the CAN bus. diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 848cefcc8..f2dffe0a6 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -16,7 +16,7 @@ from threading import Event from warnings import warn -from can import BusABC, Message +from can import BusABC, CanProtocol, Message from ...exceptions import ( CanError, @@ -169,7 +169,11 @@ def __init__(self, channel, can_filters=None, **kwargs): if ics is None: raise ImportError("Please install python-ics") - super().__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__( + channel=channel, + can_filters=can_filters, + **kwargs, + ) logger.info(f"CAN Filters: {can_filters}") logger.info(f"Got configuration of: {kwargs}") @@ -190,6 +194,9 @@ def __init__(self, channel, can_filters=None, **kwargs): serial = kwargs.get("serial") self.dev = self._find_device(type_filter, serial) + is_fd = kwargs.get("fd", False) + self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20 + with open_lock: ics.open_device(self.dev) @@ -198,7 +205,7 @@ def __init__(self, channel, can_filters=None, **kwargs): for channel in self.channels: ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel) - if kwargs.get("fd", False): + if is_fd: if "data_bitrate" in kwargs: for channel in self.channels: ics.set_fd_bit_rate( diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index ef3a48215..be0b0dae8 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -13,6 +13,7 @@ CanInitializationError, CanInterfaceNotImplementedError, CanOperationError, + CanProtocol, Message, ) @@ -99,6 +100,7 @@ def __init__( self.channel = ctypes.c_ubyte(int(channel)) self.channel_info = f"IS-CAN: {self.channel}" + self._can_protocol = CanProtocol.CAN_20 if bitrate not in self.BAUDRATES: raise ValueError(f"Invalid bitrate, choose one of {set(self.BAUDRATES)}") @@ -107,7 +109,10 @@ def __init__( iscan.isCAN_DeviceInitEx(self.channel, self.BAUDRATES[bitrate]) super().__init__( - channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs + channel=channel, + bitrate=bitrate, + poll_interval=poll_interval, + **kwargs, ) def _recv_internal( diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 3db719f96..b28c93541 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -131,6 +131,9 @@ def __init__( **kwargs ) + super().__init__(channel=channel, **kwargs) + self._can_protocol = self.bus.protocol + def flush_tx_buffer(self): """Flushes the transmit buffer on the IXXAT""" return self.bus.flush_tx_buffer() diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 550484f3e..5a366cc30 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -15,7 +15,7 @@ import sys from typing import Callable, Optional, Tuple -from can import BusABC, Message +from can import BusABC, CanProtocol, Message from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC, @@ -490,6 +490,7 @@ def __init__( self._channel_capabilities = structures.CANCAPABILITIES() self._message = structures.CANMSG() self._payload = (ctypes.c_byte * 8)() + self._can_protocol = CanProtocol.CAN_20 # Search for supplied device if unique_hardware_id is None: diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 3e7f2ff91..446b3e35c 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -15,8 +15,7 @@ import sys from typing import Callable, Optional, Tuple -import can.util -from can import BusABC, Message +from can import BusABC, CanProtocol, Message from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC, @@ -24,7 +23,7 @@ from can.ctypesutil import HANDLE, PHANDLE, CLibrary from can.ctypesutil import HRESULT as ctypes_HRESULT from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError -from can.util import deprecated_args_alias +from can.util import deprecated_args_alias, dlc2len, len2dlc from . import constants, structures from .exceptions import * @@ -536,6 +535,7 @@ def __init__( self._channel_capabilities = structures.CANCAPABILITIES2() self._message = structures.CANMSG2() self._payload = (ctypes.c_byte * 64)() + self._can_protocol = CanProtocol.CAN_FD # Search for supplied device if unique_hardware_id is None: @@ -865,7 +865,7 @@ def _recv_internal(self, timeout): # Timed out / can message type is not DATA return None, True - data_len = can.util.dlc2len(self._message.uMsgInfo.Bits.dlc) + data_len = dlc2len(self._message.uMsgInfo.Bits.dlc) # The _message.dwTime is a 32bit tick value and will overrun, # so expect to see the value restarting from 0 rx_msg = Message( @@ -915,7 +915,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: message.uMsgInfo.Bits.edl = 1 if msg.is_fd else 0 message.dwMsgId = msg.arbitration_id if msg.dlc: # this dlc means number of bytes of payload - message.uMsgInfo.Bits.dlc = can.util.len2dlc(msg.dlc) + message.uMsgInfo.Bits.dlc = len2dlc(msg.dlc) data_len_dif = msg.dlc - len(msg.data) data = msg.data + bytearray( [0] * data_len_dif diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index f6b92ccef..32d28059a 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -11,7 +11,7 @@ import sys import time -from can import BusABC, Message +from can import BusABC, CanProtocol, Message from can.util import time_perfcounter_correlation from ...exceptions import CanError, CanInitializationError, CanOperationError @@ -428,11 +428,12 @@ def __init__(self, channel, can_filters=None, **kwargs): channel = int(channel) except ValueError: raise ValueError("channel must be an integer") - self.channel = channel - log.debug("Initialising bus instance") + self.channel = channel self.single_handle = single_handle + self._can_protocol = CanProtocol.CAN_FD if fd else CanProtocol.CAN_20 + log.debug("Initialising bus instance") num_channels = ctypes.c_int(0) canGetNumberOfChannels(ctypes.byref(num_channels)) num_channels = int(num_channels.value) @@ -520,7 +521,11 @@ def __init__(self, channel, can_filters=None, **kwargs): self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) self._is_filtered = False - super().__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__( + channel=channel, + can_filters=can_filters, + **kwargs, + ) def _apply_filters(self, filters): if filters and len(filters) == 1: diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index d57234ddd..b7dd2117c 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -34,12 +34,13 @@ except ImportError: from ctypes import CDLL -from can import BusABC, Message - -from ...exceptions import ( +from can import ( + BusABC, CanInitializationError, CanInterfaceNotImplementedError, CanOperationError, + CanProtocol, + Message, ) logger = logging.getLogger(__name__) @@ -150,6 +151,7 @@ def __init__(self, channel, device=0, bitrate=500000, **kwargs): self.channel = channel self.device = device self.channel_info = f"Neousys Can: device {self.device}, channel {self.channel}" + self._can_protocol = CanProtocol.CAN_20 self.queue = queue.Queue() diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 8457a1a38..f4b1a37f0 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -19,13 +19,14 @@ from typing import Optional, Tuple, Type import can.typechecking -from can import BusABC, Message - -from ..exceptions import ( +from can import ( + BusABC, CanError, CanInitializationError, CanInterfaceNotImplementedError, CanOperationError, + CanProtocol, + Message, ) logger = logging.getLogger(__name__) @@ -219,6 +220,7 @@ def __init__( self.channel = channel self.channel_info = f"NI-CAN: {channel}" + self._can_protocol = CanProtocol.CAN_20 channel_bytes = channel.encode("ascii") config = [(NC_ATTR_START_ON_OPEN, True), (NC_ATTR_LOG_COMM_ERRS, log_errors)] diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index 4ddb52455..ba665442e 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -11,12 +11,13 @@ import logging import os import time +import warnings from queue import SimpleQueue from types import ModuleType from typing import Any, List, Optional, Tuple, Union import can.typechecking -from can import BitTiming, BitTimingFd, BusABC, Message +from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message from can.exceptions import ( CanInitializationError, CanInterfaceNotImplementedError, @@ -103,10 +104,11 @@ def __init__( self.poll_interval = poll_interval - self.fd = isinstance(timing, BitTimingFd) if timing else fd + is_fd = isinstance(timing, BitTimingFd) if timing else fd + self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20 # Set database for the initialization - database_name = ":can_fd_brs:" if self.fd else ":memory:" + database_name = ":can_fd_brs:" if is_fd else ":memory:" try: # We need two sessions for this application, @@ -158,7 +160,7 @@ def __init__( if bitrate: self._interface.baud_rate = bitrate - if self.fd: + if is_fd: # See page 951 of NI-XNET Hardware and Software Manual # to set custom can configuration self._interface.can_fd_baud_rate = fd_bitrate or bitrate @@ -188,6 +190,15 @@ def __init__( **kwargs, ) + @property + def fd(self) -> bool: + warnings.warn( + "The NiXNETcanBus.fd property is deprecated and superseded by " + "BusABC.protocol. It is scheduled for removal in version 5.0.", + DeprecationWarning, + ) + return self._can_protocol is CanProtocol.CAN_FD + def _recv_internal( self, timeout: Optional[float] ) -> Tuple[Optional[Message], bool]: diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 61d19d1b4..a9b2c016b 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -4,6 +4,7 @@ import logging import platform import time +import warnings from datetime import datetime from typing import Any, List, Optional, Tuple, Union @@ -17,6 +18,7 @@ CanError, CanInitializationError, CanOperationError, + CanProtocol, Message, ) from can.util import check_or_adjust_timing_clock, dlc2len, len2dlc @@ -244,8 +246,9 @@ def __init__( err_msg = f"Cannot find a channel with ID {device_id:08x}" raise ValueError(err_msg) + is_fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False) + self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20 self.channel_info = str(channel) - self.fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False) hwtype = PCAN_TYPE_ISA ioport = 0x02A0 @@ -269,7 +272,7 @@ def __init__( result = self.m_objPCANBasic.Initialize( self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt ) - elif self.fd: + elif is_fd: if isinstance(timing, BitTimingFd): timing = check_or_adjust_timing_clock( timing, sorted(VALID_PCAN_FD_CLOCKS, reverse=True) @@ -336,7 +339,12 @@ def __init__( if result != PCAN_ERROR_OK: raise PcanCanInitializationError(self._get_formatted_error(result)) - super().__init__(channel=channel, state=state, bitrate=bitrate, **kwargs) + super().__init__( + channel=channel, + state=state, + bitrate=bitrate, + **kwargs, + ) def _find_channel_by_dev_id(self, device_id): """ @@ -482,7 +490,7 @@ def _recv_internal( end_time = time.time() + timeout if timeout is not None else None while True: - if self.fd: + if self._can_protocol is CanProtocol.CAN_FD: result, pcan_msg, pcan_timestamp = self.m_objPCANBasic.ReadFD( self.m_PcanHandle ) @@ -544,7 +552,7 @@ def _recv_internal( error_state_indicator = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ESI.value) is_error_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value) - if self.fd: + if self._can_protocol is CanProtocol.CAN_FD: dlc = dlc2len(pcan_msg.DLC) timestamp = boottimeEpoch + (pcan_timestamp.value / (1000.0 * 1000.0)) else: @@ -590,7 +598,7 @@ def send(self, msg, timeout=None): if msg.error_state_indicator: msgType |= PCAN_MESSAGE_ESI.value - if self.fd: + if self._can_protocol is CanProtocol.CAN_FD: # create a TPCANMsg message structure CANMsg = TPCANMsgFD() @@ -649,6 +657,15 @@ def shutdown(self): self.m_objPCANBasic.Uninitialize(self.m_PcanHandle) + @property + def fd(self) -> bool: + warnings.warn( + "The PcanBus.fd property is deprecated and superseded by BusABC.protocol. " + "It is scheduled for removal in version 5.0.", + DeprecationWarning, + ) + return self._can_protocol is CanProtocol.CAN_FD + @property def state(self): return self._state diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 4d038a38a..bfe8f5774 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -7,7 +7,7 @@ import time from typing import Optional -from can import BusABC, Message +from can import BusABC, CanProtocol, Message from ..exceptions import CanInterfaceNotImplementedError, CanOperationError @@ -92,6 +92,7 @@ def __init__( if bitrate is not None: self.set_bitrate(bitrate) + self._can_protocol = CanProtocol.CAN_20 self.channel_info = ( f"Robotell USB-CAN s/n {self.get_serial_number(1)} on {channel}" ) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 7d7a1e687..0540c78be 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -12,7 +12,7 @@ from time import time import can -from can import BusABC, Message +from can import BusABC, CanProtocol, Message logger = logging.getLogger("seeedbus") @@ -100,6 +100,8 @@ def __init__( self.op_mode = operation_mode self.filter_id = bytearray([0x00, 0x00, 0x00, 0x00]) self.mask_id = bytearray([0x00, 0x00, 0x00, 0x00]) + self._can_protocol = CanProtocol.CAN_20 + if not channel: raise can.CanInitializationError("Must specify a serial port.") diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index eb336feba..9de2da99c 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -17,6 +17,7 @@ CanInitializationError, CanInterfaceNotImplementedError, CanOperationError, + CanProtocol, CanTimeoutError, Message, ) @@ -88,6 +89,7 @@ def __init__( raise TypeError("Must specify a serial port.") self.channel_info = f"Serial interface: {channel}" + self._can_protocol = CanProtocol.CAN_20 try: self._ser = serial.serial_for_url( diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 21306f28f..7ff12ce44 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -7,7 +7,7 @@ import time from typing import Any, Optional, Tuple -from can import BusABC, Message, typechecking +from can import BusABC, CanProtocol, Message, typechecking from ..exceptions import ( CanInitializationError, @@ -105,6 +105,7 @@ def __init__( ) self._buffer = bytearray() + self._can_protocol = CanProtocol.CAN_20 time.sleep(sleep_after_open) @@ -118,7 +119,11 @@ def __init__( self.open() super().__init__( - channel, ttyBaudrate=115200, bitrate=None, rtscts=False, **kwargs + channel, + ttyBaudrate=115200, + bitrate=None, + rtscts=False, + **kwargs, ) def set_bitrate(self, bitrate: int) -> None: diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index bdf39f0ab..a3f74bb82 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -30,7 +30,7 @@ import can -from can import BusABC, Message +from can import BusABC, CanProtocol, Message from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, @@ -656,6 +656,7 @@ def __init__( self._is_filtered = False self._task_id = 0 self._task_id_guard = threading.Lock() + self._can_protocol = CanProtocol.CAN_FD if fd else CanProtocol.CAN_20 # set the local_loopback parameter try: @@ -710,7 +711,11 @@ def __init__( "local_loopback": local_loopback, } ) - super().__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__( + channel=channel, + can_filters=can_filters, + **kwargs, + ) def shutdown(self) -> None: """Stops all active periodic tasks and closes the socket.""" diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 0c6c06ccf..183a9ba12 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -93,7 +93,7 @@ def __init__(self, channel, host, port, can_filters=None, **kwargs): self._expect_msg("< ok >") self._tcp_send(f"< rawmode >") self._expect_msg("< ok >") - super().__init__(channel=channel, can_filters=can_filters) + super().__init__(channel=channel, can_filters=can_filters, **kwargs) def _recv_internal(self, timeout): if len(self.__message_buffer) != 0: diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index da05b38b1..cb0dcc39e 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -1,9 +1,16 @@ import logging from threading import Event -from can import BusABC, BusState, Message +from can import ( + BusABC, + BusState, + CanError, + CanInitializationError, + CanOperationError, + CanProtocol, + Message, +) -from ...exceptions import CanError, CanInitializationError, CanOperationError from .constants import * from .exceptions import UcanException from .structures import * @@ -104,6 +111,8 @@ def __init__(self, channel, can_filters=None, **kwargs): ) from exception self.channel = int(channel) + self._can_protocol = CanProtocol.CAN_20 + device_number = int(kwargs.get("device_number", ANY_MODULE)) # configuration options @@ -145,7 +154,11 @@ def __init__(self, channel, can_filters=None, **kwargs): self._is_filtered = False - super().__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__( + channel=channel, + can_filters=can_filters, + **kwargs, + ) def _recv_internal(self, timeout): try: diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 00cbd32c8..089b8182f 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -3,21 +3,23 @@ import select import socket import struct +import warnings +from typing import List, Optional, Tuple, Union + +import can +from can import BusABC, CanProtocol +from can.typechecking import AutoDetectedConfig + +from .utils import check_msgpack_installed, pack_message, unpack_message try: from fcntl import ioctl except ModuleNotFoundError: # Missing on Windows pass -from typing import List, Optional, Tuple, Union log = logging.getLogger(__name__) -import can -from can import BusABC -from can.typechecking import AutoDetectedConfig - -from .utils import check_msgpack_installed, pack_message, unpack_message # see socket.getaddrinfo() IPv4_ADDRESS_INFO = Tuple[str, int] # address, port @@ -102,10 +104,22 @@ def __init__( "receiving own messages is not yet implemented" ) - super().__init__(channel, **kwargs) + super().__init__( + channel, + **kwargs, + ) - self.is_fd = fd self._multicast = GeneralPurposeUdpMulticastBus(channel, port, hop_limit) + self._can_protocol = CanProtocol.CAN_FD if fd else CanProtocol.CAN_20 + + @property + def is_fd(self) -> bool: + warnings.warn( + "The UdpMulticastBus.is_fd property is deprecated and superseded by " + "BusABC.protocol. It is scheduled for removal in version 5.0.", + DeprecationWarning, + ) + return self._can_protocol is CanProtocol.CAN_FD def _recv_internal(self, timeout: Optional[float]): result = self._multicast.recv(timeout) @@ -122,13 +136,13 @@ def _recv_internal(self, timeout: Optional[float]): "could not unpack received message" ) from exception - if not self.is_fd and can_message.is_fd: + if self._can_protocol is not CanProtocol.CAN_FD and can_message.is_fd: return None, False return can_message, False def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: - if not self.is_fd and msg.is_fd: + if self._can_protocol is not CanProtocol.CAN_FD and msg.is_fd: raise can.CanOperationError( "cannot send FD message over bus with CAN FD disabled" ) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 2c0a0d00f..c89e394df 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -6,7 +6,7 @@ from ctypes import byref from typing import Optional -from can import BusABC, CanInitializationError, CanOperationError, Message +from can import BusABC, CanInitializationError, CanOperationError, CanProtocol, Message from .serial_selector import find_serial_devices from .usb2canabstractionlayer import ( @@ -118,6 +118,7 @@ def __init__( baudrate = min(int(bitrate // 1000), 1000) self.channel_info = f"USB2CAN device {device_id}" + self._can_protocol = CanProtocol.CAN_20 connector = f"{device_id}; {baudrate}" self.handle = self.can.open(connector, flags) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 03a3d9b1f..f852fb0ec 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -11,6 +11,7 @@ import logging import os import time +import warnings from types import ModuleType from typing import ( Any, @@ -44,6 +45,7 @@ BusABC, CanInitializationError, CanInterfaceNotImplementedError, + CanProtocol, Message, ) from can.typechecking import AutoDetectedConfig, CanFilters @@ -202,11 +204,12 @@ def __init__( ) channel_configs = get_channel_configs() + is_fd = isinstance(timing, BitTimingFd) if timing else fd self.mask = 0 - self.fd = isinstance(timing, BitTimingFd) if timing else fd self.channel_masks: Dict[int, int] = {} self.index_to_channel: Dict[int, int] = {} + self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20 for channel in self.channels: channel_index = self._find_global_channel_idx( @@ -229,7 +232,7 @@ def __init__( interface_version = ( xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 - if self.fd + if is_fd else xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION ) @@ -324,7 +327,20 @@ def __init__( self._time_offset = 0.0 self._is_filtered = False - super().__init__(channel=channel, can_filters=can_filters, **kwargs) + super().__init__( + channel=channel, + can_filters=can_filters, + **kwargs, + ) + + @property + def fd(self) -> bool: + warnings.warn( + "The VectorBus.fd property is deprecated and superseded by " + "BusABC.protocol. It is scheduled for removal in version 5.0.", + DeprecationWarning, + ) + return self._can_protocol is CanProtocol.CAN_FD def _find_global_channel_idx( self, @@ -647,7 +663,7 @@ def _recv_internal( while True: try: - if self.fd: + if self._can_protocol is CanProtocol.CAN_FD: msg = self._recv_canfd() else: msg = self._recv_can() @@ -781,7 +797,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: def _send_sequence(self, msgs: Sequence[Message]) -> int: """Send messages and return number of successful transmissions.""" - if self.fd: + if self._can_protocol is CanProtocol.CAN_FD: return self._send_can_fd_msg_sequence(msgs) else: return self._send_can_msg_sequence(msgs) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 3eaefc230..62ad0cfe3 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -15,7 +15,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple from can import CanOperationError -from can.bus import BusABC +from can.bus import BusABC, CanProtocol from can.message import Message from can.typechecking import AutoDetectedConfig @@ -33,7 +33,8 @@ class VirtualBus(BusABC): """ - A virtual CAN bus using an internal message queue. It can be used for example for testing. + A virtual CAN bus using an internal message queue. It can be used for + example for testing. In this interface, a channel is an arbitrary object used as an identifier for connected buses. @@ -48,9 +49,11 @@ class VirtualBus(BusABC): if a message is sent to 5 receivers with the timeout set to 1.0. .. warning:: - This interface guarantees reliable delivery and message ordering, but does *not* implement rate - limiting or ID arbitration/prioritization under high loads. Please refer to the section - :ref:`virtual_interfaces_doc` for more information on this and a comparison to alternatives. + This interface guarantees reliable delivery and message ordering, but + does *not* implement rate limiting or ID arbitration/prioritization + under high loads. Please refer to the section + :ref:`virtual_interfaces_doc` for more information on this and a + comparison to alternatives. """ def __init__( @@ -59,14 +62,45 @@ def __init__( receive_own_messages: bool = False, rx_queue_size: int = 0, preserve_timestamps: bool = False, + protocol: CanProtocol = CanProtocol.CAN_20, **kwargs: Any, ) -> None: + """ + The constructed instance has access to the bus identified by the + channel parameter. It is able to see all messages transmitted on the + bus by virtual instances constructed with the same channel identifier. + + :param channel: The channel identifier. This parameter can be an + arbitrary value. The bus instance will be able to see messages + from other virtual bus instances that were created with the same + value. + :param receive_own_messages: If set to True, sent messages will be + reflected back on the input queue. + :param rx_queue_size: The size of the reception queue. The reception + queue stores messages until they are read. If the queue reaches + its capacity, it will start dropping the oldest messages to make + room for new ones. If set to 0, the queue has an infinite capacity. + Be aware that this can cause memory leaks if messages are read + with a lower frequency than they arrive on the bus. + :param preserve_timestamps: If set to True, messages transmitted via + :func:`~can.BusABC.send` will keep the timestamp set in the + :class:`~can.Message` instance. Otherwise, the timestamp value + will be replaced with the current system time. + :param protocol: The protocol implemented by this bus instance. The + value does not affect the operation of the bus instance and can + be set to an arbitrary value for testing purposes. + :param kwargs: Additional keyword arguments passed to the parent + constructor. + """ super().__init__( - channel=channel, receive_own_messages=receive_own_messages, **kwargs + channel=channel, + receive_own_messages=receive_own_messages, + **kwargs, ) # the channel identifier may be an arbitrary object self.channel_id = channel + self._can_protocol = protocol self.channel_info = f"Virtual bus channel {self.channel_id}" self.receive_own_messages = receive_own_messages self.preserve_timestamps = preserve_timestamps diff --git a/doc/bus.rst b/doc/bus.rst index e21a9e5f1..f63c244c2 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -88,6 +88,10 @@ Bus API :members: :undoc-members: +.. autoclass:: can.bus.CanProtocol + :members: + :undoc-members: + Thread safe bus ''''''''''''''' diff --git a/test/serial_test.py b/test/serial_test.py index d020d7232..5fa90704b 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -50,6 +50,9 @@ def __init__(self): self, allowed_timestamp_delta=None, preserves_channel=True ) + def test_can_protocol(self): + self.assertEqual(self.bus.protocol, can.CanProtocol.CAN_20) + def test_rx_tx_min_max_data(self): """ Tests the transfer from 0x00 to 0xFF for a 1 byte payload diff --git a/test/test_cantact.py b/test/test_cantact.py index 2cc3e479c..f90655ae5 100644 --- a/test/test_cantact.py +++ b/test/test_cantact.py @@ -14,6 +14,8 @@ class CantactTest(unittest.TestCase): def test_bus_creation(self): bus = can.Bus(channel=0, interface="cantact", _testing=True) self.assertIsInstance(bus, cantact.CantactBus) + self.assertEqual(bus.protocol, can.CanProtocol.CAN_20) + cantact.MockInterface.set_bitrate.assert_called() cantact.MockInterface.set_bit_timing.assert_not_called() cantact.MockInterface.set_enabled.assert_called() @@ -25,7 +27,10 @@ def test_bus_creation_bittiming(self): bt = can.BitTiming(f_clock=24_000_000, brp=3, tseg1=13, tseg2=2, sjw=1) bus = can.Bus(channel=0, interface="cantact", timing=bt, _testing=True) + self.assertIsInstance(bus, cantact.CantactBus) + self.assertEqual(bus.protocol, can.CanProtocol.CAN_20) + cantact.MockInterface.set_bitrate.assert_not_called() cantact.MockInterface.set_bit_timing.assert_called() cantact.MockInterface.set_enabled.assert_called() diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py index 0a87f40f9..65d9ee74b 100755 --- a/test/test_interface_canalystii.py +++ b/test/test_interface_canalystii.py @@ -22,6 +22,9 @@ def test_initialize_from_constructor(self): with create_mock_device() as mock_device: instance = mock_device.return_value bus = CANalystIIBus(bitrate=1000000) + + self.assertEqual(bus.protocol, can.CanProtocol.CAN_20) + instance.init.assert_has_calls( [ call(0, bitrate=1000000), @@ -34,6 +37,8 @@ def test_initialize_single_channel_only(self): with create_mock_device() as mock_device: instance = mock_device.return_value bus = CANalystIIBus(channel, bitrate=1000000) + + self.assertEqual(bus.protocol, can.CanProtocol.CAN_20) instance.init.assert_called_once_with(channel, bitrate=1000000) def test_initialize_with_timing_registers(self): @@ -43,6 +48,8 @@ def test_initialize_with_timing_registers(self): f_clock=8_000_000, btr0=0x03, btr1=0x6F ) bus = CANalystIIBus(bitrate=None, timing=timing) + self.assertEqual(bus.protocol, can.CanProtocol.CAN_20) + instance.init.assert_has_calls( [ call(0, timing0=0x03, timing1=0x6F), diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 6e7ccea38..043f86f8c 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -45,6 +45,7 @@ def tearDown(self): def test_bus_creation(self): self.assertIsInstance(self.bus, canlib.KvaserBus) + self.assertEqual(self.bus.protocol, can.CanProtocol.CAN_20) self.assertTrue(canlib.canOpenChannel.called) self.assertTrue(canlib.canBusOn.called) @@ -148,7 +149,8 @@ def test_available_configs(self): def test_canfd_default_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() - can.Bus(channel=0, interface="kvaser", fd=True) + bus = can.Bus(channel=0, interface="kvaser", fd=True) + self.assertEqual(bus.protocol, can.CanProtocol.CAN_FD) canlib.canSetBusParams.assert_called_once_with( 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 ) @@ -160,7 +162,8 @@ def test_canfd_nondefault_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() data_bitrate = 2000000 - can.Bus(channel=0, interface="kvaser", fd=True, data_bitrate=data_bitrate) + bus = can.Bus(channel=0, interface="kvaser", fd=True, data_bitrate=data_bitrate) + self.assertEqual(bus.protocol, can.CanProtocol.CAN_FD) bitrate_constant = canlib.BITRATE_FD[data_bitrate] canlib.canSetBusParams.assert_called_once_with( 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0, 0, 0 diff --git a/test/test_neousys.py b/test/test_neousys.py index 080278d13..69c818869 100644 --- a/test/test_neousys.py +++ b/test/test_neousys.py @@ -36,6 +36,7 @@ def tearDown(self) -> None: def test_bus_creation(self) -> None: self.assertIsInstance(self.bus, neousys.NeousysBus) + self.assertEqual(self.bus.protocol, can.CanProtocol.CAN_20) neousys.NEOUSYS_CANLIB.CAN_Setup.assert_called() neousys.NEOUSYS_CANLIB.CAN_Start.assert_called() neousys.NEOUSYS_CANLIB.CAN_RegisterReceived.assert_called() @@ -62,6 +63,8 @@ def test_bus_creation(self) -> None: def test_bus_creation_bitrate(self) -> None: self.bus = can.Bus(channel=0, interface="neousys", bitrate=200000) self.assertIsInstance(self.bus, neousys.NeousysBus) + self.assertEqual(self.bus.protocol, can.CanProtocol.CAN_20) + CAN_Start_args = ( can.interfaces.neousys.neousys.NEOUSYS_CANLIB.CAN_Setup.call_args[0] ) diff --git a/test/test_pcan.py b/test/test_pcan.py index 93a5f5ff4..19fa44dc7 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -11,7 +11,7 @@ from parameterized import parameterized import can -from can.bus import BusState +from can import BusState, CanProtocol from can.exceptions import CanInitializationError from can.interfaces.pcan import PcanBus, PcanError from can.interfaces.pcan.basic import * @@ -52,8 +52,10 @@ def test_bus_creation(self) -> None: self.bus = can.Bus(interface="pcan") self.assertIsInstance(self.bus, PcanBus) - self.MockPCANBasic.assert_called_once() + self.assertEqual(self.bus.protocol, CanProtocol.CAN_20) + self.assertFalse(self.bus.fd) + self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_called_once() self.mock_pcan.InitializeFD.assert_not_called() @@ -79,6 +81,9 @@ def test_bus_creation_fd(self, clock_param: str, clock_val: int) -> None: ) self.assertIsInstance(self.bus, PcanBus) + self.assertEqual(self.bus.protocol, CanProtocol.CAN_FD) + self.assertTrue(self.bus.fd) + self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_not_called() self.mock_pcan.InitializeFD.assert_called_once() @@ -451,10 +456,11 @@ def test_peak_fd_bus_constructor_regression(self): def test_constructor_bit_timing(self): timing = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x47, btr1=0x2F) - can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing) + bus = can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing) bitrate_arg = self.mock_pcan.Initialize.call_args[0][1] self.assertEqual(bitrate_arg.value, 0x472F) + self.assertEqual(bus.protocol, CanProtocol.CAN_20) def test_constructor_bit_timing_fd(self): timing = can.BitTimingFd( @@ -468,7 +474,8 @@ def test_constructor_bit_timing_fd(self): data_tseg2=6, data_sjw=1, ) - can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing) + bus = can.Bus(interface="pcan", channel="PCAN_USBBUS1", timing=timing) + self.assertEqual(bus.protocol, CanProtocol.CAN_FD) bitrate_arg = self.mock_pcan.InitializeFD.call_args[0][-1] diff --git a/test/test_robotell.py b/test/test_robotell.py index c0658ef2c..f95139917 100644 --- a/test/test_robotell.py +++ b/test/test_robotell.py @@ -15,6 +15,9 @@ def setUp(self): def tearDown(self): self.bus.shutdown() + def test_protocol(self): + self.assertEqual(self.bus.protocol, can.CanProtocol.CAN_20) + def test_recv_extended(self): self.serial.write( bytearray( diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 90a143a36..f756cb93a 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -9,6 +9,8 @@ import warnings from unittest.mock import patch +from .config import TEST_INTERFACE_SOCKETCAN + import can from can.interfaces.socketcan.constants import ( CAN_BCM_TX_DELETE, @@ -357,6 +359,16 @@ def test_build_bcm_update_header(self): self.assertEqual(can_id, result.can_id) self.assertEqual(1, result.nframes) + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "Only run when vcan0 is available") + def test_bus_creation_can(self): + bus = can.Bus(interface="socketcan", channel="vcan0", fd=False) + self.assertEqual(bus.protocol, can.CanProtocol.CAN_20) + + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "Only run when vcan0 is available") + def test_bus_creation_can_fd(self): + bus = can.Bus(interface="socketcan", channel="vcan0", fd=True) + self.assertEqual(bus.protocol, can.CanProtocol.CAN_FD) + @unittest.skipUnless(IS_LINUX and IS_PYPY, "Only test when run on Linux with PyPy") def test_pypy_socketcan_support(self): """Wait for PyPy raw CAN socket support diff --git a/test/test_systec.py b/test/test_systec.py index 7495f75eb..86ed31362 100644 --- a/test/test_systec.py +++ b/test/test_systec.py @@ -36,6 +36,8 @@ def setUp(self): def test_bus_creation(self): self.assertIsInstance(self.bus, ucanbus.UcanBus) + self.assertEqual(self.bus.protocol, can.CanProtocol.CAN_20) + self.assertTrue(ucan.UcanInitHwConnectControlEx.called) self.assertTrue( ucan.UcanInitHardwareEx.called or ucan.UcanInitHardwareEx2.called diff --git a/test/test_vector.py b/test/test_vector.py index b6a0632a8..93aba9c7b 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -81,6 +81,8 @@ def mock_xldriver() -> None: def test_bus_creation_mocked(mock_xldriver) -> None: bus = can.Bus(channel=0, interface="vector", _testing=True) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() @@ -97,6 +99,8 @@ def test_bus_creation_mocked(mock_xldriver) -> None: def test_bus_creation() -> None: bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), interface="vector") assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 + bus.shutdown() xl_channel_config = _find_xl_channel_config( @@ -110,12 +114,15 @@ def test_bus_creation() -> None: bus = canlib.VectorBus(channel=0, serial=_find_virtual_can_serial()) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 bus.shutdown() def test_bus_creation_bitrate_mocked(mock_xldriver) -> None: bus = can.Bus(channel=0, interface="vector", bitrate=200_000, _testing=True) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() @@ -141,6 +148,7 @@ def test_bus_creation_bitrate() -> None: bitrate=200_000, ) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 xl_channel_config = _find_xl_channel_config( serial=_find_virtual_can_serial(), channel=0 @@ -153,6 +161,8 @@ def test_bus_creation_bitrate() -> None: def test_bus_creation_fd_mocked(mock_xldriver) -> None: bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_FD + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() @@ -173,6 +183,7 @@ def test_bus_creation_fd() -> None: channel=0, serial=_find_virtual_can_serial(), interface="vector", fd=True ) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_FD xl_channel_config = _find_xl_channel_config( serial=_find_virtual_can_serial(), channel=0 @@ -204,6 +215,8 @@ def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None: _testing=True, ) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_FD + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() @@ -346,6 +359,7 @@ def test_bus_creation_timing() -> None: timing=timing, ) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 xl_channel_config = _find_xl_channel_config( serial=_find_virtual_can_serial(), channel=0 @@ -377,6 +391,8 @@ def test_bus_creation_timingfd_mocked(mock_xldriver) -> None: _testing=True, ) assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_FD + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() @@ -425,6 +441,8 @@ def test_bus_creation_timingfd() -> None: timing=timing, ) + assert bus.protocol == can.CanProtocol.CAN_FD + xl_channel_config = _find_xl_channel_config( serial=_find_virtual_can_serial(), channel=0 ) From 791820953e7d409528e253c08364f35c822ed83c Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Mon, 15 May 2023 17:37:43 +0200 Subject: [PATCH 1043/1235] Add auto-modifying cyclic tasks (#703) * Add auto-modifying cyclic tasks * Make sure self.modifier_callback exists * Don't break socketcan Added modifier_callback arguments where necessary, and changed MRO of sockercan's CyclicSendTask to match that of the fallback. * Remove __init__ from ModifiableCyclicTaskABC This makes several changes to socketcan in previous commits unnecessary. These changes are also removed. * Forgot some brackets... * Reformatting by black * modifier_callback should change one message per send * Forgot to change type hint * fix CI * use mutating callback, adapt SocketcanBus and ixxat, add test * improve docstring * fix ixxat imports --------- Co-authored-by: zariiii9003 Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/broadcastmanager.py | 32 ++++++++----- can/bus.py | 20 ++++++-- can/interfaces/ixxat/canlib.py | 22 +++++++-- can/interfaces/ixxat/canlib_vcinpl.py | 56 ++++++++++++++++------ can/interfaces/ixxat/canlib_vcinpl2.py | 56 ++++++++++++++++------ can/interfaces/socketcan/socketcan.py | 36 +++++++++++---- examples/cyclic_checksum.py | 64 ++++++++++++++++++++++++++ test/simplecyclic_test.py | 59 ++++++++++++++++++++---- 8 files changed, 274 insertions(+), 71 deletions(-) create mode 100644 examples/cyclic_checksum.py diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 39dc1f0ba..ae47fc048 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -53,7 +53,7 @@ def stop(self) -> None: """ -class CyclicSendTaskABC(CyclicTask): +class CyclicSendTaskABC(CyclicTask, abc.ABC): """ Message send task with defined period """ @@ -114,7 +114,7 @@ def _check_and_convert_messages( return messages -class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): +class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC, abc.ABC): def __init__( self, messages: Union[Sequence[Message], Message], @@ -136,7 +136,7 @@ def __init__( self.duration = duration -class RestartableCyclicTaskABC(CyclicSendTaskABC): +class RestartableCyclicTaskABC(CyclicSendTaskABC, abc.ABC): """Adds support for restarting a stopped cyclic task""" @abc.abstractmethod @@ -144,9 +144,7 @@ def start(self) -> None: """Restart a stopped periodic task.""" -class ModifiableCyclicTaskABC(CyclicSendTaskABC): - """Adds support for modifying a periodic message""" - +class ModifiableCyclicTaskABC(CyclicSendTaskABC, abc.ABC): def _check_modified_messages(self, messages: Tuple[Message, ...]) -> None: """Helper function to perform error checking when modifying the data in the cyclic task. @@ -190,7 +188,7 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: self.messages = messages -class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): +class MultiRateCyclicSendTaskABC(CyclicSendTaskABC, abc.ABC): """A Cyclic send task that supports switches send frequency after a set time.""" def __init__( @@ -218,7 +216,7 @@ def __init__( class ThreadBasedCyclicSendTask( - ModifiableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC + LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC ): """Fallback cyclic send task using daemon thread.""" @@ -230,6 +228,7 @@ def __init__( period: float, duration: Optional[float] = None, on_error: Optional[Callable[[Exception], bool]] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, ) -> None: """Transmits `messages` with a `period` seconds for `duration` seconds on a `bus`. @@ -255,6 +254,7 @@ def __init__( time.perf_counter() + duration if duration else None ) self.on_error = on_error + self.modifier_callback = modifier_callback if USE_WINDOWS_EVENTS: self.period_ms = int(round(period * 1000, 0)) @@ -301,14 +301,22 @@ def _run(self) -> None: # Prevent calling bus.send from multiple threads with self.send_lock: try: + if self.modifier_callback is not None: + self.modifier_callback(self.messages[msg_index]) self.bus.send(self.messages[msg_index]) except Exception as exc: # pylint: disable=broad-except log.exception(exc) - if self.on_error: - if not self.on_error(exc): - break - else: + + # stop if `on_error` callback was not given + if self.on_error is None: + self.stop() + raise exc + + # stop if `on_error` returns False + if not self.on_error(exc): + self.stop() break + msg_due_time_ns += self.period_ns if self.end_time is not None and time.perf_counter() >= self.end_time: break diff --git a/can/bus.py b/can/bus.py index b7a54dbb1..9c65ad52f 100644 --- a/can/bus.py +++ b/can/bus.py @@ -8,7 +8,7 @@ from abc import ABC, ABCMeta, abstractmethod from enum import Enum, auto from time import time -from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union, cast +from typing import Any, Callable, Iterator, List, Optional, Sequence, Tuple, Union, cast import can import can.typechecking @@ -195,6 +195,7 @@ def send_periodic( period: float, duration: Optional[float] = None, store_task: bool = True, + modifier_callback: Optional[Callable[[Message], None]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -216,6 +217,10 @@ def send_periodic( :param store_task: If True (the default) the task will be attached to this Bus instance. Disable to instead manage tasks manually. + :param modifier_callback: + Function which should be used to modify each message's data before + sending. The callback modifies the :attr:`~can.Message.data` of the + message and returns ``None``. :return: A started task instance. Note the task can be stopped (and depending on the backend modified) by calling the task's @@ -230,7 +235,7 @@ def send_periodic( .. note:: - For extremely long running Bus instances with many short lived + For extremely long-running Bus instances with many short-lived tasks the default api with ``store_task==True`` may not be appropriate as the stopped tasks are still taking up memory as they are associated with the Bus instance. @@ -247,9 +252,8 @@ def send_periodic( # Create a backend specific task; will be patched to a _SelfRemovingCyclicTask later task = cast( _SelfRemovingCyclicTask, - self._send_periodic_internal(msgs, period, duration), + self._send_periodic_internal(msgs, period, duration, modifier_callback), ) - # we wrap the task's stop method to also remove it from the Bus's list of tasks periodic_tasks = self._periodic_tasks original_stop_method = task.stop @@ -275,6 +279,7 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. @@ -298,7 +303,12 @@ def _send_periodic_internal( threading.Lock() ) task = ThreadBasedCyclicSendTask( - self, self._lock_send_periodic, msgs, period, duration + bus=self, + lock=self._lock_send_periodic, + messages=msgs, + period=period, + duration=duration, + modifier_callback=modifier_callback, ) return task diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index b28c93541..d47fc2d6a 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,9 +1,13 @@ -from typing import Optional +from typing import Callable, Optional, Sequence, Union import can.interfaces.ixxat.canlib_vcinpl as vcinpl import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 -from can import BusABC, Message -from can.bus import BusState +from can import ( + BusABC, + BusState, + CyclicSendTaskABC, + Message, +) class IXXATBus(BusABC): @@ -145,8 +149,16 @@ def _recv_internal(self, timeout): def send(self, msg: Message, timeout: Optional[float] = None) -> None: return self.bus.send(msg, timeout) - def _send_periodic_internal(self, msgs, period, duration=None): - return self.bus._send_periodic_internal(msgs, period, duration) + def _send_periodic_internal( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, + ) -> CyclicSendTaskABC: + return self.bus._send_periodic_internal( + msgs, period, duration, modifier_callback + ) def shutdown(self) -> None: super().shutdown() diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 5a366cc30..cbf2fb61c 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -13,14 +13,18 @@ import functools import logging import sys -from typing import Callable, Optional, Tuple - -from can import BusABC, CanProtocol, Message -from can.broadcastmanager import ( +import warnings +from typing import Callable, Optional, Sequence, Tuple, Union + +from can import ( + BusABC, + BusState, + CanProtocol, + CyclicSendTaskABC, LimitedDurationCyclicSendTaskABC, + Message, RestartableCyclicTaskABC, ) -from can.bus import BusState from can.ctypesutil import HANDLE, PHANDLE, CLibrary from can.ctypesutil import HRESULT as ctypes_HRESULT from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError @@ -785,17 +789,39 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: # Want to log outgoing messages? # log.log(self.RECV_LOGGING_LEVEL, "Sent: %s", message) - 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, + modifier_callback: Optional[Callable[[Message], None]] = None, + ) -> CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) - caps = structures.CANCAPABILITIES() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask( - self._scheduler, msgs, period, duration, self._scheduler_resolution + if modifier_callback is None: + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen( + self._device_handle, self.channel, self._scheduler + ) + caps = structures.CANCAPABILITIES() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask( + self._scheduler, msgs, period, duration, self._scheduler_resolution + ) + + # fallback to thread based cyclic task + warnings.warn( + f"{self.__class__.__name__} falls back to a thread-based cyclic task, " + "when the `modifier_callback` argument is given." + ) + return BusABC._send_periodic_internal( + self, + msgs=msgs, + period=period, + duration=duration, + modifier_callback=modifier_callback, ) def shutdown(self): diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 446b3e35c..b796be744 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -13,11 +13,15 @@ import functools import logging import sys -from typing import Callable, Optional, Tuple +import warnings +from typing import Callable, Optional, Sequence, Tuple, Union -from can import BusABC, CanProtocol, Message -from can.broadcastmanager import ( +from can import ( + BusABC, + CanProtocol, + CyclicSendTaskABC, LimitedDurationCyclicSendTaskABC, + Message, RestartableCyclicTaskABC, ) from can.ctypesutil import HANDLE, PHANDLE, CLibrary @@ -931,19 +935,41 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: else: _canlib.canChannelPostMessage(self._channel_handle, message) - 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, + modifier_callback: Optional[Callable[[Message], None]] = None, + ) -> CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) - caps = structures.CANCAPABILITIES2() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = ( - caps.dwCmsClkFreq / caps.dwCmsDivisor - ) # TODO: confirm - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask( - self._scheduler, msgs, period, duration, self._scheduler_resolution + if modifier_callback is None: + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen( + self._device_handle, self.channel, self._scheduler + ) + caps = structures.CANCAPABILITIES2() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = ( + caps.dwCmsClkFreq / caps.dwCmsDivisor + ) # TODO: confirm + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask( + self._scheduler, msgs, period, duration, self._scheduler_resolution + ) + + # fallback to thread based cyclic task + warnings.warn( + f"{self.__class__.__name__} falls back to a thread-based cyclic task, " + "when the `modifier_callback` argument is given." + ) + return BusABC._send_periodic_internal( + self, + msgs=msgs, + period=period, + duration=duration, + modifier_callback=modifier_callback, ) def shutdown(self): diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index a3f74bb82..44cecec76 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -14,7 +14,8 @@ import struct import threading import time -from typing import Dict, List, Optional, Sequence, Tuple, Type, Union +import warnings +from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type, Union log = logging.getLogger(__name__) log_tx = log.getChild("tx") @@ -806,7 +807,8 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, - ) -> CyclicSendTask: + modifier_callback: Optional[Callable[[Message], None]] = None, + ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. The Linux kernel's Broadcast Manager SocketCAN API is used to schedule @@ -838,15 +840,29 @@ def _send_periodic_internal( general the message will be sent at the given rate until at least *duration* seconds. """ - msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages( # pylint: disable=protected-access - msgs - ) + if modifier_callback is None: + msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages( # pylint: disable=protected-access + msgs + ) + + msgs_channel = str(msgs[0].channel) if msgs[0].channel else None + bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) + task_id = self._get_next_task_id() + task = CyclicSendTask(bcm_socket, task_id, msgs, period, duration) + return task - msgs_channel = str(msgs[0].channel) if msgs[0].channel else None - bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) - task_id = self._get_next_task_id() - task = CyclicSendTask(bcm_socket, task_id, msgs, period, duration) - return task + # fallback to thread based cyclic task + warnings.warn( + f"{self.__class__.__name__} falls back to a thread-based cyclic task, " + "when the `modifier_callback` argument is given." + ) + return BusABC._send_periodic_internal( + self, + msgs=msgs, + period=period, + duration=duration, + modifier_callback=modifier_callback, + ) def _get_next_task_id(self) -> int: with self._task_id_guard: diff --git a/examples/cyclic_checksum.py b/examples/cyclic_checksum.py new file mode 100644 index 000000000..3ab6c78ac --- /dev/null +++ b/examples/cyclic_checksum.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +""" +This example demonstrates how to send a periodic message containing +an automatically updating counter and checksum. + +Expects a virtual interface: + + python3 -m examples.cyclic_checksum +""" + +import logging +import time + +import can + +logging.basicConfig(level=logging.INFO) + + +def cyclic_checksum_send(bus: can.BusABC) -> None: + """ + Sends periodic messages every 1 s with no explicit timeout. + The message's counter and checksum is updated before each send. + Sleeps for 10 seconds then stops the task. + """ + message = can.Message(arbitration_id=0x78, data=[0, 1, 2, 3, 4, 5, 6, 0]) + print("Starting to send an auto-updating message every 100ms for 3 s") + task = bus.send_periodic(msgs=message, period=0.1, modifier_callback=update_message) + time.sleep(3) + task.stop() + print("stopped cyclic send") + + +def update_message(message: can.Message) -> None: + counter = increment_counter(message) + checksum = compute_xbr_checksum(message, counter) + message.data[7] = (checksum << 4) + counter + + +def increment_counter(message: can.Message) -> int: + counter = message.data[7] & 0x0F + counter += 1 + counter %= 16 + + return counter + + +def compute_xbr_checksum(message: can.Message, counter: int) -> int: + """ + Computes an XBR checksum as per SAE J1939 SPN 3188. + """ + checksum = sum(message.data[:7]) + checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder="big")) + checksum += counter & 0x0F + xbr_checksum = ((checksum >> 4) + checksum) & 0x0F + + return xbr_checksum + + +if __name__ == "__main__": + with can.Bus(channel=0, interface="virtual", receive_own_messages=True) as _bus: + notifier = can.Notifier(bus=_bus, listeners=[print]) + cyclic_checksum_send(_bus) + notifier.stop() diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 9e01be457..650a1fddf 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -5,8 +5,10 @@ """ import gc +import time import unittest from time import sleep +from typing import List from unittest.mock import MagicMock import can @@ -160,34 +162,73 @@ def test_thread_based_cyclic_send_task(self): # good case, bus is up on_error_mock = MagicMock(return_value=False) task = can.broadcastmanager.ThreadBasedCyclicSendTask( - bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + bus=bus, + lock=bus._lock_send_periodic, + messages=msg, + period=0.1, + duration=3, + on_error=on_error_mock, ) - task.start() sleep(1) on_error_mock.assert_not_called() task.stop() bus.shutdown() - # bus has been shutted down + # bus has been shut down on_error_mock = MagicMock(return_value=False) task = can.broadcastmanager.ThreadBasedCyclicSendTask( - bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + bus=bus, + lock=bus._lock_send_periodic, + messages=msg, + period=0.1, + duration=3, + on_error=on_error_mock, ) - task.start() sleep(1) - self.assertEqual(on_error_mock.call_count, 1) + self.assertEqual(1, on_error_mock.call_count) task.stop() - # bus is still shutted down, but on_error returns True + # bus is still shut down, but on_error returns True on_error_mock = MagicMock(return_value=True) task = can.broadcastmanager.ThreadBasedCyclicSendTask( - bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + bus=bus, + lock=bus._lock_send_periodic, + messages=msg, + period=0.1, + duration=3, + on_error=on_error_mock, ) - task.start() sleep(1) self.assertTrue(on_error_mock.call_count > 1) task.stop() + def test_modifier_callback(self) -> None: + msg_list: List[can.Message] = [] + + def increment_first_byte(msg: can.Message) -> None: + msg.data[0] += 1 + + original_msg = can.Message( + is_extended_id=False, arbitration_id=0x123, data=[0] * 8 + ) + + with can.ThreadSafeBus(interface="virtual", receive_own_messages=True) as bus: + notifier = can.Notifier(bus=bus, listeners=[msg_list.append]) + task = bus.send_periodic( + msgs=original_msg, period=0.001, modifier_callback=increment_first_byte + ) + time.sleep(0.2) + task.stop() + notifier.stop() + + self.assertEqual(b"\x01\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[0].data)) + self.assertEqual(b"\x02\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[1].data)) + self.assertEqual(b"\x03\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[2].data)) + self.assertEqual(b"\x04\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[3].data)) + self.assertEqual(b"\x05\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[4].data)) + self.assertEqual(b"\x06\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[5].data)) + self.assertEqual(b"\x07\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[6].data)) + if __name__ == "__main__": unittest.main() From 97f1e160f19e196871358920ba6d059f6b9663e3 Mon Sep 17 00:00:00 2001 From: Teejay Date: Tue, 16 May 2023 09:57:29 -0700 Subject: [PATCH 1044/1235] Convert setup.py to pyproject.toml (#1592) * Remove files * Update pyproject * Update ci * Fix ci errors * Apply formatter changes * Fix py312 ci * MR feedback Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- .github/workflows/ci.yml | 4 +- .github/workflows/format-code.yml | 2 +- can/interfaces/ixxat/canlib.py | 6 +- can/interfaces/seeedstudio/seeedstudio.py | 2 +- can/io/printer.py | 2 +- pyproject.toml | 143 ++++++++++++++++++++-- requirements-lint.txt | 6 - setup.cfg | 35 ------ setup.py | 104 ---------------- 9 files changed, 141 insertions(+), 163 deletions(-) delete mode 100644 requirements-lint.txt delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 949df6aab..3f143234b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-lint.txt + pip install -e .[lint] - name: mypy 3.7 run: | mypy --python-version 3.7 . @@ -125,7 +125,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-lint.txt + pip install -e .[lint] - name: Code Format Check with Black run: | black --check --verbose . diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index 68c6f56d8..30f95c103 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -17,7 +17,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-lint.txt + pip install -e .[lint] - name: Code Format Check with Black run: | black --verbose . diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index d47fc2d6a..8c07508e4 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -39,7 +39,7 @@ def __init__( tseg1_dbr: int = None, tseg2_dbr: int = None, ssp_dbr: int = None, - **kwargs + **kwargs, ): """ :param channel: @@ -116,7 +116,7 @@ def __init__( tseg1_dbr=tseg1_dbr, tseg2_dbr=tseg2_dbr, ssp_dbr=ssp_dbr, - **kwargs + **kwargs, ) else: if rx_fifo_size is None: @@ -132,7 +132,7 @@ def __init__( rx_fifo_size=rx_fifo_size, tx_fifo_size=tx_fifo_size, bitrate=bitrate, - **kwargs + **kwargs, ) super().__init__(channel=channel, **kwargs) diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 0540c78be..8e0dca8c7 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -64,7 +64,7 @@ def __init__( operation_mode="normal", bitrate=500000, *args, - **kwargs + **kwargs, ): """ :param str channel: diff --git a/can/io/printer.py b/can/io/printer.py index d0df71db8..40b42862d 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -28,7 +28,7 @@ def __init__( self, file: Optional[Union[StringPathLike, TextIO]] = None, append: bool = False, - **kwargs: Any + **kwargs: Any, ) -> None: """ :param file: An optional path-like object or a file-like object to "print" diff --git a/pyproject.toml b/pyproject.toml index af952e51e..65b8f8aed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,142 @@ [build-system] -requires = [ - "setuptools >= 40.8", - "wheel", -] +requires = ["setuptools >= 67.7.2"] build-backend = "setuptools.build_meta" +[project] +name = "python-can" +dynamic = ["readme", "version"] +description = "Controller Area Network interface module for Python" +authors = [{ name = "python-can contributors" }] +dependencies = [ + "wrapt~=1.10", + "packaging >= 23.1", + "setuptools >= 67.7.2", + "typing_extensions>=3.10.0.0", + "msgpack~=1.0.0; platform_system != 'Windows'", + "pywin32>=305; platform_system == 'Windows' and platform_python_implementation == 'CPython'", +] +requires-python = ">=3.7" +license = { text = "LGPL v3" } +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Information Technology", + "Intended Audience :: Manufacturing", + "Intended Audience :: Telecommunications Industry", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Natural Language :: English", + "Natural Language :: English", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: System :: Hardware :: Hardware Drivers", + "Topic :: System :: Logging", + "Topic :: System :: Monitoring", + "Topic :: System :: Networking", + "Topic :: Utilities", +] + +[project.scripts] +"can_logconvert.py" = "scripts.can_logconvert:main" +"can_logger.py" = "scripts.can_logger:main" +"can_player.py" = "scripts.can_player:main" +"can_viewer.py" = "scripts.can_viewer:main" + +[project.urls] +homepage = "https://github.com/hardbyte/python-can" +documentation = "https://python-can.readthedocs.io" +repository = "https://github.com/hardbyte/python-can" +changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" + +[project.optional-dependencies] +lint = [ + "pylint==2.16.4", + "ruff==0.0.260", + "black~=23.1.0", + "mypy==1.0.1", + "mypy-extensions==0.4.3", + "types-setuptools" +] +seeedstudio = ["pyserial>=3.0"] +serial = ["pyserial~=3.0"] +neovi = ["filelock", "python-ics>=2.12"] +canalystii = ["canalystii>=0.1.0"] +cantact = ["cantact>=0.0.7"] +cvector = ["python-can-cvector"] +gs_usb = ["gs_usb>=0.2.1"] +nixnet = ["nixnet>=0.3.2"] +pcan = ["uptime~=3.0.1"] +remote = ["python-can-remote"] +sontheim = ["python-can-sontheim>=0.1.2"] +canine = ["python-can-canine>=0.2.2"] +viewer = [ + "windows-curses; platform_system == 'Windows' and platform_python_implementation=='CPython'" +] +mf4 = ["asammdf>=6.0.0"] + +[tool.setuptools.dynamic] +readme = { file = "README.rst" } +version = { attr = "can.__version__" } + +[tool.setuptools.package-data] +"*" = ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.md"] +doc = ["*.*"] +examples = ["*.py"] +can = ["py.typed"] + +[tool.setuptools.packages.find] +include = ["can*", "scripts"] + +[tool.mypy] +warn_return_any = true +warn_unused_configs = true +ignore_missing_imports = true +no_implicit_optional = true +disallow_incomplete_defs = true +warn_redundant_casts = true +warn_unused_ignores = false +exclude = [ + "venv", + "^doc/conf.py$", + "^build", + "^test", + "^setup.py$", + "^can/interfaces/__init__.py", + "^can/interfaces/etas", + "^can/interfaces/gs_usb", + "^can/interfaces/ics_neovi", + "^can/interfaces/iscan", + "^can/interfaces/ixxat", + "^can/interfaces/kvaser", + "^can/interfaces/nican", + "^can/interfaces/neousys", + "^can/interfaces/pcan", + "^can/interfaces/serial", + "^can/interfaces/slcan", + "^can/interfaces/socketcan", + "^can/interfaces/systec", + "^can/interfaces/udp_multicast", + "^can/interfaces/usb2can", + "^can/interfaces/virtual", +] + [tool.ruff] select = [ - "F401", # unused-imports - "UP", # pyupgrade - "I", # isort -] + "F401", # unused-imports + "UP", # pyupgrade + "I", # isort -# Assume Python 3.7. -target-version = "py37" +] [tool.ruff.isort] known-first-party = ["can"] diff --git a/requirements-lint.txt b/requirements-lint.txt deleted file mode 100644 index 829fe5663..000000000 --- a/requirements-lint.txt +++ /dev/null @@ -1,6 +0,0 @@ -pylint==2.16.4 -ruff==0.0.260 -black~=23.1.0 -mypy==1.0.1 -mypy-extensions==0.4.3 -types-setuptools diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3e121b650..000000000 --- a/setup.cfg +++ /dev/null @@ -1,35 +0,0 @@ -[metadata] -license_files = LICENSE.txt - -[mypy] -warn_return_any = True -warn_unused_configs = True -ignore_missing_imports = True -no_implicit_optional = True -disallow_incomplete_defs = True -warn_redundant_casts = True -warn_unused_ignores = False -exclude = - (?x)( - venv - |^doc/conf.py$ - |^test - |^setup.py$ - |^can/interfaces/__init__.py - |^can/interfaces/etas - |^can/interfaces/gs_usb - |^can/interfaces/ics_neovi - |^can/interfaces/iscan - |^can/interfaces/ixxat - |^can/interfaces/kvaser - |^can/interfaces/nican - |^can/interfaces/neousys - |^can/interfaces/pcan - |^can/interfaces/serial - |^can/interfaces/slcan - |^can/interfaces/socketcan - |^can/interfaces/systec - |^can/interfaces/udp_multicast - |^can/interfaces/usb2can - |^can/interfaces/virtual - ) diff --git a/setup.py b/setup.py deleted file mode 100644 index 65298b072..000000000 --- a/setup.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python - -""" -Setup script for the `can` package. -Learn more at https://github.com/hardbyte/python-can/ -""" - -import logging -import re -from os import listdir -from os.path import isfile, join - -from setuptools import find_packages, setup - -logging.basicConfig(level=logging.WARNING) - -with open("can/__init__.py", encoding="utf-8") as fd: - version = re.search( - r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE - ).group(1) - -with open("README.rst", encoding="utf-8") as f: - long_description = f.read() - -# Dependencies -extras_require = { - "seeedstudio": ["pyserial>=3.0"], - "serial": ["pyserial~=3.0"], - "neovi": ["filelock", "python-ics>=2.12"], - "canalystii": ["canalystii>=0.1.0"], - "cantact": ["cantact>=0.0.7"], - "cvector": ["python-can-cvector"], - "gs_usb": ["gs_usb>=0.2.1"], - "nixnet": ["nixnet>=0.3.2"], - "pcan": ["uptime~=3.0.1"], - "remote": ["python-can-remote"], - "sontheim": ["python-can-sontheim>=0.1.2"], - "canine": ["python-can-canine>=0.2.2"], - "viewer": [ - 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"' - ], - "mf4": ["asammdf>=6.0.0"], -} - -setup( - # Description - name="python-can", - url="https://github.com/hardbyte/python-can", - description="Controller Area Network interface module for Python", - long_description=long_description, - long_description_content_type="text/x-rst", - classifiers=[ - # a list of all available ones: https://pypi.org/classifiers/ - "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Natural Language :: English", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Operating System :: Microsoft :: Windows", - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Intended Audience :: Information Technology", - "Intended Audience :: Manufacturing", - "Intended Audience :: Telecommunications Industry", - "Natural Language :: English", - "Topic :: System :: Logging", - "Topic :: System :: Monitoring", - "Topic :: System :: Networking", - "Topic :: System :: Hardware :: Hardware Drivers", - "Topic :: Utilities", - ], - version=version, - packages=find_packages(exclude=["test*", "doc", "scripts", "examples"]), - scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), - author="python-can contributors", - license="LGPL v3", - package_data={ - "": ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.md"], - "doc": ["*.*"], - "examples": ["*.py"], - "can": ["py.typed"], - }, - # Installation - # see https://www.python.org/dev/peps/pep-0345/#version-specifiers - python_requires=">=3.7", - install_requires=[ - "setuptools", - "wrapt~=1.10", - "typing_extensions>=3.10.0.0", - 'pywin32>=305;platform_system=="Windows" and platform_python_implementation=="CPython"', - 'msgpack~=1.0.0;platform_system!="Windows"', - "packaging", - ], - extras_require=extras_require, -) From b61482aa814c80b15527b17c83e3aece360e8d92 Mon Sep 17 00:00:00 2001 From: Robert Imschweiler <50044286+ro-i@users.noreply.github.com> Date: Tue, 16 May 2023 19:01:54 +0200 Subject: [PATCH 1045/1235] can.io: Distinguish Text/Binary-IO for Reader/Writer classes. (#1585) --- can/io/asc.py | 6 +++--- can/io/blf.py | 4 ++-- can/io/canutils.py | 6 +++--- can/io/csv.py | 6 +++--- can/io/generic.py | 20 ++++++++++++++++++++ can/io/logger.py | 34 +++++++++++++++++++++++++--------- can/io/mf4.py | 6 +++--- can/io/player.py | 23 ++++++++++++++++------- can/io/trc.py | 9 ++++++--- 9 files changed, 81 insertions(+), 33 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 5169d7468..3114acfbe 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -14,7 +14,7 @@ from ..message import Message from ..typechecking import StringPathLike from ..util import channel2int, dlc2len, len2dlc -from .generic import FileIOMessageWriter, MessageReader +from .generic import TextIOMessageReader, TextIOMessageWriter CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF @@ -24,7 +24,7 @@ logger = logging.getLogger("can.io.asc") -class ASCReader(MessageReader): +class ASCReader(TextIOMessageReader): """ Iterator of CAN messages from a ASC logging file. Meta data (comments, bus statistics, J1939 Transport Protocol messages) is ignored. @@ -308,7 +308,7 @@ def __iter__(self) -> Generator[Message, None, None]: self.stop() -class ASCWriter(FileIOMessageWriter): +class ASCWriter(TextIOMessageWriter): """Logs CAN data to an ASCII log file (.asc). The measurement starts with the timestamp of the first registered message. diff --git a/can/io/blf.py b/can/io/blf.py index e9dd8380f..071c089d7 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -22,7 +22,7 @@ from ..message import Message from ..typechecking import StringPathLike from ..util import channel2int, dlc2len, len2dlc -from .generic import FileIOMessageWriter, MessageReader +from .generic import BinaryIOMessageReader, FileIOMessageWriter TSystemTime = Tuple[int, int, int, int, int, int, int, int] @@ -132,7 +132,7 @@ def systemtime_to_timestamp(systemtime: TSystemTime) -> float: return 0 -class BLFReader(MessageReader): +class BLFReader(BinaryIOMessageReader): """ Iterator of CAN messages from a Binary Logging File. diff --git a/can/io/canutils.py b/can/io/canutils.py index a9dced6a1..d7ae99daf 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -10,7 +10,7 @@ from can.message import Message from ..typechecking import StringPathLike -from .generic import FileIOMessageWriter, MessageReader +from .generic import TextIOMessageReader, TextIOMessageWriter log = logging.getLogger("can.io.canutils") @@ -23,7 +23,7 @@ CANFD_ESI = 0x02 -class CanutilsLogReader(MessageReader): +class CanutilsLogReader(TextIOMessageReader): """ Iterator over CAN messages from a .log Logging File (candump -L). @@ -122,7 +122,7 @@ def __iter__(self) -> Generator[Message, None, None]: self.stop() -class CanutilsLogWriter(FileIOMessageWriter): +class CanutilsLogWriter(TextIOMessageWriter): """Logs CAN data to an ASCII log file (.log). This class is is compatible with "candump -L". diff --git a/can/io/csv.py b/can/io/csv.py index b96e69342..2abaeb70e 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -15,10 +15,10 @@ from can.message import Message from ..typechecking import StringPathLike -from .generic import FileIOMessageWriter, MessageReader +from .generic import TextIOMessageReader, TextIOMessageWriter -class CSVReader(MessageReader): +class CSVReader(TextIOMessageReader): """Iterator over CAN messages from a .csv file that was generated by :class:`~can.CSVWriter` or that uses the same format as described there. Assumes that there is a header @@ -67,7 +67,7 @@ def __iter__(self) -> Generator[Message, None, None]: self.stop() -class CSVWriter(FileIOMessageWriter): +class CSVWriter(TextIOMessageWriter): """Writes a comma separated text file with a line for each message. Includes a header line. diff --git a/can/io/generic.py b/can/io/generic.py index 193ec3df2..eb9647474 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,13 +1,17 @@ """Contains generic base classes for file IO.""" +import gzip import locale from abc import ABCMeta from types import TracebackType from typing import ( Any, + BinaryIO, ContextManager, Iterable, Optional, + TextIO, Type, + Union, cast, ) @@ -105,5 +109,21 @@ def file_size(self) -> int: return self.file.tell() +class TextIOMessageWriter(FileIOMessageWriter, metaclass=ABCMeta): + file: TextIO + + +class BinaryIOMessageWriter(FileIOMessageWriter, metaclass=ABCMeta): + file: Union[BinaryIO, gzip.GzipFile] + + class MessageReader(BaseIOHandler, Iterable[Message], metaclass=ABCMeta): """The base class for all readers.""" + + +class TextIOMessageReader(MessageReader, metaclass=ABCMeta): + file: TextIO + + +class BinaryIOMessageReader(MessageReader, metaclass=ABCMeta): + file: Union[BinaryIO, gzip.GzipFile] diff --git a/can/io/logger.py b/can/io/logger.py index 07d288ba3..7075a7ee2 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -20,7 +20,12 @@ from .blf import BLFWriter from .canutils import CanutilsLogWriter from .csv import CSVWriter -from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter +from .generic import ( + BaseIOHandler, + BinaryIOMessageWriter, + FileIOMessageWriter, + MessageWriter, +) from .mf4 import MF4Writer from .printer import Printer from .sqlite import SqliteWriter @@ -94,20 +99,28 @@ def __new__( # type: ignore file_or_filename: AcceptedIOType = filename if suffix == ".gz": - suffix, file_or_filename = Logger.compress(filename, **kwargs) + LoggerType, file_or_filename = Logger.compress(filename, **kwargs) + else: + LoggerType = cls._get_logger_for_suffix(suffix) + + return LoggerType(file=file_or_filename, **kwargs) + @classmethod + def _get_logger_for_suffix(cls, suffix: str) -> Type[MessageWriter]: try: LoggerType = Logger.message_writers[suffix] if LoggerType is None: raise ValueError(f'failed to import logger for extension "{suffix}"') - return LoggerType(file=file_or_filename, **kwargs) + return LoggerType except KeyError: raise ValueError( f'No write support for this unknown log format "{suffix}"' ) from None - @staticmethod - def compress(filename: StringPathLike, **kwargs: Any) -> Tuple[str, FileLike]: + @classmethod + def compress( + cls, filename: StringPathLike, **kwargs: Any + ) -> Tuple[Type[MessageWriter], FileLike]: """ Return the suffix and io object of the decompressed file. File will automatically recompress upon close. @@ -117,12 +130,15 @@ def compress(filename: StringPathLike, **kwargs: Any) -> Tuple[str, FileLike]: raise ValueError( f"The file type {real_suffix} is currently incompatible with gzip." ) - if kwargs.get("append", False): - mode = "ab" if real_suffix == ".blf" else "at" + LoggerType = cls._get_logger_for_suffix(real_suffix) + append = kwargs.get("append", False) + + if issubclass(LoggerType, BinaryIOMessageWriter): + mode = "ab" if append else "wb" else: - mode = "wb" if real_suffix == ".blf" else "wt" + mode = "at" if append else "wt" - return real_suffix, gzip.open(filename, mode) + return LoggerType, gzip.open(filename, mode) def on_message_received(self, msg: Message) -> None: pass diff --git a/can/io/mf4.py b/can/io/mf4.py index faad9c37a..215543e9f 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -14,7 +14,7 @@ from ..message import Message from ..typechecking import StringPathLike from ..util import channel2int, dlc2len, len2dlc -from .generic import FileIOMessageWriter, MessageReader +from .generic import BinaryIOMessageReader, BinaryIOMessageWriter logger = logging.getLogger("can.io.mf4") @@ -75,7 +75,7 @@ CAN_ID_MASK = 0x1FFFFFFF -class MF4Writer(FileIOMessageWriter): +class MF4Writer(BinaryIOMessageWriter): """Logs CAN data to an ASAM Measurement Data File v4 (.mf4). MF4Writer does not support append mode. @@ -265,7 +265,7 @@ def on_message_received(self, msg: Message) -> None: self._rtr_buffer = np.zeros(1, dtype=RTR_DTYPE) -class MF4Reader(MessageReader): +class MF4Reader(BinaryIOMessageReader): """ Iterator of CAN messages from a MF4 logging file. diff --git a/can/io/player.py b/can/io/player.py index e4db0e167..5b9dc060a 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -16,7 +16,7 @@ from .blf import BLFReader from .canutils import CanutilsLogReader from .csv import CSVReader -from .generic import MessageReader +from .generic import BinaryIOMessageReader, MessageReader from .mf4 import MF4Reader from .sqlite import SqliteReader from .trc import TRCReader @@ -87,7 +87,13 @@ def __new__( # type: ignore file_or_filename: AcceptedIOType = filename if suffix == ".gz": - suffix, file_or_filename = LogReader.decompress(filename) + ReaderType, file_or_filename = LogReader.decompress(filename) + else: + ReaderType = cls._get_logger_for_suffix(suffix) + return ReaderType(file=file_or_filename, **kwargs) + + @classmethod + def _get_logger_for_suffix(cls, suffix: str) -> typing.Type[MessageReader]: try: ReaderType = LogReader.message_readers[suffix] except KeyError: @@ -96,19 +102,22 @@ def __new__( # type: ignore ) from None if ReaderType is None: raise ImportError(f"failed to import reader for extension {suffix}") - return ReaderType(file=file_or_filename, **kwargs) + return ReaderType - @staticmethod + @classmethod def decompress( + cls, filename: StringPathLike, - ) -> typing.Tuple[str, typing.Union[str, FileLike]]: + ) -> typing.Tuple[typing.Type[MessageReader], typing.Union[str, FileLike]]: """ Return the suffix and io object of the decompressed file. """ real_suffix = pathlib.Path(filename).suffixes[-2].lower() - mode = "rb" if real_suffix == ".blf" else "rt" + ReaderType = cls._get_logger_for_suffix(real_suffix) + + mode = "rb" if issubclass(ReaderType, BinaryIOMessageReader) else "rt" - return real_suffix, gzip.open(filename, mode) + return ReaderType, gzip.open(filename, mode) def __iter__(self) -> typing.Generator[Message, None, None]: raise NotImplementedError() diff --git a/can/io/trc.py b/can/io/trc.py index fc2a9e1f7..f116bdc04 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -16,7 +16,10 @@ from ..message import Message from ..typechecking import StringPathLike from ..util import channel2int, dlc2len, len2dlc -from .generic import FileIOMessageWriter, MessageReader +from .generic import ( + TextIOMessageReader, + TextIOMessageWriter, +) logger = logging.getLogger("can.io.trc") @@ -36,7 +39,7 @@ def __ge__(self, other): return NotImplemented -class TRCReader(MessageReader): +class TRCReader(TextIOMessageReader): """ Iterator of CAN messages from a TRC logging file. """ @@ -241,7 +244,7 @@ def __iter__(self) -> Generator[Message, None, None]: self.stop() -class TRCWriter(FileIOMessageWriter): +class TRCWriter(TextIOMessageWriter): """Logs CAN data to text file (.trc). The measurement starts with the timestamp of the first registered message. From 1a3f5e3769aa565ada8c27177a94f7db43d019dc Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 20 May 2023 14:41:00 +0200 Subject: [PATCH 1046/1235] Raise Minimum Python Version to 3.8 (#1597) * raise minimum python version to 3.8 * try to fix entry points * use walrus and hasattr * update supported versions --- .github/workflows/ci.yml | 5 ----- README.rst | 3 ++- can/__init__.py | 1 + can/_entry_points.py | 34 ++++++++++++++++++++++++++++++ can/broadcastmanager.py | 4 +--- can/bus.py | 25 +++++++++++++++++++--- can/interfaces/__init__.py | 43 +++++++++----------------------------- can/io/generic.py | 5 +++-- can/io/logger.py | 30 +++++++++++++------------- can/io/player.py | 43 +++++++++++++++++++------------------- can/notifier.py | 22 +++++++------------ can/thread_safe_bus.py | 21 ++----------------- can/typechecking.py | 12 +++++------ pyproject.toml | 19 +++++++---------- tox.ini | 4 ++-- 15 files changed, 133 insertions(+), 138 deletions(-) create mode 100644 can/_entry_points.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f143234b..9633398e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,12 +18,10 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] experimental: [false] python-version: [ - "3.7", "3.8", "3.9", "3.10", "3.11", - "pypy-3.7", "pypy-3.8", "pypy-3.9", ] @@ -85,9 +83,6 @@ jobs: run: | python -m pip install --upgrade pip pip install -e .[lint] - - name: mypy 3.7 - run: | - mypy --python-version 3.7 . - name: mypy 3.8 run: | mypy --python-version 3.8 . diff --git a/README.rst b/README.rst index e7d40f590..07d2b0668 100644 --- a/README.rst +++ b/README.rst @@ -62,7 +62,8 @@ Library Version Python ------------------------------ ----------- 2.x 2.6+, 3.4+ 3.x 2.7+, 3.5+ - 4.x 3.7+ + 4.0+ 3.7+ + 4.3+ 3.8+ ============================== =========== diff --git a/can/__init__.py b/can/__init__.py index a6691eecb..34db1727c 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -61,6 +61,7 @@ "exceptions", "interface", "interfaces", + "io", "listener", "logconvert", "log", diff --git a/can/_entry_points.py b/can/_entry_points.py new file mode 100644 index 000000000..6842e3c1a --- /dev/null +++ b/can/_entry_points.py @@ -0,0 +1,34 @@ +import importlib +import sys +from dataclasses import dataclass +from importlib.metadata import entry_points +from typing import Any, List + + +@dataclass +class _EntryPoint: + key: str + module_name: str + class_name: str + + def load(self) -> Any: + module = importlib.import_module(self.module_name) + return getattr(module, self.class_name) + + +# See https://docs.python.org/3/library/importlib.metadata.html#entry-points, +# "Compatibility Note". +if sys.version_info >= (3, 10): + + def read_entry_points(group: str) -> List[_EntryPoint]: + return [ + _EntryPoint(ep.name, ep.module, ep.attr) for ep in entry_points(group=group) + ] + +else: + + def read_entry_points(group: str) -> List[_EntryPoint]: + return [ + _EntryPoint(ep.name, *ep.value.split(":", maxsplit=1)) + for ep in entry_points().get(group, []) + ] diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index ae47fc048..84554a507 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -10,9 +10,7 @@ import sys import threading import time -from typing import TYPE_CHECKING, Callable, Optional, Sequence, Tuple, Union - -from typing_extensions import Final +from typing import TYPE_CHECKING, Callable, Final, Optional, Sequence, Tuple, Union from can import typechecking from can.message import Message diff --git a/can/bus.py b/can/bus.py index 9c65ad52f..555389b0f 100644 --- a/can/bus.py +++ b/can/bus.py @@ -8,7 +8,21 @@ from abc import ABC, ABCMeta, abstractmethod from enum import Enum, auto from time import time -from typing import Any, Callable, Iterator, List, Optional, Sequence, Tuple, Union, cast +from types import TracebackType +from typing import ( + Any, + Callable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) + +from typing_extensions import Self import can import can.typechecking @@ -450,10 +464,15 @@ def shutdown(self) -> None: self._is_shutdown = True self.stop_all_periodic_tasks() - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: self.shutdown() def __del__(self) -> None: diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index b14914230..f220d28e5 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -2,8 +2,9 @@ Interfaces contain low level implementations that interact with CAN hardware. """ -import sys -from typing import Dict, Tuple, cast +from typing import Dict, Tuple + +from can._entry_points import read_entry_points __all__ = [ "BACKENDS", @@ -60,36 +61,12 @@ "socketcand": ("can.interfaces.socketcand", "SocketCanDaemonBus"), } -if sys.version_info >= (3, 8): - from importlib.metadata import entry_points - - # See https://docs.python.org/3/library/importlib.metadata.html#entry-points, - # "Compatibility Note". - if sys.version_info >= (3, 10): - BACKENDS.update( - { - interface.name: (interface.module, interface.attr) - for interface in entry_points(group="can.interface") - } - ) - else: - # The entry_points().get(...) causes a deprecation warning on Python >= 3.10. - BACKENDS.update( - { - interface.name: cast( - Tuple[str, str], tuple(interface.value.split(":", maxsplit=1)) - ) - for interface in entry_points().get("can.interface", []) - } - ) -else: - from pkg_resources import iter_entry_points - BACKENDS.update( - { - interface.name: (interface.module_name, interface.attrs[0]) - for interface in iter_entry_points("can.interface") - } - ) +BACKENDS.update( + { + interface.key: (interface.module_name, interface.class_name) + for interface in read_entry_points(group="can.interface") + } +) -VALID_INTERFACES = frozenset(BACKENDS.keys()) +VALID_INTERFACES = frozenset(sorted(BACKENDS.keys())) diff --git a/can/io/generic.py b/can/io/generic.py index eb9647474..4d877865e 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -8,6 +8,7 @@ BinaryIO, ContextManager, Iterable, + Literal, Optional, TextIO, Type, @@ -15,7 +16,7 @@ cast, ) -from typing_extensions import Literal +from typing_extensions import Self from .. import typechecking from ..listener import Listener @@ -65,7 +66,7 @@ def __init__( # for multiple inheritance super().__init__() - def __enter__(self) -> "BaseIOHandler": + def __enter__(self) -> Self: return self def __exit__( diff --git a/can/io/logger.py b/can/io/logger.py index 7075a7ee2..c3e83c883 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -8,11 +8,11 @@ from abc import ABC, abstractmethod from datetime import datetime from types import TracebackType -from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, cast +from typing import Any, Callable, Dict, Literal, Optional, Set, Tuple, Type, cast -from pkg_resources import iter_entry_points -from typing_extensions import Literal +from typing_extensions import Self +from .._entry_points import read_entry_points from ..listener import Listener from ..message import Message from ..typechecking import AcceptedIOType, FileLike, StringPathLike @@ -89,8 +89,8 @@ def __new__( # type: ignore if not Logger.fetched_plugins: Logger.message_writers.update( { - writer.name: writer.load() - for writer in iter_entry_points("can.io.message_writer") + writer.key: cast(Type[MessageWriter], writer.load()) + for writer in read_entry_points("can.io.message_writer") } ) Logger.fetched_plugins = True @@ -99,19 +99,19 @@ def __new__( # type: ignore file_or_filename: AcceptedIOType = filename if suffix == ".gz": - LoggerType, file_or_filename = Logger.compress(filename, **kwargs) + logger_type, file_or_filename = Logger.compress(filename, **kwargs) else: - LoggerType = cls._get_logger_for_suffix(suffix) + logger_type = cls._get_logger_for_suffix(suffix) - return LoggerType(file=file_or_filename, **kwargs) + return logger_type(file=file_or_filename, **kwargs) @classmethod def _get_logger_for_suffix(cls, suffix: str) -> Type[MessageWriter]: try: - LoggerType = Logger.message_writers[suffix] - if LoggerType is None: + logger_type = Logger.message_writers[suffix] + if logger_type is None: raise ValueError(f'failed to import logger for extension "{suffix}"') - return LoggerType + return logger_type except KeyError: raise ValueError( f'No write support for this unknown log format "{suffix}"' @@ -130,15 +130,15 @@ def compress( raise ValueError( f"The file type {real_suffix} is currently incompatible with gzip." ) - LoggerType = cls._get_logger_for_suffix(real_suffix) + logger_type = cls._get_logger_for_suffix(real_suffix) append = kwargs.get("append", False) - if issubclass(LoggerType, BinaryIOMessageWriter): + if issubclass(logger_type, BinaryIOMessageWriter): mode = "ab" if append else "wb" else: mode = "at" if append else "wt" - return LoggerType, gzip.open(filename, mode) + return logger_type, gzip.open(filename, mode) def on_message_received(self, msg: Message) -> None: pass @@ -275,7 +275,7 @@ def stop(self) -> None: """ self.writer.stop() - def __enter__(self) -> "BaseRotatingLogger": + def __enter__(self) -> Self: return self def __exit__( diff --git a/can/io/player.py b/can/io/player.py index 5b9dc060a..9fd9d9ed3 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -6,10 +6,9 @@ import gzip import pathlib import time -import typing - -from pkg_resources import iter_entry_points +from typing import Any, Dict, Generator, Iterable, Optional, Tuple, Type, Union, cast +from .._entry_points import read_entry_points from ..message import Message from ..typechecking import AcceptedIOType, FileLike, StringPathLike from .asc import ASCReader @@ -54,7 +53,7 @@ class LogReader(MessageReader): """ fetched_plugins = False - message_readers: typing.Dict[str, typing.Optional[typing.Type[MessageReader]]] = { + message_readers: Dict[str, Optional[Type[MessageReader]]] = { ".asc": ASCReader, ".blf": BLFReader, ".csv": CSVReader, @@ -66,9 +65,9 @@ class LogReader(MessageReader): @staticmethod def __new__( # type: ignore - cls: typing.Any, + cls: Any, filename: StringPathLike, - **kwargs: typing.Any, + **kwargs: Any, ) -> MessageReader: """ :param filename: the filename/path of the file to read from @@ -77,8 +76,8 @@ def __new__( # type: ignore if not LogReader.fetched_plugins: LogReader.message_readers.update( { - reader.name: reader.load() - for reader in iter_entry_points("can.io.message_reader") + reader.key: cast(Type[MessageReader], reader.load()) + for reader in read_entry_points("can.io.message_reader") } ) LogReader.fetched_plugins = True @@ -87,39 +86,39 @@ def __new__( # type: ignore file_or_filename: AcceptedIOType = filename if suffix == ".gz": - ReaderType, file_or_filename = LogReader.decompress(filename) + reader_type, file_or_filename = LogReader.decompress(filename) else: - ReaderType = cls._get_logger_for_suffix(suffix) - return ReaderType(file=file_or_filename, **kwargs) + reader_type = cls._get_logger_for_suffix(suffix) + return reader_type(file=file_or_filename, **kwargs) @classmethod - def _get_logger_for_suffix(cls, suffix: str) -> typing.Type[MessageReader]: + def _get_logger_for_suffix(cls, suffix: str) -> Type[MessageReader]: try: - ReaderType = LogReader.message_readers[suffix] + reader_type = LogReader.message_readers[suffix] except KeyError: raise ValueError( f'No read support for this unknown log format "{suffix}"' ) from None - if ReaderType is None: + if reader_type is None: raise ImportError(f"failed to import reader for extension {suffix}") - return ReaderType + return reader_type @classmethod def decompress( cls, filename: StringPathLike, - ) -> typing.Tuple[typing.Type[MessageReader], typing.Union[str, FileLike]]: + ) -> Tuple[Type[MessageReader], Union[str, FileLike]]: """ Return the suffix and io object of the decompressed file. """ real_suffix = pathlib.Path(filename).suffixes[-2].lower() - ReaderType = cls._get_logger_for_suffix(real_suffix) + reader_type = cls._get_logger_for_suffix(real_suffix) - mode = "rb" if issubclass(ReaderType, BinaryIOMessageReader) else "rt" + mode = "rb" if issubclass(reader_type, BinaryIOMessageReader) else "rt" - return ReaderType, gzip.open(filename, mode) + return reader_type, gzip.open(filename, mode) - def __iter__(self) -> typing.Generator[Message, None, None]: + def __iter__(self) -> Generator[Message, None, None]: raise NotImplementedError() @@ -130,7 +129,7 @@ class MessageSync: def __init__( self, - messages: typing.Iterable[Message], + messages: Iterable[Message], timestamps: bool = True, gap: float = 0.0001, skip: float = 60.0, @@ -148,7 +147,7 @@ def __init__( self.gap = gap self.skip = skip - def __iter__(self) -> typing.Generator[Message, None, None]: + def __iter__(self) -> Generator[Message, None, None]: t_wakeup = playback_start_time = time.perf_counter() recorded_start_time = None t_skipped = 0.0 diff --git a/can/notifier.py b/can/notifier.py index fce210f49..92a91f8a9 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -104,14 +104,13 @@ def stop(self, timeout: float = 5) -> None: # reader is a file descriptor self._loop.remove_reader(reader) for listener in self.listeners: - # Mypy prefers this over a hasattr(...) check - getattr(listener, "stop", lambda: None)() + if hasattr(listener, "stop"): + listener.stop() def _rx_thread(self, bus: BusABC) -> None: - msg = None try: while self._running: - if msg is not None: + if msg := bus.recv(self.timeout): with self._lock: if self._loop is not None: self._loop.call_soon_threadsafe( @@ -119,12 +118,11 @@ def _rx_thread(self, bus: BusABC) -> None: ) else: self._on_message_received(msg) - msg = bus.recv(self.timeout) except Exception as exc: # pylint: disable=broad-except self.exception = exc if self._loop is not None: self._loop.call_soon_threadsafe(self._on_error, exc) - # Raise anyways + # Raise anyway raise elif not self._on_error(exc): # If it was not handled, raise the exception here @@ -134,14 +132,13 @@ def _rx_thread(self, bus: BusABC) -> None: logger.info("suppressed exception: %s", exc) def _on_message_available(self, bus: BusABC) -> None: - msg = bus.recv(0) - if msg is not None: + if msg := bus.recv(0): self._on_message_received(msg) def _on_message_received(self, msg: Message) -> None: for callback in self.listeners: res = callback(msg) - if res is not None and self._loop is not None and asyncio.iscoroutine(res): + if res and self._loop and asyncio.iscoroutine(res): # Schedule coroutine self._loop.create_task(res) @@ -153,12 +150,9 @@ def _on_error(self, exc: Exception) -> bool: was_handled = False for listener in self.listeners: - on_error = getattr( - listener, "on_error", None - ) # Mypy prefers this over hasattr(...) - if on_error is not None: + if hasattr(listener, "on_error"): try: - on_error(exc) + listener.on_error(exc) except NotImplementedError: pass else: diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 4793ed1ff..9b008667f 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -10,26 +10,9 @@ ObjectProxy = object import_exc = exc -from .interface import Bus - -try: - from contextlib import nullcontext - -except ImportError: +from contextlib import nullcontext - class nullcontext: # type: ignore - """A context manager that does nothing at all. - A fallback for Python 3.7's :class:`contextlib.nullcontext` manager. - """ - - def __init__(self, enter_result=None): - self.enter_result = enter_result - - def __enter__(self): - return self.enter_result - - def __exit__(self, *args): - pass +from .interface import Bus class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method diff --git a/can/typechecking.py b/can/typechecking.py index dc5c22270..29c760d31 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -6,15 +6,13 @@ if typing.TYPE_CHECKING: import os -import typing_extensions - -class CanFilter(typing_extensions.TypedDict): +class CanFilter(typing.TypedDict): can_id: int can_mask: int -class CanFilterExtended(typing_extensions.TypedDict): +class CanFilterExtended(typing.TypedDict): can_id: int can_mask: int extended: bool @@ -42,7 +40,7 @@ class CanFilterExtended(typing_extensions.TypedDict): BusConfig = typing.NewType("BusConfig", typing.Dict[str, typing.Any]) -class AutoDetectedConfig(typing_extensions.TypedDict): +class AutoDetectedConfig(typing.TypedDict): interface: str channel: Channel @@ -50,7 +48,7 @@ class AutoDetectedConfig(typing_extensions.TypedDict): ReadableBytesLike = typing.Union[bytes, bytearray, memoryview] -class BitTimingDict(typing_extensions.TypedDict): +class BitTimingDict(typing.TypedDict): f_clock: int brp: int tseg1: int @@ -59,7 +57,7 @@ class BitTimingDict(typing_extensions.TypedDict): nof_samples: int -class BitTimingFdDict(typing_extensions.TypedDict): +class BitTimingFdDict(typing.TypedDict): f_clock: int nom_brp: int nom_tseg1: int diff --git a/pyproject.toml b/pyproject.toml index 65b8f8aed..9ae2de98a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 67.7.2"] +requires = ["setuptools >= 67.7"] build-backend = "setuptools.build_meta" [project] @@ -10,14 +10,13 @@ authors = [{ name = "python-can contributors" }] dependencies = [ "wrapt~=1.10", "packaging >= 23.1", - "setuptools >= 67.7.2", "typing_extensions>=3.10.0.0", "msgpack~=1.0.0; platform_system != 'Windows'", "pywin32>=305; platform_system == 'Windows' and platform_python_implementation == 'CPython'", ] -requires-python = ">=3.7" +requires-python = ">=3.8" license = { text = "LGPL v3" } -classifiers = [ +classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", @@ -32,7 +31,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -60,12 +58,10 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ - "pylint==2.16.4", - "ruff==0.0.260", - "black~=23.1.0", - "mypy==1.0.1", - "mypy-extensions==0.4.3", - "types-setuptools" + "pylint==2.17.*", + "ruff==0.0.267", + "black==23.3.*", + "mypy==1.3.*", ] seeedstudio = ["pyserial>=3.0"] serial = ["pyserial~=3.0"] @@ -135,7 +131,6 @@ select = [ "F401", # unused-imports "UP", # pyupgrade "I", # isort - ] [tool.ruff.isort] diff --git a/tox.ini b/tox.ini index b48b5642f..477b1d4fc 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,8 @@ isolated_build = true [testenv] deps = - pytest==7.1.*,>=7.1.2 - pytest-timeout==2.0.2 + pytest==7.3.* + pytest-timeout==2.1.* coveralls==3.3.1 pytest-cov==4.0.0 coverage==6.5.0 From b291f806ec7ce02e3c8256b3102d3a0e7f22f85d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 20 May 2023 19:23:49 +0200 Subject: [PATCH 1047/1235] Fix socketcan KeyError (#1599) * use get * adapt test for issue #1598 --- can/interfaces/socketcan/utils.py | 2 +- test/data/ip_link_list.json | 91 +++++++++++++++++++++++++++++++ test/test_socketcan_helpers.py | 15 +---- 3 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 test/data/ip_link_list.json diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 8b2114692..91878f9b6 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -66,7 +66,7 @@ def find_available_interfaces() -> List[str]: output_json, ) - interfaces = [i["ifname"] for i in output_json if i["link_type"] == "can"] + interfaces = [i["ifname"] for i in output_json if i.get("link_type") == "can"] return interfaces diff --git a/test/data/ip_link_list.json b/test/data/ip_link_list.json new file mode 100644 index 000000000..a96313b43 --- /dev/null +++ b/test/data/ip_link_list.json @@ -0,0 +1,91 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "linkmode": "DEFAULT", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00" + }, + { + "ifindex": 2, + "ifname": "eth0", + "flags": [ + "NO-CARRIER", + "BROADCAST", + "MULTICAST", + "UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "operstate": "DOWN", + "linkmode": "DEFAULT", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "11:22:33:44:55:66", + "broadcast": "ff:ff:ff:ff:ff:ff" + }, + { + "ifindex": 3, + "ifname": "wlan0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "linkmode": "DORMANT", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "11:22:33:44:55:66", + "broadcast": "ff:ff:ff:ff:ff:ff" + }, + { + "ifindex": 48, + "ifname": "vcan0", + "flags": [ + "NOARP", + "UP", + "LOWER_UP" + ], + "mtu": 72, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "linkmode": "DEFAULT", + "group": "default", + "txqlen": 1000, + "link_type": "can" + }, + { + "ifindex": 50, + "ifname": "mycustomCan123", + "flags": [ + "NOARP", + "UP", + "LOWER_UP" + ], + "mtu": 72, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "linkmode": "DEFAULT", + "group": "default", + "txqlen": 1000, + "link_type": "can" + }, + {} +] \ No newline at end of file diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index 0f4e1b4ea..a1d0bc8af 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -4,9 +4,8 @@ Tests helpers in `can.interfaces.socketcan.socketcan_common`. """ -import gzip import unittest -from base64 import b64decode +from pathlib import Path from unittest import mock from can.interfaces.socketcan.utils import error_code_to_str, find_available_interfaces @@ -42,17 +41,7 @@ def test_find_available_interfaces(self): def test_find_available_interfaces_w_patch(self): # Contains lo, eth0, wlan0, vcan0, mycustomCan123 - ip_output_gz_b64 = ( - "H4sIAAAAAAAAA+2UzW+CMBjG7/wVhrNL+BC29IboEqNSwzQejDEViiMC5aNsmmX/+wpZTGUwDAcP" - "y5qmh+d5++bN80u7EXpsfZRnsUTf8yMXn0TQk/u8GqEQM1EMiMjpXoAOGZM3F6mUZxAuhoY55UpL" - "fbWoKjO4Hts7pl/kLdc+pDlrrmuaqnNq4vqZU8wSkSTHOeYHIjFOM4poOevKmlpwbfF+4EfHkLil" - "PRo/G6vZkrcPKcnjwnOxh/KA8h49JQGOimAkSaq03NFz/B0PiffIOfIXkeumOCtiEiUJXG++bp8S" - "5Dooo/WVZeFnvxmYUgsM01fpBmQWfDAN256M7SqioQ2NkWm8LKvGnIU3qTN+xylrV/FdaHrJzmFk" - "gkacozuzZMnhtAGkLANFAaoKBgOgaUDXG0F6Hrje7SDVWpDvAYpuIdmJV4dn2cSx9VUuGiFCe25Y" - "fwTi4KmW4ptzG0ULGvYPLN1APSqdMN3/82TRtOeqSbW5hmcnzygJTRTJivofcEvAgrAVvgD8aLkv" - "/AcAAA==" - ) - ip_output = gzip.decompress(b64decode(ip_output_gz_b64)).decode("ascii") + ip_output = (Path(__file__).parent / "data" / "ip_link_list.json").read_text() with mock.patch("subprocess.check_output") as check_output: check_output.return_value = ip_output From 88a0dbfa03ae8c9f64aa72ce439a95ac549861d5 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 20 May 2023 20:29:37 +0200 Subject: [PATCH 1048/1235] activate ruff pycodestyle checks (#1602) --- can/__init__.py | 8 +++--- can/interfaces/ixxat/canlib_vcinpl2.py | 9 +++---- can/interfaces/socketcan/socketcan.py | 23 +++++++++-------- can/interfaces/vector/canlib.py | 34 ++++++++++---------------- can/interfaces/vector/xldriver.py | 9 +------ pyproject.toml | 7 +++++- 6 files changed, 39 insertions(+), 51 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 34db1727c..034f388a1 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -76,10 +76,6 @@ "viewer", ] -log = logging.getLogger("can") - -rc: Dict[str, Any] = {} - from . import typechecking # isort:skip from . import util # isort:skip from . import broadcastmanager, interface @@ -127,3 +123,7 @@ from .notifier import Notifier from .thread_safe_bus import ThreadSafeBus from .util import set_logging_level + +log = logging.getLogger("can") + +rc: Dict[str, Any] = {} diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index b796be744..2e6e9ad8e 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -13,6 +13,7 @@ import functools import logging import sys +import time import warnings from typing import Callable, Optional, Sequence, Tuple, Union @@ -43,8 +44,6 @@ log = logging.getLogger("can.ixxat") -from time import perf_counter - # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -144,7 +143,7 @@ def __check_status(result, function, args): _canlib.map_symbol( "vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32) ) - except: + except ImportError: _canlib.map_symbol( "vciFormatErrorA", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32) ) @@ -812,7 +811,7 @@ def _recv_internal(self, timeout): else: timeout_ms = int(timeout * 1000) remaining_ms = timeout_ms - t0 = perf_counter() + t0 = time.perf_counter() while True: try: @@ -861,7 +860,7 @@ def _recv_internal(self, timeout): log.warning("Unexpected message info type") if t0 is not None: - remaining_ms = timeout_ms - int((perf_counter() - t0) * 1000) + remaining_ms = timeout_ms - int((time.perf_counter() - t0) * 1000) if remaining_ms < 0: break diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 44cecec76..377fe6478 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -17,6 +17,17 @@ import warnings from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type, Union +import can +from can import BusABC, CanProtocol, Message +from can.broadcastmanager import ( + LimitedDurationCyclicSendTaskABC, + ModifiableCyclicTaskABC, + RestartableCyclicTaskABC, +) +from can.interfaces.socketcan import constants +from can.interfaces.socketcan.utils import find_available_interfaces, pack_filters +from can.typechecking import CanFilters + log = logging.getLogger(__name__) log_tx = log.getChild("tx") log_rx = log.getChild("rx") @@ -30,18 +41,6 @@ log.error("socket.CMSG_SPACE not available on this platform") -import can -from can import BusABC, CanProtocol, Message -from can.broadcastmanager import ( - LimitedDurationCyclicSendTaskABC, - ModifiableCyclicTaskABC, - RestartableCyclicTaskABC, -) -from can.interfaces.socketcan import constants -from can.interfaces.socketcan.utils import find_available_interfaces, pack_filters -from can.typechecking import CanFilters - - # Setup BCM struct def bcm_header_factory( fields: List[Tuple[str, Union[Type[ctypes.c_uint32], Type[ctypes.c_long]]]], diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index f852fb0ec..1a84f3d2a 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -4,8 +4,6 @@ Authors: Julien Grave , Christian Sandberg """ -# Import Standard Python Modules -# ============================== import contextlib import ctypes import logging @@ -26,19 +24,6 @@ cast, ) -WaitForSingleObject: Optional[Callable[[int, int], int]] -INFINITE: Optional[int] -try: - # Try builtin Python 3 Windows API - from _winapi import INFINITE, WaitForSingleObject # type: ignore - - HAS_EVENTS = True -except ImportError: - WaitForSingleObject, INFINITE = None, None - HAS_EVENTS = False - -# Import Modules -# ============== from can import ( BitTiming, BitTimingFd, @@ -57,15 +42,11 @@ time_perfcounter_correlation, ) -# Define Module Logger -# ==================== -LOG = logging.getLogger(__name__) - -# Import Vector API modules -# ========================= from . import xlclass, xldefine from .exceptions import VectorError, VectorInitializationError, VectorOperationError +LOG = logging.getLogger(__name__) + # Import safely Vector API module for Travis tests xldriver: Optional[ModuleType] = None try: @@ -73,6 +54,17 @@ except Exception as exc: LOG.warning("Could not import vxlapi: %s", exc) +WaitForSingleObject: Optional[Callable[[int, int], int]] +INFINITE: Optional[int] +try: + # Try builtin Python 3 Windows API + from _winapi import INFINITE, WaitForSingleObject # type: ignore + + HAS_EVENTS = True +except ImportError: + WaitForSingleObject, INFINITE = None, None + HAS_EVENTS = False + class VectorBus(BusABC): """The CAN Bus implemented for the Vector interface.""" diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index f84b3cf1d..2af90c728 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -5,22 +5,15 @@ Authors: Julien Grave , Christian Sandberg """ -# Import Standard Python Modules -# ============================== import ctypes import logging import platform +from . import xlclass from .exceptions import VectorInitializationError, VectorOperationError -# 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) diff --git a/pyproject.toml b/pyproject.toml index 9ae2de98a..8427c5655 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==2.17.*", - "ruff==0.0.267", + "ruff==0.0.269", "black==23.3.*", "mypy==1.3.*", ] @@ -131,6 +131,11 @@ select = [ "F401", # unused-imports "UP", # pyupgrade "I", # isort + "E", # pycodestyle errors + "W", # pycodestyle warnings +] +ignore = [ + "E501", # Line too long ] [tool.ruff.isort] From e8aa4e790ed7029a1706134a58242c16662ad761 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 20 May 2023 20:31:24 +0200 Subject: [PATCH 1049/1235] Update linter instructions in development.rst (#1603) --- doc/development.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/development.rst b/doc/development.rst index cfb8dbe5d..fb717a52d 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -53,9 +53,11 @@ The documentation can be built with:: The linters can be run with:: - pip install -r requirements-lint.txt - pylint --rcfile=.pylintrc-wip can/**.py - black --check --verbose can + pip install -e .[lint] + black --check can + mypy can + ruff check can + pylint --rcfile=.pylintrc can/**.py Creating a new interface/backend From 2582fe975c189471b0c439c1488909d48b219bdf Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 21 May 2023 14:01:08 +0200 Subject: [PATCH 1050/1235] remove unnecessary script files (#1604) --- doc/scripts.rst | 2 +- pyproject.toml | 8 ++++---- scripts/can_logconvert.py | 11 ----------- scripts/can_logger.py | 11 ----------- scripts/can_player.py | 11 ----------- scripts/can_viewer.py | 11 ----------- test/test_scripts.py | 12 +++--------- 7 files changed, 8 insertions(+), 58 deletions(-) delete mode 100644 scripts/can_logconvert.py delete mode 100644 scripts/can_logger.py delete mode 100644 scripts/can_player.py delete mode 100644 scripts/can_viewer.py diff --git a/doc/scripts.rst b/doc/scripts.rst index 5a615afa7..e3a59a409 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -3,7 +3,7 @@ Scripts The following modules are callable from ``python-can``. -They can be called for example by ``python -m can.logger`` or ``can_logger.py`` (if installed using pip). +They can be called for example by ``python -m can.logger`` or ``can_logger`` (if installed using pip). can.logger ---------- diff --git a/pyproject.toml b/pyproject.toml index 8427c5655..6ee1dbadc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,10 +45,10 @@ classifiers = [ ] [project.scripts] -"can_logconvert.py" = "scripts.can_logconvert:main" -"can_logger.py" = "scripts.can_logger:main" -"can_player.py" = "scripts.can_player:main" -"can_viewer.py" = "scripts.can_viewer:main" +can_logconvert = "can.logconvert:main" +can_logger = "can.logger:main" +can_player = "can.player:main" +can_viewer = "can.viewer:main" [project.urls] homepage = "https://github.com/hardbyte/python-can" diff --git a/scripts/can_logconvert.py b/scripts/can_logconvert.py deleted file mode 100644 index 3cd8839a2..000000000 --- a/scripts/can_logconvert.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -""" -See :mod:`can.logconvert`. -""" - -from can.logconvert import main - - -if __name__ == "__main__": - main() diff --git a/scripts/can_logger.py b/scripts/can_logger.py deleted file mode 100644 index 4202448e6..000000000 --- a/scripts/can_logger.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -""" -See :mod:`can.logger`. -""" - -from can.logger import main - - -if __name__ == "__main__": - main() diff --git a/scripts/can_player.py b/scripts/can_player.py deleted file mode 100644 index 1fe44175d..000000000 --- a/scripts/can_player.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -""" -See :mod:`can.player`. -""" - -from can.player import main - - -if __name__ == "__main__": - main() diff --git a/scripts/can_viewer.py b/scripts/can_viewer.py deleted file mode 100644 index eef990b0e..000000000 --- a/scripts/can_viewer.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -""" -See :mod:`can.viewer`. -""" - -from can.viewer import main - - -if __name__ == "__main__": - main() diff --git a/test/test_scripts.py b/test/test_scripts.py index e7bd7fd09..9d8c059cf 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -74,10 +74,8 @@ class TestLoggerScript(CanScriptTest): def _commands(self): commands = [ "python -m can.logger --help", - "python scripts/can_logger.py --help", + "can_logger --help", ] - if IS_UNIX: - commands += ["can_logger.py --help"] return commands def _import(self): @@ -90,10 +88,8 @@ class TestPlayerScript(CanScriptTest): def _commands(self): commands = [ "python -m can.player --help", - "python scripts/can_player.py --help", + "can_player --help", ] - if IS_UNIX: - commands += ["can_player.py --help"] return commands def _import(self): @@ -106,10 +102,8 @@ class TestLogconvertScript(CanScriptTest): def _commands(self): commands = [ "python -m can.logconvert --help", - "python scripts/can_logconvert.py --help", + "can_logconvert --help", ] - if IS_UNIX: - commands += ["can_logconvert.py --help"] return commands def _import(self): From 94125139002af2f1a11d13186ab653821c954ebd Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:07:10 +0200 Subject: [PATCH 1051/1235] fix IXXAT not properly shut down message (#1606) --- can/interfaces/ixxat/canlib_vcinpl.py | 1 + can/interfaces/ixxat/canlib_vcinpl2.py | 1 + 2 files changed, 2 insertions(+) diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index cbf2fb61c..1bb0fd802 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -825,6 +825,7 @@ def _send_periodic_internal( ) def shutdown(self): + super().shutdown() if self._scheduler is not None: _canlib.canSchedulerClose(self._scheduler) _canlib.canChannelClose(self._channel_handle) diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 2e6e9ad8e..b10ac1b94 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -972,6 +972,7 @@ def _send_periodic_internal( ) def shutdown(self): + super().shutdown() if self._scheduler is not None: _canlib.canSchedulerClose(self._scheduler) _canlib.canChannelClose(self._channel_handle) From 1df66043926dfe455b09941cf75dfeb13b2d2bbc Mon Sep 17 00:00:00 2001 From: MattWoodhead Date: Fri, 2 Jun 2023 17:34:26 +0100 Subject: [PATCH 1052/1235] Can Player compatibility with interfaces that use additional configuration (#1610) * Update mf4.py * Format code with black * Update trc.py * Format code with black * Update ci.yml Remove pylint specs for directories and files that no longer exist. --------- Co-authored-by: MattWoodhead --- .github/workflows/ci.yml | 2 -- can/io/mf4.py | 6 +++++- can/io/trc.py | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9633398e3..686a5d77b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,9 +103,7 @@ jobs: pylint --rcfile=.pylintrc \ can/**.py \ can/io \ - setup.py \ doc/conf.py \ - scripts/**.py \ examples/**.py \ can/interfaces/socketcan diff --git a/can/io/mf4.py b/can/io/mf4.py index 215543e9f..c7e71e816 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -272,7 +272,11 @@ class MF4Reader(BinaryIOMessageReader): The MF4Reader only supports MF4 files that were recorded with python-can. """ - def __init__(self, file: Union[StringPathLike, BinaryIO]) -> None: + def __init__( + self, + file: Union[StringPathLike, BinaryIO], + **kwargs: Any, + ) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to be opened in diff --git a/can/io/trc.py b/can/io/trc.py index f116bdc04..ccf122d57 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -11,7 +11,7 @@ import os from datetime import datetime, timedelta, timezone from enum import Enum -from typing import Callable, Dict, Generator, List, Optional, TextIO, Union +from typing import Any, Callable, Dict, Generator, List, Optional, TextIO, Union from ..message import Message from ..typechecking import StringPathLike @@ -49,6 +49,7 @@ class TRCReader(TextIOMessageReader): def __init__( self, file: Union[StringPathLike, TextIO], + **kwargs: Any, ) -> None: """ :param file: a path-like object or as file-like object to read from @@ -265,6 +266,7 @@ def __init__( self, file: Union[StringPathLike, TextIO], channel: int = 1, + **kwargs: Any, ) -> None: """ :param file: a path-like object or as file-like object to write to From 67324f158318e613ba3226704a9985124dd3caff Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Tue, 13 Jun 2023 22:15:19 +0200 Subject: [PATCH 1053/1235] Fix decoding error in Kvaser constructor for non-ASCII product name (#1613) * Implement test to trigger ASCII decoding error in Kvaser bus constructor * Change handling of invalid chars in Kvaser device name Previously, illegal characters triggered an exception. With the new behavior, illegal characters will be replaced with a placeholder value. * Rename variables in test --- can/interfaces/kvaser/canlib.py | 3 ++- test/test_kvaser.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 32d28059a..4e5e8c51b 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -727,7 +727,8 @@ def get_channel_info(channel): ctypes.sizeof(number), ) - return f"{name.value.decode('ascii')}, S/N {serial.value} (#{number.value + 1})" + name_decoded = name.value.decode("ascii", errors="replace") + return f"{name_decoded}, S/N {serial.value} (#{number.value + 1})" init_kvaser_library() diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 043f86f8c..1254f2fc7 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -3,6 +3,7 @@ """ """ +import ctypes import time import unittest from unittest.mock import Mock @@ -49,6 +50,26 @@ def test_bus_creation(self): self.assertTrue(canlib.canOpenChannel.called) self.assertTrue(canlib.canBusOn.called) + def test_bus_creation_illegal_channel_name(self): + # Test if the bus constructor is able to deal with non-ASCII characters + def canGetChannelDataMock( + channel: ctypes.c_int, + param: ctypes.c_int, + buf: ctypes.c_void_p, + bufsize: ctypes.c_size_t, + ): + if param == constants.canCHANNELDATA_DEVDESCR_ASCII: + buf_char_ptr = ctypes.cast(buf, ctypes.POINTER(ctypes.c_char)) + for i, char in enumerate(b"hello\x7a\xcb"): + buf_char_ptr[i] = char + + canlib.canGetChannelData = canGetChannelDataMock + bus = can.Bus(channel=0, interface="kvaser") + + self.assertTrue(bus.channel_info.startswith("hello")) + + bus.shutdown() + def test_bus_shutdown(self): self.bus.shutdown() self.assertTrue(canlib.canBusOff.called) From dc0ae68acb28f01a503e084718cfa2acf39d1e16 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:23:56 +0200 Subject: [PATCH 1054/1235] update CHANGELOG.md and version to 4.2.2 (#1615) --- CHANGELOG.md | 11 +++++++++++ can/__init__.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a240cb650..493ac1966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +Version 4.2.2 +============= + +Bug Fixes +--------- +* Fix socketcan KeyError (#1598, #1599). +* Fix IXXAT not properly shutdown message (#1606). +* Fix Mf4Reader and TRCReader incompatibility with extra CLI args (#1610). +* Fix decoding error in Kvaser constructor for non-ASCII product name (#1613). + + Version 4.2.1 ============= diff --git a/can/__init__.py b/can/__init__.py index 034f388a1..46a461b38 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.2.1" +__version__ = "4.2.2" __all__ = [ "ASCReader", "ASCWriter", From c5d7a7ce71e5e12cad4682d6c1411f8688de54bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?uml=C3=A4ute?= Date: Fri, 30 Jun 2023 11:18:46 +0200 Subject: [PATCH 1055/1235] BigEndian test fixes (#1625) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PCAN_BITRATES are stored in BIGendian, no use to byteswap on littleendian. Also the TPCANChannelInformation expects data in the native byte order Closes: https://github.com/hardbyte/python-can/issues/1624 Co-authored-by: IOhannes m zmölnig (Debian/GNU) --- test/test_bit_timing.py | 2 +- test/test_pcan.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py index a0d4e03a5..d0d0a4a7f 100644 --- a/test/test_bit_timing.py +++ b/test/test_bit_timing.py @@ -175,7 +175,7 @@ def test_from_btr(): def test_btr_persistence(): f_clock = 8_000_000 for btr0btr1 in PCAN_BITRATES.values(): - btr1, btr0 = struct.unpack("BB", btr0btr1) + btr0, btr1 = struct.pack(">H", btr0btr1.value) t = can.BitTiming.from_registers(f_clock, btr0, btr1) assert t.btr0 == btr0 diff --git a/test/test_pcan.py b/test/test_pcan.py index 19fa44dc7..be6c5ad64 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -3,6 +3,7 @@ """ import ctypes +import struct import unittest from unittest import mock from unittest.mock import Mock, patch @@ -379,9 +380,7 @@ def test_detect_available_configs(self) -> None: self.assertEqual(len(configs), 50) else: value = (TPCANChannelInformation * 1).from_buffer_copy( - b"Q\x00\x05\x00\x01\x00\x00\x00PCAN-USB FD\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b'\x00\x00\x00\x00\x00\x00\x003"\x11\x00\x01\x00\x00\x00' + struct.pack("HBBI33sII", 81, 5, 0, 1, b"PCAN-USB FD", 1122867, 1) ) self.mock_pcan.GetValue = Mock(return_value=(PCAN_ERROR_OK, value)) configs = PcanBus._detect_available_configs() From 78d25ff6c9a63a084f8add6976d4bf1817a553be Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Thu, 6 Jul 2023 10:13:34 -0400 Subject: [PATCH 1056/1235] Enable send and receive on network ID above 255 (#1627) --- can/interfaces/ics_neovi/neovi_bus.py | 55 ++++++++++++--------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index f2dffe0a6..f78293c86 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -12,6 +12,7 @@ import os import tempfile from collections import Counter, defaultdict, deque +from functools import partial from itertools import cycle from threading import Event from warnings import warn @@ -317,7 +318,8 @@ def _process_msg_queue(self, timeout=0.1): except ics.RuntimeError: return for ics_msg in messages: - if ics_msg.NetworkID not in self.channels: + channel = ics_msg.NetworkID | (ics_msg.NetworkID2 << 8) + if channel not in self.channels: continue is_tx = bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG) @@ -364,50 +366,37 @@ def _get_timestamp_for_msg(self, ics_msg): def _ics_msg_to_message(self, ics_msg): is_fd = ics_msg.Protocol == ics.SPY_PROTOCOL_CANFD + message_from_ics = partial( + Message, + timestamp=self._get_timestamp_for_msg(ics_msg), + arbitration_id=ics_msg.ArbIDOrHeader, + is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), + is_remote_frame=bool(ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME), + is_error_frame=bool(ics_msg.StatusBitField2 & ics.SPY_STATUS2_ERROR_FRAME), + channel=ics_msg.NetworkID | (ics_msg.NetworkID2 << 8), + dlc=ics_msg.NumberBytesData, + is_fd=is_fd, + is_rx=not bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG), + ) + if is_fd: if ics_msg.ExtraDataPtrEnabled: data = ics_msg.ExtraDataPtr[: ics_msg.NumberBytesData] else: data = ics_msg.Data[: ics_msg.NumberBytesData] - return Message( - timestamp=self._get_timestamp_for_msg(ics_msg), - arbitration_id=ics_msg.ArbIDOrHeader, + return message_from_ics( data=data, - dlc=ics_msg.NumberBytesData, - is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), - is_fd=is_fd, - is_rx=not bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG), - is_remote_frame=bool( - ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME - ), - is_error_frame=bool( - ics_msg.StatusBitField2 & ics.SPY_STATUS2_ERROR_FRAME - ), error_state_indicator=bool( ics_msg.StatusBitField3 & ics.SPY_STATUS3_CANFD_ESI ), bitrate_switch=bool( ics_msg.StatusBitField3 & ics.SPY_STATUS3_CANFD_BRS ), - channel=ics_msg.NetworkID, ) else: - return Message( - timestamp=self._get_timestamp_for_msg(ics_msg), - arbitration_id=ics_msg.ArbIDOrHeader, + return message_from_ics( data=ics_msg.Data[: ics_msg.NumberBytesData], - dlc=ics_msg.NumberBytesData, - is_extended_id=bool(ics_msg.StatusBitField & ics.SPY_STATUS_XTD_FRAME), - is_fd=is_fd, - is_rx=not bool(ics_msg.StatusBitField & ics.SPY_STATUS_TX_MSG), - is_remote_frame=bool( - ics_msg.StatusBitField & ics.SPY_STATUS_REMOTE_FRAME - ), - is_error_frame=bool( - ics_msg.StatusBitField2 & ics.SPY_STATUS2_ERROR_FRAME - ), - channel=ics_msg.NetworkID, ) def _recv_internal(self, timeout=0.1): @@ -479,12 +468,16 @@ def send(self, msg, timeout=0): message.StatusBitField2 = 0 message.StatusBitField3 = flag3 if msg.channel is not None: - message.NetworkID = msg.channel + network_id = msg.channel elif len(self.channels) == 1: - message.NetworkID = self.channels[0] + network_id = self.channels[0] else: raise ValueError("msg.channel must be set when using multiple channels.") + message.NetworkID, message.NetworkID2 = int(network_id & 0xFF), int( + (network_id >> 8) & 0xFF + ) + if timeout != 0: msg_desc_id = next(description_id) message.DescriptionID = msg_desc_id From ddc7c35d9f6cf8bac8da5531fac44dee56ccc90b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 9 Jul 2023 14:00:41 +0200 Subject: [PATCH 1057/1235] Fix Vector channel detection (#1634) --- can/interfaces/vector/canlib.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 1a84f3d2a..55aeb5da4 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -400,7 +400,11 @@ def _read_bus_params(self, channel: int) -> "VectorBusParams": vcc_list = get_channel_configs() for vcc in vcc_list: if vcc.channel_mask == channel_mask: - return vcc.bus_params + bus_params = vcc.bus_params + if bus_params is None: + # for CAN channels, this should never be `None` + raise ValueError("Invalid bus parameters.") + return bus_params raise CanInitializationError( f"Channel configuration for channel {channel} not found." @@ -1090,7 +1094,7 @@ class VectorChannelConfig(NamedTuple): channel_bus_capabilities: xldefine.XL_BusCapabilities is_on_bus: bool connected_bus_type: xldefine.XL_BusTypes - bus_params: VectorBusParams + bus_params: Optional[VectorBusParams] serial_number: int article_number: int transceiver_name: str @@ -1110,9 +1114,14 @@ def _get_xl_driver_config() -> xlclass.XLdriverConfig: return driver_config -def _read_bus_params_from_c_struct(bus_params: xlclass.XLbusParams) -> VectorBusParams: +def _read_bus_params_from_c_struct( + bus_params: xlclass.XLbusParams, +) -> Optional[VectorBusParams]: + bus_type = xldefine.XL_BusTypes(bus_params.busType) + if bus_type is not xldefine.XL_BusTypes.XL_BUS_TYPE_CAN: + return None return VectorBusParams( - bus_type=xldefine.XL_BusTypes(bus_params.busType), + bus_type=bus_type, can=VectorCanParams( bitrate=bus_params.data.can.bitRate, sjw=bus_params.data.can.sjw, From 631f7bea71134e1bc8c1af9d6a87f1b75985ff7c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 11 Jul 2023 20:35:40 +0200 Subject: [PATCH 1058/1235] align `ID:` in message string (#1635) --- can/message.py | 6 +++--- doc/message.rst | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/can/message.py b/can/message.py index 02706583c..63a4eea41 100644 --- a/can/message.py +++ b/can/message.py @@ -110,10 +110,10 @@ def __init__( # pylint: disable=too-many-locals, too-many-arguments def __str__(self) -> str: field_strings = [f"Timestamp: {self.timestamp:>15.6f}"] if self.is_extended_id: - arbitration_id_string = f"ID: {self.arbitration_id:08x}" + arbitration_id_string = f"{self.arbitration_id:08x}" else: - arbitration_id_string = f"ID: {self.arbitration_id:04x}" - field_strings.append(arbitration_id_string.rjust(12, " ")) + arbitration_id_string = f"{self.arbitration_id:03x}" + field_strings.append(f"ID: {arbitration_id_string:>8}") flag_string = " ".join( [ diff --git a/doc/message.rst b/doc/message.rst index d47473e17..e0003cfe5 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -44,7 +44,7 @@ Message 2\ :sup:`29` - 1 for 29-bit identifiers). >>> print(Message(is_extended_id=False, arbitration_id=100)) - Timestamp: 0.000000 ID: 0064 S Rx DL: 0 + Timestamp: 0.000000 ID: 064 S Rx DL: 0 .. attribute:: data @@ -106,7 +106,7 @@ Message Previously this was exposed as `id_type`. >>> print(Message(is_extended_id=False)) - Timestamp: 0.000000 ID: 0000 S Rx DL: 0 + Timestamp: 0.000000 ID: 000 S Rx DL: 0 >>> print(Message(is_extended_id=True)) Timestamp: 0.000000 ID: 00000000 X Rx DL: 0 From d81a16b2a85fec16611ed0bdfdcba8ac54bdc02a Mon Sep 17 00:00:00 2001 From: Yann poupon <100286656+yannpoupon@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:32:26 +0200 Subject: [PATCH 1059/1235] Optimize PCAN send performance (#1640) --- can/interfaces/pcan/pcan.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index a9b2c016b..13775e983 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -608,8 +608,7 @@ def send(self, msg, timeout=None): CANMsg.MSGTYPE = msgType # copy data - for i in range(msg.dlc): - CANMsg.DATA[i] = msg.data[i] + CANMsg.DATA[: msg.dlc] = msg.data[: msg.dlc] log.debug("Data: %s", msg.data) log.debug("Type: %s", type(msg.data)) @@ -628,8 +627,7 @@ def send(self, msg, timeout=None): # if a remote frame will be sent, data bytes are not important. if not msg.is_remote_frame: # copy data - for i in range(CANMsg.LEN): - CANMsg.DATA[i] = msg.data[i] + CANMsg.DATA[: CANMsg.LEN] = msg.data[: CANMsg.LEN] log.debug("Data: %s", msg.data) log.debug("Type: %s", type(msg.data)) From 38142543fb87441532e71c956179e7bdddba8e59 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 17 Aug 2023 09:35:01 +0200 Subject: [PATCH 1060/1235] Support version string of older PCAN basic API (#1644) --- can/interfaces/pcan/pcan.py | 5 ++++- test/test_pcan.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 13775e983..d2973ade6 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -416,7 +416,10 @@ def get_api_version(self): if error != PCAN_ERROR_OK: raise CanInitializationError(f"Failed to read pcan basic api version") - return version.parse(value.decode("ascii")) + # fix https://github.com/hardbyte/python-can/issues/1642 + version_string = value.decode("ascii").replace(",", ".").replace(" ", "") + + return version.parse(version_string) def check_api_version(self): apv = self.get_api_version() diff --git a/test/test_pcan.py b/test/test_pcan.py index be6c5ad64..9f4e36fc4 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -120,6 +120,19 @@ def test_api_version_read_fail(self) -> None: with self.assertRaises(CanInitializationError): self.bus = can.Bus(interface="pcan") + def test_issue1642(self) -> None: + self.PCAN_API_VERSION_SIM = "1, 3, 0, 50" + with self.assertLogs("can.pcan", level="WARNING") as cm: + self.bus = can.Bus(interface="pcan") + found_version_warning = False + for i in cm.output: + if "version" in i and "pcan" in i: + found_version_warning = True + self.assertTrue( + found_version_warning, + f"No warning was logged for incompatible api version {cm.output}", + ) + @parameterized.expand( [ ("no_error", PCAN_ERROR_OK, PCAN_ERROR_OK, "some ok text 1"), From fcf615d4023494f455854a22cdfb8072c35be033 Mon Sep 17 00:00:00 2001 From: Fabian Henze <32638720+henzef@users.noreply.github.com> Date: Fri, 18 Aug 2023 18:38:47 +0200 Subject: [PATCH 1061/1235] ixxat: Fix exception in 'state' property on bus coupling errors (#1647) BusState is not an exception type and should be raised (especially not in a property). --- can/interfaces/ixxat/canlib_vcinpl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 1bb0fd802..922c683b8 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -852,7 +852,7 @@ def state(self) -> BusState: error_byte_2 = status.dwStatus & 0xF0 # CAN_STATUS_BUSCERR = 0x20 # bus coupling error if error_byte_2 & constants.CAN_STATUS_BUSCERR: - raise BusState.ERROR + return BusState.ERROR return BusState.ACTIVE From 436a7c3da411c54ee407a6e64477b88e1b6e985f Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 23 Aug 2023 18:13:16 +0200 Subject: [PATCH 1062/1235] fix pepy badge url (#1649) --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 07d2b0668..d2f05b2c1 100644 --- a/README.rst +++ b/README.rst @@ -17,11 +17,11 @@ python-can :target: https://pypi.python.org/pypi/python-can/ :alt: Supported Python implementations -.. |downloads| image:: https://pepy.tech/badge/python-can +.. |downloads| image:: https://static.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 +.. |downloads_monthly| image:: https://static.pepy.tech/badge/python-can/month :target: https://pepy.tech/project/python-can :alt: Monthly downloads on PePy From 4267e44e336f66641f9be27c4353cd4df9298961 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 23 Aug 2023 18:14:14 +0200 Subject: [PATCH 1063/1235] Do not stop notifier if exception was handled (#1645) --- can/notifier.py | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index 92a91f8a9..1c8b77c5d 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -3,10 +3,11 @@ """ import asyncio +import functools import logging import threading import time -from typing import Awaitable, Callable, Iterable, List, Optional, Union +from typing import Awaitable, Callable, Iterable, List, Optional, Union, cast from can.bus import BusABC from can.listener import Listener @@ -108,28 +109,33 @@ def stop(self, timeout: float = 5) -> None: listener.stop() def _rx_thread(self, bus: BusABC) -> None: - try: - while self._running: + # determine message handling callable early, not inside while loop + handle_message = cast( + Callable[[Message], None], + self._on_message_received + if self._loop is None + else functools.partial( + self._loop.call_soon_threadsafe, self._on_message_received + ), + ) + + while self._running: + try: if msg := bus.recv(self.timeout): with self._lock: - if self._loop is not None: - self._loop.call_soon_threadsafe( - self._on_message_received, msg - ) - else: - self._on_message_received(msg) - except Exception as exc: # pylint: disable=broad-except - self.exception = exc - if self._loop is not None: - self._loop.call_soon_threadsafe(self._on_error, exc) - # Raise anyway - raise - elif not self._on_error(exc): - # If it was not handled, raise the exception here - raise - else: - # It was handled, so only log it - logger.info("suppressed exception: %s", exc) + handle_message(msg) + except Exception as exc: # pylint: disable=broad-except + self.exception = exc + if self._loop is not None: + self._loop.call_soon_threadsafe(self._on_error, exc) + # Raise anyway + raise + elif not self._on_error(exc): + # If it was not handled, raise the exception here + raise + else: + # It was handled, so only log it + logger.debug("suppressed exception: %s", exc) def _on_message_available(self, bus: BusABC) -> None: if msg := bus.recv(0): From 1774051c3298be2ecb7e374ebb1f088365430c0b Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 25 Aug 2023 10:21:45 -0400 Subject: [PATCH 1064/1235] Fixed serial number range (#1650) Fix lower serial number value --- can/interfaces/ics_neovi/neovi_bus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index f78293c86..0698c1416 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -255,7 +255,7 @@ def get_serial_number(device): :return: ics device serial string :rtype: str """ - if int("AA0000", 36) < device.SerialNumber < int("ZZZZZZ", 36): + if int("0A0000", 36) < device.SerialNumber < int("ZZZZZZ", 36): return ics.base36enc(device.SerialNumber) else: return str(device.SerialNumber) From 4b17b9c0c24e302627e41ea5dce17b86234016b9 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 8 Sep 2023 09:04:40 -0400 Subject: [PATCH 1065/1235] Use same configuration file as Linux on macOS (#1657) --- can/util.py | 2 +- doc/configuration.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/can/util.py b/can/util.py index 59abdd579..71980e82f 100644 --- a/can/util.py +++ b/can/util.py @@ -43,7 +43,7 @@ CONFIG_FILES = ["~/can.conf"] -if platform.system() == "Linux": +if platform.system() in ("Linux", "Darwin"): CONFIG_FILES.extend(["/etc/can.conf", "~/.can", "~/.canrc"]) elif platform.system() == "Windows" or platform.python_implementation() == "IronPython": CONFIG_FILES.extend(["can.ini", os.path.join(os.getenv("APPDATA", ""), "can.ini")]) diff --git a/doc/configuration.rst b/doc/configuration.rst index 494351350..7b42017a9 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -36,7 +36,7 @@ You can also specify the interface and channel for each Bus instance:: Configuration File ------------------ -On Linux systems the config file is searched in the following paths: +On Linux and macOS systems the config file is searched in the following paths: #. ``~/can.conf`` #. ``/etc/can.conf`` @@ -159,4 +159,4 @@ Lookup table of interface names: | ``"virtual"`` | :doc:`interfaces/virtual` | +---------------------+-------------------------------------+ -Additional interface types can be added via the :ref:`plugin interface`. \ No newline at end of file +Additional interface types can be added via the :ref:`plugin interface`. From 40c779130baac24b3c94ae98696d7fdf99f6a142 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 8 Sep 2023 23:16:30 +0200 Subject: [PATCH 1066/1235] Fix PCAN timestamp (#1651) * fix PCAN timestamp * remove unused datetime import --- can/interfaces/pcan/pcan.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index d2973ade6..25610a614 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -5,7 +5,6 @@ import platform import time import warnings -from datetime import datetime from typing import Any, List, Optional, Tuple, Union from packaging import version @@ -85,7 +84,7 @@ if uptime.boottime() is None: boottimeEpoch = 0 else: - boottimeEpoch = (uptime.boottime() - datetime.fromtimestamp(0)).total_seconds() + boottimeEpoch = uptime.boottime().timestamp() except ImportError as error: log.warning( "uptime library not available, timestamps are relative to boot time and not to Epoch UTC", From f3fa07107d08c6225b9ab77443df13507c1a9c4b Mon Sep 17 00:00:00 2001 From: luojiaaoo <62821977+luojiaaoo@users.noreply.github.com> Date: Tue, 12 Sep 2023 15:11:04 +0800 Subject: [PATCH 1067/1235] Kvaser: add parameter exclusive and override_exclusive (#1660) * * For interface Kvaser, add parameter exclusive Don't allow sharing of this CANlib channel. * For interface Kvaser, add parameter override_exclusive Open the channel even if it is opened for exclusive access already. * fix --------- Co-authored-by: luoja --- can/interfaces/kvaser/canlib.py | 17 ++++++++++++++++- can/interfaces/kvaser/constants.py | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 4e5e8c51b..38949137d 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -404,6 +404,10 @@ def __init__(self, channel, can_filters=None, **kwargs): computer, set this to True or set single_handle to True. :param bool fd: If CAN-FD frames should be supported. + :param bool exclusive: + Don't allow sharing of this CANlib channel. + :param bool override_exclusive: + Open the channel even if it is opened for exclusive access already. :param int data_bitrate: Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. @@ -420,6 +424,8 @@ def __init__(self, channel, can_filters=None, **kwargs): driver_mode = kwargs.get("driver_mode", DRIVER_MODE_NORMAL) single_handle = kwargs.get("single_handle", False) receive_own_messages = kwargs.get("receive_own_messages", False) + exclusive = kwargs.get("exclusive", False) + override_exclusive = kwargs.get("override_exclusive", False) accept_virtual = kwargs.get("accept_virtual", True) fd = kwargs.get("fd", False) data_bitrate = kwargs.get("data_bitrate", None) @@ -445,6 +451,10 @@ def __init__(self, channel, can_filters=None, **kwargs): self.channel_info = channel_info flags = 0 + if exclusive: + flags |= canstat.canOPEN_EXCLUSIVE + if override_exclusive: + flags |= canstat.canOPEN_OVERRIDE_EXCLUSIVE if accept_virtual: flags |= canstat.canOPEN_ACCEPT_VIRTUAL if fd: @@ -491,7 +501,12 @@ def __init__(self, channel, can_filters=None, **kwargs): self._write_handle = self._read_handle else: log.debug("Creating separate handle for TX on channel: %s", channel) - self._write_handle = canOpenChannel(channel, flags) + if exclusive: + flags_ = flags & ~canstat.canOPEN_EXCLUSIVE + flags_ |= canstat.canOPEN_OVERRIDE_EXCLUSIVE + else: + flags_ = flags + self._write_handle = canOpenChannel(channel, flags_) canBusOn(self._read_handle) can_driver_mode = ( diff --git a/can/interfaces/kvaser/constants.py b/can/interfaces/kvaser/constants.py index 9dd3a9163..3d01faa84 100644 --- a/can/interfaces/kvaser/constants.py +++ b/can/interfaces/kvaser/constants.py @@ -161,6 +161,8 @@ def CANSTATUS_SUCCESS(status): canDRIVER_SELFRECEPTION = 8 canDRIVER_OFF = 0 +canOPEN_EXCLUSIVE = 0x0008 +canOPEN_REQUIRE_EXTENDED = 0x0010 canOPEN_ACCEPT_VIRTUAL = 0x0020 canOPEN_OVERRIDE_EXCLUSIVE = 0x0040 canOPEN_REQUIRE_INIT_ACCESS = 0x0080 From b794153a5744afecb4113f3f814a5c94695e9f76 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 17 Sep 2023 22:04:43 +0200 Subject: [PATCH 1068/1235] Catch `pywintypes.error` in broadcast manager (#1659) --- can/broadcastmanager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 84554a507..a017b7d52 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -22,6 +22,7 @@ # try to import win32event for event-based cyclic send task (needs the pywin32 package) USE_WINDOWS_EVENTS = False try: + import pywintypes import win32event # Python 3.11 provides a more precise sleep implementation on Windows, so this is not necessary. @@ -263,7 +264,7 @@ def __init__( win32event.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, win32event.TIMER_ALL_ACCESS, ) - except (AttributeError, OSError): + except (AttributeError, OSError, pywintypes.error): self.event = win32event.CreateWaitableTimer(None, False, None) self.start() From e09d35e568cdd32cd3137e6813482988c6925a4e Mon Sep 17 00:00:00 2001 From: ro-id <84511204+ro-id@users.noreply.github.com> Date: Sun, 17 Sep 2023 22:06:17 +0200 Subject: [PATCH 1069/1235] Fix BLFReader error for incomplete or truncated stream (#1662) * bugfix BLFreader zlib.error: Error -5 while decompressing data: incomplete or truncated stream in Python * delete elif copied on to many lines * Update blf.py shorten line -> comment in next line delete unused variable * fromatting --------- Co-authored-by: Iding Robin --- can/io/blf.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index 071c089d7..81146233d 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -182,12 +182,13 @@ def __iter__(self) -> Generator[Message, None, None]: self.file.read(obj_size % 4) if obj_type == LOG_CONTAINER: - method, uncompressed_size = LOG_CONTAINER_STRUCT.unpack_from(obj_data) + method, _ = LOG_CONTAINER_STRUCT.unpack_from(obj_data) container_data = obj_data[LOG_CONTAINER_STRUCT.size :] if method == NO_COMPRESSION: data = container_data elif method == ZLIB_DEFLATE: - data = zlib.decompress(container_data, 15, uncompressed_size) + zobj = zlib.decompressobj() + data = zobj.decompress(container_data) else: # Unknown compression method LOG.warning("Unknown compression method (%d)", method) From db177b386e04d6369ea99949a0f1145e58e21868 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 17 Sep 2023 22:06:40 +0200 Subject: [PATCH 1070/1235] Relax BitTiming & BitTimingFd Validation (#1618) * add `strict` parameter to can.BitTiming * add `strict` parameter to can.BitTimingFd * use `strict` in `from_bitrate_and_segments` and `recreate_with_f_clock` * cover `strict` parameter in tests * pylint: disable=too-many-arguments --- can/bit_timing.py | 160 ++++++++++++++++++++++++++-------------- can/util.py | 6 +- test/test_bit_timing.py | 73 ++++++++++++++++-- test/test_util.py | 12 +-- 4 files changed, 181 insertions(+), 70 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index bf76c08af..285b1c302 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -36,6 +36,7 @@ def __init__( tseg2: int, sjw: int, nof_samples: int = 1, + strict: bool = False, ) -> None: """ :param int f_clock: @@ -56,6 +57,10 @@ def __init__( In this case, the bit will be sampled three quanta in a row, with the last sample being taken in the edge between TSEG1 and TSEG2. Three samples should only be used for relatively slow baudrates. + :param bool strict: + If True, restrict bit timings to the minimum required range as defined in + ISO 11898. This can be used to ensure compatibility across a wide variety + of CAN hardware. :raises ValueError: if the arguments are invalid. """ @@ -68,19 +73,13 @@ def __init__( "nof_samples": nof_samples, } self._validate() + if strict: + self._restrict_to_minimum_range() def _validate(self) -> None: - if not 8 <= self.nbt <= 25: - raise ValueError(f"nominal bit time (={self.nbt}) must be in [8...25].") - if not 1 <= self.brp <= 64: raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...64].") - if not 5_000 <= self.bitrate <= 2_000_000: - raise ValueError( - f"bitrate (={self.bitrate}) must be in [5,000...2,000,000]." - ) - if not 1 <= self.tseg1 <= 16: raise ValueError(f"tseg1 (={self.tseg1}) must be in [1...16].") @@ -104,6 +103,18 @@ def _validate(self) -> None: if self.nof_samples not in (1, 3): raise ValueError("nof_samples must be 1 or 3") + def _restrict_to_minimum_range(self) -> None: + if not 8 <= self.nbt <= 25: + raise ValueError(f"nominal bit time (={self.nbt}) must be in [8...25].") + + if not 1 <= self.brp <= 32: + raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...32].") + + if not 5_000 <= self.bitrate <= 1_000_000: + raise ValueError( + f"bitrate (={self.bitrate}) must be in [5,000...1,000,000]." + ) + @classmethod def from_bitrate_and_segments( cls, @@ -113,6 +124,7 @@ def from_bitrate_and_segments( tseg2: int, sjw: int, nof_samples: int = 1, + strict: bool = False, ) -> "BitTiming": """Create a :class:`~can.BitTiming` instance from bitrate and segment lengths. @@ -134,6 +146,10 @@ def from_bitrate_and_segments( In this case, the bit will be sampled three quanta in a row, with the last sample being taken in the edge between TSEG1 and TSEG2. Three samples should only be used for relatively slow baudrates. + :param bool strict: + If True, restrict bit timings to the minimum required range as defined in + ISO 11898. This can be used to ensure compatibility across a wide variety + of CAN hardware. :raises ValueError: if the arguments are invalid. """ @@ -149,6 +165,7 @@ def from_bitrate_and_segments( tseg2=tseg2, sjw=sjw, nof_samples=nof_samples, + strict=strict, ) if abs(bt.bitrate - bitrate) > bitrate / 256: raise ValueError( @@ -175,6 +192,11 @@ def from_registers( :raises ValueError: if the arguments are invalid. """ + if not 0 <= btr0 < 2**16: + raise ValueError(f"Invalid btr0 value. ({btr0})") + if not 0 <= btr1 < 2**16: + raise ValueError(f"Invalid btr1 value. ({btr1})") + brp = (btr0 & 0x3F) + 1 sjw = (btr0 >> 6) + 1 tseg1 = (btr1 & 0xF) + 1 @@ -239,6 +261,7 @@ def from_sample_point( tseg1=tseg1, tseg2=tseg2, sjw=sjw, + strict=True, ) possible_solutions.append(bt) except ValueError: @@ -316,12 +339,12 @@ def sample_point(self) -> float: @property def btr0(self) -> int: - """Bit timing register 0.""" + """Bit timing register 0 for SJA1000.""" return (self.sjw - 1) << 6 | self.brp - 1 @property def btr1(self) -> int: - """Bit timing register 1.""" + """Bit timing register 1 for SJA1000.""" sam = 1 if self.nof_samples == 3 else 0 return sam << 7 | (self.tseg2 - 1) << 4 | self.tseg1 - 1 @@ -373,6 +396,7 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming": tseg2=self.tseg2, sjw=self.sjw, nof_samples=self.nof_samples, + strict=True, ) except ValueError: pass @@ -474,7 +498,7 @@ class BitTimingFd(Mapping): ) """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, f_clock: int, nom_brp: int, @@ -485,6 +509,7 @@ def __init__( data_tseg1: int, data_tseg2: int, data_sjw: int, + strict: bool = False, ) -> None: """ Initialize a BitTimingFd instance with the specified parameters. @@ -513,6 +538,10 @@ def __init__( :param int data_sjw: The Synchronization Jump Width for the data phase. This value determines the maximum number of time quanta that the controller can resynchronize every bit. + :param bool strict: + If True, restrict bit timings to the minimum required range as defined in + ISO 11898. This can be used to ensure compatibility across a wide variety + of CAN hardware. :raises ValueError: if the arguments are invalid. """ @@ -528,32 +557,23 @@ def __init__( "data_sjw": data_sjw, } self._validate() + if strict: + self._restrict_to_minimum_range() def _validate(self) -> None: - if self.nbt < 8: - raise ValueError(f"nominal bit time (={self.nbt}) must be at least 8.") - - if self.dbt < 8: - raise ValueError(f"data bit time (={self.dbt}) must be at least 8.") - - if not 1 <= self.nom_brp <= 256: - raise ValueError( - f"nominal bitrate prescaler (={self.nom_brp}) must be in [1...256]." - ) - - if not 1 <= self.data_brp <= 256: - raise ValueError( - f"data bitrate prescaler (={self.data_brp}) must be in [1...256]." - ) + for param, value in self._data.items(): + if value < 0: # type: ignore[operator] + err_msg = f"'{param}' (={value}) must not be negative." + raise ValueError(err_msg) - if not 5_000 <= self.nom_bitrate <= 2_000_000: + if self.nom_brp < 1: raise ValueError( - f"nom_bitrate (={self.nom_bitrate}) must be in [5,000...2,000,000]." + f"nominal bitrate prescaler (={self.nom_brp}) must be at least 1." ) - if not 25_000 <= self.data_bitrate <= 8_000_000: + if self.data_brp < 1: raise ValueError( - f"data_bitrate (={self.data_bitrate}) must be in [25,000...8,000,000]." + f"data bitrate prescaler (={self.data_brp}) must be at least 1." ) if self.data_bitrate < self.nom_bitrate: @@ -562,30 +582,12 @@ def _validate(self) -> None: f"equal to nom_bitrate (={self.nom_bitrate})" ) - if not 2 <= self.nom_tseg1 <= 256: - raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...256].") - - if not 1 <= self.nom_tseg2 <= 128: - raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [1...128].") - - if not 1 <= self.data_tseg1 <= 32: - raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...32].") - - if not 1 <= self.data_tseg2 <= 16: - raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [1...16].") - - if not 1 <= self.nom_sjw <= 128: - raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...128].") - if self.nom_sjw > self.nom_tseg2: raise ValueError( f"nom_sjw (={self.nom_sjw}) must not be " f"greater than nom_tseg2 (={self.nom_tseg2})." ) - if not 1 <= self.data_sjw <= 16: - raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...128].") - if self.data_sjw > self.data_tseg2: raise ValueError( f"data_sjw (={self.data_sjw}) must not be " @@ -604,8 +606,46 @@ def _validate(self) -> None: f"(data_sample_point={self.data_sample_point:.2f}%)." ) + def _restrict_to_minimum_range(self) -> None: + # restrict to minimum required range as defined in ISO 11898 + if not 8 <= self.nbt <= 80: + raise ValueError(f"Nominal bit time (={self.nbt}) must be in [8...80]") + + if not 5 <= self.dbt <= 25: + raise ValueError(f"Nominal bit time (={self.dbt}) must be in [5...25]") + + if not 1 <= self.data_tseg1 <= 16: + raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...16].") + + if not 2 <= self.data_tseg2 <= 8: + raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [2...8].") + + if not 1 <= self.data_sjw <= 8: + raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...8].") + + if self.nom_brp == self.data_brp: + # shared prescaler + if not 2 <= self.nom_tseg1 <= 128: + raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...128].") + + if not 2 <= self.nom_tseg2 <= 32: + raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [2...32].") + + if not 1 <= self.nom_sjw <= 32: + raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...32].") + else: + # separate prescaler + if not 2 <= self.nom_tseg1 <= 64: + raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...64].") + + if not 2 <= self.nom_tseg2 <= 16: + raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [2...16].") + + if not 1 <= self.nom_sjw <= 16: + raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...16].") + @classmethod - def from_bitrate_and_segments( + def from_bitrate_and_segments( # pylint: disable=too-many-arguments cls, f_clock: int, nom_bitrate: int, @@ -616,6 +656,7 @@ def from_bitrate_and_segments( data_tseg1: int, data_tseg2: int, data_sjw: int, + strict: bool = False, ) -> "BitTimingFd": """ Create a :class:`~can.BitTimingFd` instance with the bitrates and segments lengths. @@ -644,6 +685,10 @@ def from_bitrate_and_segments( :param int data_sjw: The Synchronization Jump Width for the data phase. This value determines the maximum number of time quanta that the controller can resynchronize every bit. + :param bool strict: + If True, restrict bit timings to the minimum required range as defined in + ISO 11898. This can be used to ensure compatibility across a wide variety + of CAN hardware. :raises ValueError: if the arguments are invalid. """ @@ -665,6 +710,7 @@ def from_bitrate_and_segments( data_tseg1=data_tseg1, data_tseg2=data_tseg2, data_sjw=data_sjw, + strict=strict, ) if abs(bt.nom_bitrate - nom_bitrate) > nom_bitrate / 256: @@ -724,9 +770,11 @@ def from_sample_point( possible_solutions: List[BitTimingFd] = [] + sync_seg = 1 + for nom_brp in range(1, 257): nbt = round(int(f_clock / (nom_bitrate * nom_brp))) - if nbt < 8: + if nbt < 1: break effective_nom_bitrate = f_clock / (nbt * nom_brp) @@ -734,15 +782,15 @@ def from_sample_point( continue nom_tseg1 = int(round(nom_sample_point / 100 * nbt)) - 1 - # limit tseg1, so tseg2 is at least 1 TQ - nom_tseg1 = min(nom_tseg1, nbt - 2) + # limit tseg1, so tseg2 is at least 2 TQ + nom_tseg1 = min(nom_tseg1, nbt - sync_seg - 2) nom_tseg2 = nbt - nom_tseg1 - 1 nom_sjw = min(nom_tseg2, 128) for data_brp in range(1, 257): dbt = round(int(f_clock / (data_bitrate * data_brp))) - if dbt < 8: + if dbt < 1: break effective_data_bitrate = f_clock / (dbt * data_brp) @@ -750,8 +798,8 @@ def from_sample_point( continue data_tseg1 = int(round(data_sample_point / 100 * dbt)) - 1 - # limit tseg1, so tseg2 is at least 1 TQ - data_tseg1 = min(data_tseg1, dbt - 2) + # limit tseg1, so tseg2 is at least 2 TQ + data_tseg1 = min(data_tseg1, dbt - sync_seg - 2) data_tseg2 = dbt - data_tseg1 - 1 data_sjw = min(data_tseg2, 16) @@ -767,6 +815,7 @@ def from_sample_point( data_tseg1=data_tseg1, data_tseg2=data_tseg2, data_sjw=data_sjw, + strict=True, ) possible_solutions.append(bt) except ValueError: @@ -971,6 +1020,7 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTimingFd": data_tseg1=self.data_tseg1, data_tseg2=self.data_tseg2, data_sjw=self.data_sjw, + strict=True, ) except ValueError: pass diff --git a/can/util.py b/can/util.py index 71980e82f..402934379 100644 --- a/can/util.py +++ b/can/util.py @@ -250,14 +250,16 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: **{ key: int(config[key]) for key in typechecking.BitTimingFdDict.__annotations__ - } + }, + strict=False, ) elif set(typechecking.BitTimingDict.__annotations__).issubset(config): config["timing"] = can.BitTiming( **{ key: int(config[key]) for key in typechecking.BitTimingDict.__annotations__ - } + }, + strict=False, ) except (ValueError, TypeError): pass diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py index d0d0a4a7f..6852687a5 100644 --- a/test/test_bit_timing.py +++ b/test/test_bit_timing.py @@ -11,7 +11,7 @@ def test_sja1000(): """Test some values obtained using other bit timing calculators.""" timing = can.BitTiming( - f_clock=8_000_000, brp=4, tseg1=11, tseg2=4, sjw=2, nof_samples=3 + f_clock=8_000_000, brp=4, tseg1=11, tseg2=4, sjw=2, nof_samples=3, strict=True ) assert timing.f_clock == 8_000_000 assert timing.bitrate == 125_000 @@ -25,7 +25,9 @@ def test_sja1000(): assert timing.btr0 == 0x43 assert timing.btr1 == 0xBA - timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=13, tseg2=2, sjw=1) + timing = can.BitTiming( + f_clock=8_000_000, brp=1, tseg1=13, tseg2=2, sjw=1, strict=True + ) assert timing.f_clock == 8_000_000 assert timing.bitrate == 500_000 assert timing.brp == 1 @@ -38,7 +40,9 @@ def test_sja1000(): assert timing.btr0 == 0x00 assert timing.btr1 == 0x1C - timing = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1) + timing = can.BitTiming( + f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1, strict=True + ) assert timing.f_clock == 8_000_000 assert timing.bitrate == 1_000_000 assert timing.brp == 1 @@ -84,7 +88,7 @@ def test_from_bitrate_and_segments(): assert timing.btr1 == 0x1C timing = can.BitTiming.from_bitrate_and_segments( - f_clock=8_000_000, bitrate=1_000_000, tseg1=5, tseg2=2, sjw=1 + f_clock=8_000_000, bitrate=1_000_000, tseg1=5, tseg2=2, sjw=1, strict=True ) assert timing.f_clock == 8_000_000 assert timing.bitrate == 1_000_000 @@ -127,8 +131,24 @@ def test_from_bitrate_and_segments(): assert timing.data_sjw == 10 assert timing.data_sample_point == 75 + # test strict invalid + with pytest.raises(ValueError): + can.BitTimingFd.from_bitrate_and_segments( + f_clock=80_000_000, + nom_bitrate=500_000, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_bitrate=2_000_000, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + strict=True, + ) + def test_can_fd(): + # test non-strict timing = can.BitTimingFd( f_clock=80_000_000, nom_brp=1, @@ -149,7 +169,6 @@ def test_can_fd(): assert timing.nom_tseg2 == 40 assert timing.nom_sjw == 40 assert timing.nom_sample_point == 75 - assert timing.f_clock == 80_000_000 assert timing.data_bitrate == 2_000_000 assert timing.data_brp == 1 assert timing.dbt == 40 @@ -158,6 +177,50 @@ def test_can_fd(): assert timing.data_sjw == 10 assert timing.data_sample_point == 75 + # test strict invalid + with pytest.raises(ValueError): + can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + strict=True, + ) + + # test strict valid + timing = can.BitTimingFd( + f_clock=80_000_000, + nom_brp=2, + nom_tseg1=59, + nom_tseg2=20, + nom_sjw=20, + data_brp=2, + data_tseg1=14, + data_tseg2=5, + data_sjw=5, + strict=True, + ) + assert timing.f_clock == 80_000_000 + assert timing.nom_bitrate == 500_000 + assert timing.nom_brp == 2 + assert timing.nbt == 80 + assert timing.nom_tseg1 == 59 + assert timing.nom_tseg2 == 20 + assert timing.nom_sjw == 20 + assert timing.nom_sample_point == 75 + assert timing.data_bitrate == 2_000_000 + assert timing.data_brp == 2 + assert timing.dbt == 20 + assert timing.data_tseg1 == 14 + assert timing.data_tseg2 == 5 + assert timing.data_sjw == 5 + assert timing.data_sample_point == 75 + def test_from_btr(): timing = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14) diff --git a/test/test_util.py b/test/test_util.py index a4aacdf86..ac8c87d9e 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -253,14 +253,10 @@ def test_adjust_timing_fd(self): ) assert new_timing.__class__ == BitTimingFd assert new_timing.f_clock == 80_000_000 - assert new_timing.nom_bitrate == 500_000 - assert new_timing.nom_tseg1 == 119 - assert new_timing.nom_tseg2 == 40 - assert new_timing.nom_sjw == 40 - assert new_timing.data_bitrate == 2_000_000 - assert new_timing.data_tseg1 == 29 - assert new_timing.data_tseg2 == 10 - assert new_timing.data_sjw == 10 + assert new_timing.nom_bitrate == timing.nom_bitrate + assert new_timing.nom_sample_point == timing.nom_sample_point + assert new_timing.data_bitrate == timing.data_bitrate + assert new_timing.data_sample_point == timing.data_sample_point with pytest.raises(CanInitializationError): check_or_adjust_timing_clock(timing, valid_clocks=[8_000, 16_000]) From 3c3f12313a18f714ebfa21b77fa30b87d4746e98 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 2 Oct 2023 18:22:04 -0400 Subject: [PATCH 1071/1235] We do not need to account for drift when we USE_WINDOWS_EVENTS (#1666) --- can/broadcastmanager.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index a017b7d52..398114a59 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -35,7 +35,6 @@ log = logging.getLogger("can.bcm") NANOSECONDS_IN_SECOND: Final[int] = 1_000_000_000 -NANOSECONDS_IN_MILLISECOND: Final[int] = 1_000_000 class CyclicTask(abc.ABC): @@ -316,19 +315,19 @@ def _run(self) -> None: self.stop() break - msg_due_time_ns += self.period_ns + if not USE_WINDOWS_EVENTS: + msg_due_time_ns += self.period_ns 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_ns = msg_due_time_ns - time.perf_counter_ns() - - if delay_ns > 0: - if USE_WINDOWS_EVENTS: - win32event.WaitForSingleObject( - self.event.handle, - int(round(delay_ns / NANOSECONDS_IN_MILLISECOND)), - ) - else: + if USE_WINDOWS_EVENTS: + win32event.WaitForSingleObject( + self.event.handle, + win32event.INFINITE, + ) + else: + # Compensate for the time it takes to send the message + delay_ns = msg_due_time_ns - time.perf_counter_ns() + if delay_ns > 0: time.sleep(delay_ns / NANOSECONDS_IN_SECOND) From 237f2be345071325f3f94469b7d3a8912d21077d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 9 Oct 2023 00:02:21 +0200 Subject: [PATCH 1072/1235] Update linters, activate more ruff rules (#1669) * update ruff to 0.0.286 * activate pyflakes rules * activate flake8-type-checking * activate Ruff-specific rules * activate pylint rules * activate flake8-comprehensions * update ruff to 0.0.292 * remove unnecessary tuple() call * update mypy to 1.5.*, update black to 23.9.* --------- Co-authored-by: zariiii9003 --- can/bit_timing.py | 5 +- can/interfaces/gs_usb.py | 2 +- can/interfaces/ics_neovi/neovi_bus.py | 18 ++-- can/interfaces/ixxat/canlib.py | 18 ++-- can/interfaces/ixxat/canlib_vcinpl2.py | 14 ++-- can/interfaces/pcan/basic.py | 104 +++++++++++------------- can/interfaces/pcan/pcan.py | 6 +- can/interfaces/socketcand/socketcand.py | 2 +- can/interfaces/systec/ucan.py | 2 +- can/interfaces/vector/canlib.py | 18 ++-- can/io/asc.py | 8 +- can/io/trc.py | 4 +- can/logger.py | 8 +- can/util.py | 5 +- can/viewer.py | 2 +- pyproject.toml | 28 ++++--- test/back2back_test.py | 2 +- test/network_test.py | 7 +- test/test_player.py | 10 +-- test/test_slcan.py | 4 +- test/test_socketcan.py | 4 +- test/test_vector.py | 2 +- 22 files changed, 133 insertions(+), 140 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index 285b1c302..3e8cb1bcd 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -1,8 +1,9 @@ # pylint: disable=too-many-lines import math -from typing import Iterator, List, Mapping, cast +from typing import TYPE_CHECKING, Iterator, List, Mapping, cast -from can.typechecking import BitTimingDict, BitTimingFdDict +if TYPE_CHECKING: + from can.typechecking import BitTimingDict, BitTimingFdDict class BitTiming(Mapping): diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 32ad54e75..38f9fe41a 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -35,7 +35,7 @@ def __init__( """ if (index is not None) and ((bus or address) is not None): raise CanInitializationError( - f"index and bus/address cannot be used simultaneously" + "index and bus/address cannot be used simultaneously" ) if index is not None: diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 0698c1416..9270bfc90 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -302,15 +302,15 @@ def _find_device(self, type_filter=None, serial=None): for device in devices: if serial is None or self.get_serial_number(device) == str(serial): return device - else: - msg = ["No device"] - - if type_filter is not None: - msg.append(f"with type {type_filter}") - if serial is not None: - msg.append(f"with serial {serial}") - msg.append("found.") - raise CanInitializationError(" ".join(msg)) + + msg = ["No device"] + + if type_filter is not None: + msg.append(f"with type {type_filter}") + if serial is not None: + msg.append(f"with serial {serial}") + msg.append("found.") + raise CanInitializationError(" ".join(msg)) def _process_msg_queue(self, timeout=0.1): try: diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 8c07508e4..f18c86acd 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -28,17 +28,17 @@ def __init__( unique_hardware_id: Optional[int] = None, extended: bool = True, fd: bool = False, - rx_fifo_size: int = None, - tx_fifo_size: int = None, + rx_fifo_size: Optional[int] = None, + tx_fifo_size: Optional[int] = None, bitrate: int = 500000, data_bitrate: int = 2000000, - sjw_abr: int = None, - tseg1_abr: int = None, - tseg2_abr: int = None, - sjw_dbr: int = None, - tseg1_dbr: int = None, - tseg2_dbr: int = None, - ssp_dbr: int = None, + sjw_abr: Optional[int] = None, + tseg1_abr: Optional[int] = None, + tseg2_abr: Optional[int] = None, + sjw_dbr: Optional[int] = None, + tseg1_dbr: Optional[int] = None, + tseg2_dbr: Optional[int] = None, + ssp_dbr: Optional[int] = None, **kwargs, ): """ diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index b10ac1b94..2c306c880 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -436,13 +436,13 @@ def __init__( tx_fifo_size: int = 128, bitrate: int = 500000, data_bitrate: int = 2000000, - sjw_abr: int = None, - tseg1_abr: int = None, - tseg2_abr: int = None, - sjw_dbr: int = None, - tseg1_dbr: int = None, - tseg2_dbr: int = None, - ssp_dbr: int = None, + sjw_abr: Optional[int] = None, + tseg1_abr: Optional[int] = None, + tseg2_abr: Optional[int] = None, + sjw_dbr: Optional[int] = None, + tseg1_dbr: Optional[int] = None, + tseg2_dbr: Optional[int] = None, + ssp_dbr: Optional[int] = None, **kwargs, ): """ diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 7c41e2816..a57340955 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -297,73 +297,63 @@ # PCAN parameter values # -PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) -PCAN_PARAMETER_ON = int(0x01) # The PCAN parameter is set (active) -PCAN_FILTER_CLOSE = int(0x00) # The PCAN filter is closed. No messages will be received -PCAN_FILTER_OPEN = int( - 0x01 -) # The PCAN filter is fully opened. All messages will be received -PCAN_FILTER_CUSTOM = int( - 0x02 -) # The PCAN filter is custom configured. Only registered messages will be received -PCAN_CHANNEL_UNAVAILABLE = int( - 0x00 -) # The PCAN-Channel handle is illegal, or its associated hardware is not available -PCAN_CHANNEL_AVAILABLE = int( - 0x01 -) # The PCAN-Channel handle is available to be connected (PnP Hardware: it means furthermore that the hardware is plugged-in) -PCAN_CHANNEL_OCCUPIED = int( - 0x02 -) # The PCAN-Channel handle is valid, and is already being used +PCAN_PARAMETER_OFF = 0x00 # The PCAN parameter is not set (inactive) +PCAN_PARAMETER_ON = 0x01 # The PCAN parameter is set (active) +PCAN_FILTER_CLOSE = 0x00 # The PCAN filter is closed. No messages will be received +PCAN_FILTER_OPEN = ( + 0x01 # The PCAN filter is fully opened. All messages will be received +) +PCAN_FILTER_CUSTOM = 0x02 # The PCAN filter is custom configured. Only registered messages will be received +PCAN_CHANNEL_UNAVAILABLE = 0x00 # The PCAN-Channel handle is illegal, or its associated hardware is not available +PCAN_CHANNEL_AVAILABLE = 0x01 # The PCAN-Channel handle is available to be connected (PnP Hardware: it means furthermore that the hardware is plugged-in) +PCAN_CHANNEL_OCCUPIED = ( + 0x02 # The PCAN-Channel handle is valid, and is already being used +) PCAN_CHANNEL_PCANVIEW = ( PCAN_CHANNEL_AVAILABLE | PCAN_CHANNEL_OCCUPIED ) # The PCAN-Channel handle is already being used by a PCAN-View application, but is available to connect -LOG_FUNCTION_DEFAULT = int(0x00) # Logs system exceptions / errors -LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions -LOG_FUNCTION_PARAMETERS = int( - 0x02 -) # Logs the parameters passed to the PCAN-Basic API functions -LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions -LOG_FUNCTION_WRITE = int(0x08) # Logs the CAN messages passed to the CAN_Write function -LOG_FUNCTION_READ = int( - 0x10 -) # Logs the CAN messages received within the CAN_Read function -LOG_FUNCTION_ALL = int( - 0xFFFF -) # Logs all possible information within the PCAN-Basic API functions +LOG_FUNCTION_DEFAULT = 0x00 # Logs system exceptions / errors +LOG_FUNCTION_ENTRY = 0x01 # Logs the entries to the PCAN-Basic API functions +LOG_FUNCTION_PARAMETERS = ( + 0x02 # Logs the parameters passed to the PCAN-Basic API functions +) +LOG_FUNCTION_LEAVE = 0x04 # Logs the exits from the PCAN-Basic API functions +LOG_FUNCTION_WRITE = 0x08 # Logs the CAN messages passed to the CAN_Write function +LOG_FUNCTION_READ = 0x10 # Logs the CAN messages received within the CAN_Read function +LOG_FUNCTION_ALL = ( + 0xFFFF # Logs all possible information within the PCAN-Basic API functions +) -TRACE_FILE_SINGLE = int( - 0x00 -) # A single file is written until it size reaches PAN_TRACE_SIZE -TRACE_FILE_SEGMENTED = int( - 0x01 -) # Traced data is distributed in several files with size PAN_TRACE_SIZE -TRACE_FILE_DATE = int(0x02) # Includes the date into the name of the trace file -TRACE_FILE_TIME = int(0x04) # Includes the start time into the name of the trace file -TRACE_FILE_OVERWRITE = int( - 0x80 -) # Causes the overwriting of available traces (same name) +TRACE_FILE_SINGLE = ( + 0x00 # A single file is written until it size reaches PAN_TRACE_SIZE +) +TRACE_FILE_SEGMENTED = ( + 0x01 # Traced data is distributed in several files with size PAN_TRACE_SIZE +) +TRACE_FILE_DATE = 0x02 # Includes the date into the name of the trace file +TRACE_FILE_TIME = 0x04 # Includes the start time into the name of the trace file +TRACE_FILE_OVERWRITE = 0x80 # Causes the overwriting of available traces (same name) -FEATURE_FD_CAPABLE = int(0x01) # Device supports flexible data-rate (CAN-FD) -FEATURE_DELAY_CAPABLE = int( - 0x02 -) # Device supports a delay between sending frames (FPGA based USB devices) -FEATURE_IO_CAPABLE = int( - 0x04 -) # Device supports I/O functionality for electronic circuits (USB-Chip devices) +FEATURE_FD_CAPABLE = 0x01 # Device supports flexible data-rate (CAN-FD) +FEATURE_DELAY_CAPABLE = ( + 0x02 # Device supports a delay between sending frames (FPGA based USB devices) +) +FEATURE_IO_CAPABLE = ( + 0x04 # Device supports I/O functionality for electronic circuits (USB-Chip devices) +) -SERVICE_STATUS_STOPPED = int(0x01) # The service is not running -SERVICE_STATUS_RUNNING = int(0x04) # The service is running +SERVICE_STATUS_STOPPED = 0x01 # The service is not running +SERVICE_STATUS_RUNNING = 0x04 # The service is running # Other constants # -MAX_LENGTH_HARDWARE_NAME = int( - 33 -) # Maximum length of the name of a device: 32 characters + terminator -MAX_LENGTH_VERSION_STRING = int( - 256 -) # Maximum length of a version string: 255 characters + terminator +MAX_LENGTH_HARDWARE_NAME = ( + 33 # Maximum length of the name of a device: 32 characters + terminator +) +MAX_LENGTH_VERSION_STRING = ( + 256 # Maximum length of a version string: 255 characters + terminator +) # PCAN message types # diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 25610a614..01a4b1dc3 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -85,7 +85,7 @@ boottimeEpoch = 0 else: boottimeEpoch = uptime.boottime().timestamp() -except ImportError as error: +except ImportError: log.warning( "uptime library not available, timestamps are relative to boot time and not to Epoch UTC", ) @@ -283,7 +283,7 @@ def __init__( clock_param = "f_clock" if "f_clock" in kwargs else "f_clock_mhz" fd_parameters_values = [ f"{key}={kwargs[key]}" - for key in (clock_param,) + PCAN_FD_PARAMETER_LIST + for key in (clock_param, *PCAN_FD_PARAMETER_LIST) if key in kwargs ] @@ -413,7 +413,7 @@ def bits(n): def get_api_version(self): error, value = self.m_objPCANBasic.GetValue(PCAN_NONEBUS, PCAN_API_VERSION) if error != PCAN_ERROR_OK: - raise CanInitializationError(f"Failed to read pcan basic api version") + raise CanInitializationError("Failed to read pcan basic api version") # fix https://github.com/hardbyte/python-can/issues/1642 version_string = value.decode("ascii").replace(",", ".").replace(" ", "") diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 183a9ba12..26ae63ca6 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -91,7 +91,7 @@ def __init__(self, channel, host, port, can_filters=None, **kwargs): ) self._tcp_send(f"< open {channel} >") self._expect_msg("< ok >") - self._tcp_send(f"< rawmode >") + self._tcp_send("< rawmode >") self._expect_msg("< ok >") super().__init__(channel=channel, can_filters=can_filters, **kwargs) diff --git a/can/interfaces/systec/ucan.py b/can/interfaces/systec/ucan.py index bbc484314..f969532d7 100644 --- a/can/interfaces/systec/ucan.py +++ b/can/interfaces/systec/ucan.py @@ -409,7 +409,7 @@ def init_hardware(self, serial=None, device_number=ANY_MODULE): Initializes the device with the corresponding serial or device number. :param int or None serial: Serial number of the USB-CANmodul. - :param int device_number: Device number (0 – 254, or :const:`ANY_MODULE` for the first device). + :param int device_number: Device number (0 - 254, or :const:`ANY_MODULE` for the first device). """ if not self._hw_is_initialized: # initialize hardware either by device number or serial diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 55aeb5da4..cedf25666 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -69,19 +69,17 @@ class VectorBus(BusABC): """The CAN Bus implemented for the Vector interface.""" - deprecated_args = dict( - sjwAbr="sjw_abr", - tseg1Abr="tseg1_abr", - tseg2Abr="tseg2_abr", - sjwDbr="sjw_dbr", - tseg1Dbr="tseg1_dbr", - tseg2Dbr="tseg2_dbr", - ) - @deprecated_args_alias( deprecation_start="4.0.0", deprecation_end="5.0.0", - **deprecated_args, + **{ + "sjwAbr": "sjw_abr", + "tseg1Abr": "tseg1_abr", + "tseg2Abr": "tseg2_abr", + "sjwDbr": "sjw_dbr", + "tseg1Dbr": "tseg1_dbr", + "tseg2Dbr": "tseg2_dbr", + }, ) def __init__( self, diff --git a/can/io/asc.py b/can/io/asc.py index 3114acfbe..f039cda32 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -64,8 +64,8 @@ def __init__( self.internal_events_logged = False def _extract_header(self) -> None: - for line in self.file: - line = line.strip() + for _line in self.file: + line = _line.strip() datetime_match = re.match( r"date\s+\w+\s+(?P.+)", line, re.IGNORECASE @@ -255,8 +255,8 @@ def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Messag def __iter__(self) -> Generator[Message, None, None]: self._extract_header() - for line in self.file: - line = line.strip() + for _line in self.file: + line = _line.strip() trigger_match = re.match( r"begin\s+triggerblock\s+\w+\s+(?P.+)", diff --git a/can/io/trc.py b/can/io/trc.py index ccf122d57..889d55196 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -68,8 +68,8 @@ def __init__( def _extract_header(self): line = "" - for line in self.file: - line = line.strip() + for _line in self.file: + line = _line.strip() if line.startswith(";$FILEVERSION"): logger.debug("TRCReader: Found file version '%s'", line) try: diff --git a/can/logger.py b/can/logger.py index 56f9156a8..f20965b04 100644 --- a/can/logger.py +++ b/can/logger.py @@ -3,16 +3,18 @@ import re import sys from datetime import datetime -from typing import Any, Dict, List, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, List, Sequence, Tuple, Union import can -from can.io import BaseRotatingLogger -from can.io.generic import MessageWriter from can.util import cast_from_string from . import Bus, BusState, Logger, SizedRotatingLogger from .typechecking import CanFilter, CanFilters +if TYPE_CHECKING: + from can.io import BaseRotatingLogger + from can.io.generic import MessageWriter + def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: """Adds common options to an argument parser.""" diff --git a/can/util.py b/can/util.py index 402934379..42b1f49ac 100644 --- a/can/util.py +++ b/can/util.py @@ -190,9 +190,8 @@ def load_config( ) # Slightly complex here to only search for the file config if required - for cfg in config_sources: - if callable(cfg): - cfg = cfg(context) + for _cfg in config_sources: + cfg = _cfg(context) if callable(_cfg) else _cfg # remove legacy operator (and copy to interface if not already present) if "bustype" in cfg: if "interface" not in cfg or not cfg["interface"]: diff --git a/can/viewer.py b/can/viewer.py index be7f76b73..db19fd1f6 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -537,7 +537,7 @@ def parse_args(args: List[str]) -> Tuple: scaling.append(float(t)) if scaling: - data_structs[key] = (struct.Struct(fmt),) + tuple(scaling) + data_structs[key] = (struct.Struct(fmt), *scaling) else: data_structs[key] = struct.Struct(fmt) diff --git a/pyproject.toml b/pyproject.toml index 6ee1dbadc..e20fd2785 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,9 +59,9 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==2.17.*", - "ruff==0.0.269", - "black==23.3.*", - "mypy==1.3.*", + "ruff==0.0.292", + "black==23.9.*", + "mypy==1.5.*", ] seeedstudio = ["pyserial>=3.0"] serial = ["pyserial~=3.0"] @@ -91,7 +91,7 @@ examples = ["*.py"] can = ["py.typed"] [tool.setuptools.packages.find] -include = ["can*", "scripts"] +include = ["can*"] [tool.mypy] warn_return_any = true @@ -128,14 +128,22 @@ exclude = [ [tool.ruff] select = [ - "F401", # unused-imports - "UP", # pyupgrade - "I", # isort - "E", # pycodestyle errors - "W", # pycodestyle warnings + "F", # pyflakes + "UP", # pyupgrade + "I", # isort + "E", # pycodestyle errors + "W", # pycodestyle warnings + "PL", # pylint + "RUF", # ruff-specific rules + "C4", # flake8-comprehensions + "TCH", # flake8-type-checking ] ignore = [ - "E501", # Line too long + "E501", # Line too long + "F403", # undefined-local-with-import-star + "F405", # undefined-local-with-import-star-usage + "PLR", # pylint refactor + "RUF012", # mutable-class-default ] [tool.ruff.isort] diff --git a/test/back2back_test.py b/test/back2back_test.py index 52bfaf716..cd5aca6aa 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -116,7 +116,7 @@ def test_timestamp(self): self.assertTrue( 1.75 <= delta_time <= 2.25, "Time difference should have been 2s +/- 250ms." - "But measured {}".format(delta_time), + f"But measured {delta_time}", ) def test_standard_message(self): diff --git a/test/network_test.py b/test/network_test.py index 61690c1b4..250976fb2 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -6,12 +6,15 @@ import threading import unittest +import can + logging.getLogger(__file__).setLevel(logging.WARNING) + # make a random bool: -rbool = lambda: bool(round(random.random())) +def rbool(): + return bool(round(random.random())) -import can channel = "vcan0" diff --git a/test/test_player.py b/test/test_player.py index 5ad6e774c..e5e77fe8a 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -60,14 +60,8 @@ def test_play_virtual(self): dlc=8, data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], ) - if sys.version_info >= (3, 8): - # The args argument was introduced with python 3.8 - self.assertTrue( - msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0]) - ) - self.assertTrue( - msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0]) - ) + self.assertTrue(msg1.equals(self.mock_virtual_bus.send.mock_calls[0].args[0])) + self.assertTrue(msg2.equals(self.mock_virtual_bus.send.mock_calls[1].args[0])) self.assertSuccessfulCleanup() def test_play_virtual_verbose(self): diff --git a/test/test_slcan.py b/test/test_slcan.py index a2f8f5d15..f74207b9f 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -12,8 +12,8 @@ """ Mentioned in #1010 & #1490 -> PyPy works best with pure Python applications. Whenever you use a C extension module, -> it runs much slower than in CPython. The reason is that PyPy can't optimize C extension modules since they're not fully supported. +> PyPy works best with pure Python applications. Whenever you use a C extension module, +> it runs much slower than in CPython. The reason is that PyPy can't optimize C extension modules since they're not fully supported. > In addition, PyPy has to emulate reference counting for that part of the code, making it even slower. https://realpython.com/pypy-faster-python/#it-doesnt-work-well-with-c-extensions diff --git a/test/test_socketcan.py b/test/test_socketcan.py index f756cb93a..af06b8169 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -9,8 +9,6 @@ import warnings from unittest.mock import patch -from .config import TEST_INTERFACE_SOCKETCAN - import can from can.interfaces.socketcan.constants import ( CAN_BCM_TX_DELETE, @@ -28,7 +26,7 @@ build_bcm_update_header, ) -from .config import IS_LINUX, IS_PYPY +from .config import IS_LINUX, IS_PYPY, TEST_INTERFACE_SOCKETCAN class SocketCANTest(unittest.TestCase): diff --git a/test/test_vector.py b/test/test_vector.py index 93aba9c7b..ab9f8a928 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -881,7 +881,7 @@ def _find_xl_channel_config(serial: int, channel: int) -> xlclass.XLchannelConfi raise LookupError("XLchannelConfig not found.") -@functools.lru_cache() +@functools.lru_cache def _find_virtual_can_serial() -> int: """Serial number might be 0 or 100 depending on driver version.""" xl_driver_config = xlclass.XLdriverConfig() From e3d912b0bf5b5f21482d85041d1172af5b622188 Mon Sep 17 00:00:00 2001 From: Mateusz Dionizy <62315827+deronek@users.noreply.github.com> Date: Mon, 9 Oct 2023 00:06:17 +0200 Subject: [PATCH 1073/1235] Send HighPriority Message to flush VectorBus Tx buffer (#1636) * Refactor flush_tx_queue for VectorBus * Remove test_flush_tx_buffer_mocked Implementation cannot be tested due to Vector TX queue being not accessible * Update formatting in flush_tx_queue * Add tests for VectorBus flush_tx_queue * Refactor docstring for VectorBus flush_tx_queue --- can/interfaces/vector/canlib.py | 35 ++++++++++++++++++++++++++++++++- test/test_vector.py | 33 ++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index cedf25666..bc564958f 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -876,7 +876,40 @@ def _build_xl_can_tx_event(msg: Message) -> xlclass.XLcanTxEvent: return xl_can_tx_event def flush_tx_buffer(self) -> None: - self.xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask) + """ + Flush the TX buffer of the bus. + + Implementation does not use function ``xlCanFlushTransmitQueue`` of the XL driver, as it works only + for XL family devices. + + .. warning:: + Using this function will flush the queue and send a high voltage message (ID = 0, DLC = 0, no data). + """ + if self._can_protocol is CanProtocol.CAN_FD: + xl_can_tx_event = xlclass.XLcanTxEvent() + xl_can_tx_event.tag = xldefine.XL_CANFD_TX_EventTags.XL_CAN_EV_TAG_TX_MSG + xl_can_tx_event.tagData.canMsg.msgFlags |= ( + xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_HIGHPRIO + ) + + self.xldriver.xlCanTransmitEx( + self.port_handle, + self.mask, + ctypes.c_uint(1), + ctypes.c_uint(0), + xl_can_tx_event, + ) + else: + xl_event = xlclass.XLevent() + xl_event.tag = xldefine.XL_EventTags.XL_TRANSMIT_MSG + xl_event.tagData.msg.flags |= ( + xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_OVERRUN + | xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_WAKEUP + ) + + self.xldriver.xlCanTransmit( + self.port_handle, self.mask, ctypes.c_uint(1), xl_event + ) def shutdown(self) -> None: super().shutdown() diff --git a/test/test_vector.py b/test/test_vector.py index ab9f8a928..3db43fbbb 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -613,7 +613,38 @@ def test_receive_fd_non_msg_event() -> None: def test_flush_tx_buffer_mocked(mock_xldriver) -> None: bus = can.Bus(channel=0, interface="vector", _testing=True) bus.flush_tx_buffer() - can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called() + transmit_args = can.interfaces.vector.canlib.xldriver.xlCanTransmit.call_args[0] + + num_msg = transmit_args[2] + assert num_msg.value == ctypes.c_uint(1).value + + event = transmit_args[3] + assert isinstance(event, xlclass.XLevent) + assert event.tag & xldefine.XL_EventTags.XL_TRANSMIT_MSG + assert event.tagData.msg.flags & ( + xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_OVERRUN + | xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_WAKEUP + ) + + +def test_flush_tx_buffer_fd_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, interface="vector", fd=True, _testing=True) + bus.flush_tx_buffer() + transmit_args = can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.call_args[0] + + num_msg = transmit_args[2] + assert num_msg.value == ctypes.c_uint(1).value + + num_msg_sent = transmit_args[3] + assert num_msg_sent.value == ctypes.c_uint(0).value + + event = transmit_args[4] + assert isinstance(event, xlclass.XLcanTxEvent) + assert event.tag & xldefine.XL_CANFD_TX_EventTags.XL_CAN_EV_TAG_TX_MSG + assert ( + event.tagData.canMsg.msgFlags + & xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_HIGHPRIO + ) @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") From b547dfc989f274a75a8ab7d2f3a25f57ff693e0a Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 9 Oct 2023 00:26:49 +0200 Subject: [PATCH 1074/1235] PCAN: remove Windows registry check (#1672) Co-authored-by: zariiii9003 --- can/interfaces/pcan/basic.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index a57340955..e003e83f9 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -24,9 +24,6 @@ IS_WINDOWS = PLATFORM == "Windows" IS_LINUX = PLATFORM == "Linux" -if IS_WINDOWS: - import winreg - logger = logging.getLogger("can.pcan") # /////////////////////////////////////////////////////////// @@ -668,14 +665,6 @@ class PCANBasic: def __init__(self): if platform.system() == "Windows": load_library_func = windll.LoadLibrary - - # look for Peak drivers in Windows registry - with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as reg: - try: - with winreg.OpenKey(reg, r"SOFTWARE\PEAK-System\PEAK-Drivers"): - pass - except OSError: - raise OSError("The PEAK-driver could not be found!") from None else: load_library_func = cdll.LoadLibrary From d2dc07d85a6630a083f3b38df833c6d535d53172 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 9 Oct 2023 00:36:23 +0200 Subject: [PATCH 1075/1235] Test Python 3.12 (#1673) Co-authored-by: zariiii9003 --- .github/workflows/ci.yml | 15 ++++++++++----- pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 686a5d77b..c1aa8935a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,14 +22,16 @@ jobs: "3.9", "3.10", "3.11", + "3.12", "pypy-3.8", "pypy-3.9", ] - include: - # Only test on a single configuration while there are just pre-releases - - os: ubuntu-latest - experimental: true - python-version: "3.12.0-alpha - 3.12.0" + # uncomment when python 3.13.0 alpha is available + #include: + # # Only test on a single configuration while there are just pre-releases + # - os: ubuntu-latest + # experimental: true + # python-version: "3.13.0-alpha - 3.13.0" fail-fast: false steps: - uses: actions/checkout@v3 @@ -95,6 +97,9 @@ jobs: - name: mypy 3.11 run: | mypy --python-version 3.11 . + - name: mypy 3.12 + run: | + mypy --python-version 3.12 . - name: ruff run: | ruff check can diff --git a/pyproject.toml b/pyproject.toml index e20fd2785..a34508ee8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ classifiers = [ "Intended Audience :: Telecommunications Industry", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Natural Language :: English", - "Natural Language :: English", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", @@ -35,6 +34,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: System :: Hardware :: Hardware Drivers", From b0ba12fa833e60bc0661cb1e3f325501f6171609 Mon Sep 17 00:00:00 2001 From: Daniel Hrisca Date: Thu, 12 Oct 2023 21:15:04 +0300 Subject: [PATCH 1076/1235] Vector: Skip the can_op_mode check if the device reports can_op_mode=0 (#1678) --- can/interfaces/vector/canlib.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index bc564958f..024f6a4c9 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -538,16 +538,19 @@ def _check_can_settings( ) # check CAN operation mode - if fd: - settings_acceptable &= bool( - bus_params_data.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD - ) - elif bus_params_data.can_op_mode != 0: # can_op_mode is always 0 for cancaseXL - settings_acceptable &= bool( - bus_params_data.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 - ) + # skip the check if can_op_mode is 0 + # as it happens for cancaseXL, VN7600 and sometimes on other hardware (VN1640) + if bus_params_data.can_op_mode: + if fd: + settings_acceptable &= bool( + bus_params_data.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD + ) + else: + settings_acceptable &= bool( + bus_params_data.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 + ) # check bitrates if bitrate: From b2689ae184363692fc030248e8b55fd0e40ae068 Mon Sep 17 00:00:00 2001 From: Daniel Hrisca Date: Thu, 12 Oct 2023 21:18:20 +0300 Subject: [PATCH 1077/1235] Add BitTiming.iterate_from_sample_point static methods (#1671) * add the possibility to return all the possible solutions using the from_sample_point static methods * Format code with black * add iterators for timings from sample point * update docstrings * make ruff happy * add a small test for iterate_from_sample_point * Format code with black --------- Co-authored-by: danielhrisca --- can/bit_timing.py | 114 +++++++++++++++++++++++++++++++--------- test/test_bit_timing.py | 26 +++++++++ 2 files changed, 115 insertions(+), 25 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index 3e8cb1bcd..f5d50eac1 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -213,17 +213,10 @@ def from_registers( ) @classmethod - def from_sample_point( + def iterate_from_sample_point( cls, f_clock: int, bitrate: int, sample_point: float = 69.0 - ) -> "BitTiming": - """Create a :class:`~can.BitTiming` instance for a sample point. - - This function tries to find bit timings, which are close to the requested - sample point. It does not take physical bus properties into account, so the - calculated bus timings might not work properly for you. - - The :func:`oscillator_tolerance` function might be helpful to evaluate the - bus timings. + ) -> Iterator["BitTiming"]: + """Create a :class:`~can.BitTiming` iterator with all the solutions for a sample point. :param int f_clock: The CAN system clock frequency in Hz. @@ -238,7 +231,6 @@ def from_sample_point( if sample_point < 50.0: raise ValueError(f"sample_point (={sample_point}) must not be below 50%.") - possible_solutions: List[BitTiming] = [] for brp in range(1, 65): nbt = round(int(f_clock / (bitrate * brp))) if nbt < 8: @@ -264,10 +256,40 @@ def from_sample_point( sjw=sjw, strict=True, ) - possible_solutions.append(bt) + yield bt except ValueError: continue + @classmethod + def from_sample_point( + cls, f_clock: int, bitrate: int, sample_point: float = 69.0 + ) -> "BitTiming": + """Create a :class:`~can.BitTiming` instance for a sample point. + + This function tries to find bit timings, which are close to the requested + sample point. It does not take physical bus properties into account, so the + calculated bus timings might not work properly for you. + + The :func:`oscillator_tolerance` function might be helpful to evaluate the + bus timings. + + :param int f_clock: + The CAN system clock frequency in Hz. + :param int bitrate: + Bitrate in bit/s. + :param int sample_point: + The sample point value in percent. + :raises ValueError: + if the arguments are invalid. + """ + + if sample_point < 50.0: + raise ValueError(f"sample_point (={sample_point}) must not be below 50%.") + + possible_solutions: List[BitTiming] = list( + cls.iterate_from_sample_point(f_clock, bitrate, sample_point) + ) + if not possible_solutions: raise ValueError("No suitable bit timings found.") @@ -729,22 +751,15 @@ def from_bitrate_and_segments( # pylint: disable=too-many-arguments return bt @classmethod - def from_sample_point( + def iterate_from_sample_point( cls, f_clock: int, nom_bitrate: int, nom_sample_point: float, data_bitrate: int, data_sample_point: float, - ) -> "BitTimingFd": - """Create a :class:`~can.BitTimingFd` instance for a given nominal/data sample point pair. - - This function tries to find bit timings, which are close to the requested - sample points. It does not take physical bus properties into account, so the - calculated bus timings might not work properly for you. - - The :func:`oscillator_tolerance` function might be helpful to evaluate the - bus timings. + ) -> Iterator["BitTimingFd"]: + """Create an :class:`~can.BitTimingFd` iterator with all the solutions for a sample point. :param int f_clock: The CAN system clock frequency in Hz. @@ -769,8 +784,6 @@ def from_sample_point( f"data_sample_point (={data_sample_point}) must not be below 50%." ) - possible_solutions: List[BitTimingFd] = [] - sync_seg = 1 for nom_brp in range(1, 257): @@ -818,10 +831,61 @@ def from_sample_point( data_sjw=data_sjw, strict=True, ) - possible_solutions.append(bt) + yield bt except ValueError: continue + @classmethod + def from_sample_point( + cls, + f_clock: int, + nom_bitrate: int, + nom_sample_point: float, + data_bitrate: int, + data_sample_point: float, + ) -> "BitTimingFd": + """Create a :class:`~can.BitTimingFd` instance for a sample point. + + This function tries to find bit timings, which are close to the requested + sample points. It does not take physical bus properties into account, so the + calculated bus timings might not work properly for you. + + The :func:`oscillator_tolerance` function might be helpful to evaluate the + bus timings. + + :param int f_clock: + The CAN system clock frequency in Hz. + :param int nom_bitrate: + Nominal bitrate in bit/s. + :param int nom_sample_point: + The sample point value of the arbitration phase in percent. + :param int data_bitrate: + Data bitrate in bit/s. + :param int data_sample_point: + The sample point value of the data phase in percent. + :raises ValueError: + if the arguments are invalid. + """ + if nom_sample_point < 50.0: + raise ValueError( + f"nom_sample_point (={nom_sample_point}) must not be below 50%." + ) + + if data_sample_point < 50.0: + raise ValueError( + f"data_sample_point (={data_sample_point}) must not be below 50%." + ) + + possible_solutions: List[BitTimingFd] = list( + cls.iterate_from_sample_point( + f_clock, + nom_bitrate, + nom_sample_point, + data_bitrate, + data_sample_point, + ) + ) + if not possible_solutions: raise ValueError("No suitable bit timings found.") diff --git a/test/test_bit_timing.py b/test/test_bit_timing.py index 6852687a5..514c31244 100644 --- a/test/test_bit_timing.py +++ b/test/test_bit_timing.py @@ -286,6 +286,32 @@ def test_from_sample_point(): ) +def test_iterate_from_sample_point(): + for sp in range(50, 100): + solutions = list( + can.BitTiming.iterate_from_sample_point( + f_clock=16_000_000, + bitrate=500_000, + sample_point=sp, + ) + ) + assert len(solutions) >= 2 + + for nsp in range(50, 100): + for dsp in range(50, 100): + solutions = list( + can.BitTimingFd.iterate_from_sample_point( + f_clock=80_000_000, + nom_bitrate=500_000, + nom_sample_point=nsp, + data_bitrate=2_000_000, + data_sample_point=dsp, + ) + ) + + assert len(solutions) >= 2 + + def test_equality(): t1 = can.BitTiming.from_registers(f_clock=8_000_000, btr0=0x00, btr1=0x14) t2 = can.BitTiming(f_clock=8_000_000, brp=1, tseg1=5, tseg2=2, sjw=1, nof_samples=1) From e869fb7242f8a4bf12593f6a37550a462764ca00 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 16 Oct 2023 15:47:41 -0400 Subject: [PATCH 1078/1235] Fix ThreadBasedCyclicSendTask thread not being stopped on Windows (#1679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also adding unit test to cover RestartableCyclicTaskABC Co-authored-by: Pierre-Luc Tessier Gagné --- can/broadcastmanager.py | 5 +++-- test/simplecyclic_test.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 398114a59..0ac9b6adc 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -269,9 +269,10 @@ def __init__( self.start() def stop(self) -> None: - if USE_WINDOWS_EVENTS: - win32event.CancelWaitableTimer(self.event.handle) self.stopped = True + if USE_WINDOWS_EVENTS: + # Reset and signal any pending wait by setting the timer to 0 + win32event.SetWaitableTimer(self.event.handle, 0, 0, None, None, False) def start(self) -> None: self.stopped = False diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 650a1fddf..21e88e9f0 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -152,6 +152,41 @@ def test_stopping_perodic_tasks(self): bus.shutdown() + def test_restart_perodic_tasks(self): + period = 0.01 + safe_timeout = period * 5 + + msg = can.Message( + is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] + ) + + with can.ThreadSafeBus(interface="virtual", receive_own_messages=True) as bus: + task = bus.send_periodic(msg, period) + self.assertIsInstance(task, can.broadcastmanager.RestartableCyclicTaskABC) + + # Test that the task is sending messages + sleep(safe_timeout) + assert not bus.queue.empty(), "messages should have been transmitted" + + # Stop the task and check that messages are no longer being sent + bus.stop_all_periodic_tasks(remove_tasks=False) + sleep(safe_timeout) + while not bus.queue.empty(): + bus.recv(timeout=period) + sleep(safe_timeout) + assert bus.queue.empty(), "messages should not have been transmitted" + + # Restart the task and check that messages are being sent again + task.start() + sleep(safe_timeout) + assert not bus.queue.empty(), "messages should have been transmitted" + + # Stop all tasks and wait for the thread to exit + bus.stop_all_periodic_tasks() + if isinstance(task, can.broadcastmanager.ThreadBasedCyclicSendTask): + # Avoids issues where the thread is still running when the bus is shutdown + task.thread.join(safe_timeout) + @unittest.skipIf(IS_CI, "fails randomly when run on CI server") def test_thread_based_cyclic_send_task(self): bus = can.ThreadSafeBus(interface="virtual") From 7b353ca240ea2ff73901eabce3996861e17a2deb Mon Sep 17 00:00:00 2001 From: MattWoodhead Date: Mon, 16 Oct 2023 22:12:52 +0100 Subject: [PATCH 1079/1235] Implement _detect_available_configs for the Ixxat bus. (#1607) * Add _detect_available_configs to ixxat bus * Add typing and cover CI test failure * Format code with black * Format code with black * re-order imports for ruff * Update ixxat docs * fix doctest * Update test_interface_ixxat.py * make ruff happy --------- Co-authored-by: MattWoodhead Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/ixxat/canlib.py | 7 +++- can/interfaces/ixxat/canlib_vcinpl.py | 55 +++++++++++++++++++++++++- can/interfaces/ixxat/canlib_vcinpl2.py | 10 ++--- can/interfaces/pcan/pcan.py | 4 +- doc/interfaces/ixxat.rst | 31 +++++++++++++-- test/test_interface_ixxat.py | 12 ++++++ 6 files changed, 105 insertions(+), 14 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index f18c86acd..330ccdcd9 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,4 +1,4 @@ -from typing import Callable, Optional, Sequence, Union +from typing import Callable, List, Optional, Sequence, Union import can.interfaces.ixxat.canlib_vcinpl as vcinpl import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 @@ -8,6 +8,7 @@ CyclicSendTaskABC, Message, ) +from can.typechecking import AutoDetectedConfig class IXXATBus(BusABC): @@ -170,3 +171,7 @@ def state(self) -> BusState: Return the current state of the hardware """ return self.bus.state + + @staticmethod + def _detect_available_configs() -> List[AutoDetectedConfig]: + return vcinpl._detect_available_configs() diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 922c683b8..334adee11 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -14,7 +14,7 @@ import logging import sys import warnings -from typing import Callable, Optional, Sequence, Tuple, Union +from typing import Callable, List, Optional, Sequence, Tuple, Union from can import ( BusABC, @@ -28,6 +28,7 @@ from can.ctypesutil import HANDLE, PHANDLE, CLibrary from can.ctypesutil import HRESULT as ctypes_HRESULT from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError +from can.typechecking import AutoDetectedConfig from can.util import deprecated_args_alias from . import constants, structures @@ -943,3 +944,55 @@ def get_ixxat_hwids(): _canlib.vciEnumDeviceClose(device_handle) return hwids + + +def _detect_available_configs() -> List[AutoDetectedConfig]: + config_list = [] # list in wich to store the resulting bus kwargs + + # used to detect HWID + device_handle = HANDLE() + device_info = structures.VCIDEVICEINFO() + + # used to attempt to open channels + channel_handle = HANDLE() + device_handle2 = HANDLE() + + try: + _canlib.vciEnumDeviceOpen(ctypes.byref(device_handle)) + while True: + try: + _canlib.vciEnumDeviceNext(device_handle, ctypes.byref(device_info)) + except StopIteration: + break + else: + hwid = device_info.UniqueHardwareId.AsChar.decode("ascii") + _canlib.vciDeviceOpen( + ctypes.byref(device_info.VciObjectId), + ctypes.byref(device_handle2), + ) + for channel in range(4): + try: + _canlib.canChannelOpen( + device_handle2, + channel, + constants.FALSE, + ctypes.byref(channel_handle), + ) + except Exception: + # Array outside of bounds error == accessing a channel not in the hardware + break + else: + _canlib.canChannelClose(channel_handle) + config_list.append( + { + "interface": "ixxat", + "channel": channel, + "unique_hardware_id": hwid, + } + ) + _canlib.vciDeviceClose(device_handle2) + _canlib.vciEnumDeviceClose(device_handle) + except AttributeError: + pass # _canlib is None in the CI tests -> return a blank list + + return config_list diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 2c306c880..aaefa1bf9 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -509,17 +509,15 @@ def __init__( tseg1_abr is None or tseg2_abr is None or sjw_abr is None ): raise ValueError( - "To use bitrate {} (that has not predefined preset) is mandatory to use also parameters tseg1_abr, tseg2_abr and swj_abr".format( - bitrate - ) + f"To use bitrate {bitrate} (that has not predefined preset) is mandatory " + f"to use also parameters tseg1_abr, tseg2_abr and swj_abr" ) if data_bitrate not in constants.CAN_DATABITRATE_PRESETS and ( tseg1_dbr is None or tseg2_dbr is None or sjw_dbr is None ): raise ValueError( - "To use data_bitrate {} (that has not predefined preset) is mandatory to use also parameters tseg1_dbr, tseg2_dbr and swj_dbr".format( - data_bitrate - ) + f"To use data_bitrate {data_bitrate} (that has not predefined preset) is mandatory " + f"to use also parameters tseg1_dbr, tseg2_dbr and swj_dbr" ) if rx_fifo_size <= 0: diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 01a4b1dc3..884c1680b 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -396,9 +396,7 @@ def bits(n): for b in bits(error): stsReturn = self.m_objPCANBasic.GetErrorText(b, 0x9) if stsReturn[0] != PCAN_ERROR_OK: - text = "An error occurred. Error-code's text ({:X}h) couldn't be retrieved".format( - error - ) + text = f"An error occurred. Error-code's text ({error:X}h) couldn't be retrieved" else: text = stsReturn[1].decode("utf-8", errors="replace") diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index f73a01036..1337bf738 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -56,17 +56,42 @@ VCI documentation, section "Message filters" for more info. List available devices ---------------------- -In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id. -To get a list of all connected IXXAT you can use the function ``get_ixxat_hwids()`` as demonstrated below: + +In case you have connected multiple IXXAT devices, you have to select them by using their unique hardware id. +The function :meth:`~can.detect_available_configs` can be used to generate a list of :class:`~can.BusABC` constructors +(including the channel number and unique hardware ID number for the connected devices). .. testsetup:: ixxat + from unittest.mock import Mock + import can + assert hasattr(can, "detect_available_configs") + can.detect_available_configs = Mock( + "interface", + return_value=[{'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW441489'}, {'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW107422'}, {'interface': 'ixxat', 'channel': 1, 'unique_hardware_id': 'HW107422'}], + ) + + .. doctest:: ixxat + + >>> import can + >>> configs = can.detect_available_configs("ixxat") + >>> for config in configs: + ... print(config) + {'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW441489'} + {'interface': 'ixxat', 'channel': 0, 'unique_hardware_id': 'HW107422'} + {'interface': 'ixxat', 'channel': 1, 'unique_hardware_id': 'HW107422'} + + +You may also get a list of all connected IXXAT devices using the function ``get_ixxat_hwids()`` as demonstrated below: + + .. testsetup:: ixxat2 + from unittest.mock import Mock import can.interfaces.ixxat assert hasattr(can.interfaces.ixxat, "get_ixxat_hwids") can.interfaces.ixxat.get_ixxat_hwids = Mock(side_effect=lambda: ['HW441489', 'HW107422']) - .. doctest:: ixxat + .. doctest:: ixxat2 >>> from can.interfaces.ixxat import get_ixxat_hwids >>> for hwid in get_ixxat_hwids(): diff --git a/test/test_interface_ixxat.py b/test/test_interface_ixxat.py index 2ff016d97..90b5f7adc 100644 --- a/test/test_interface_ixxat.py +++ b/test/test_interface_ixxat.py @@ -51,6 +51,18 @@ def setUp(self): raise unittest.SkipTest("not available on this platform") def test_bus_creation(self): + try: + configs = can.detect_available_configs("ixxat") + if configs: + for interface_kwargs in configs: + bus = can.Bus(**interface_kwargs) + bus.shutdown() + else: + raise unittest.SkipTest("No adapters were detected") + except can.CanInterfaceNotImplementedError: + raise unittest.SkipTest("not available on this platform") + + def test_bus_creation_incorrect_channel(self): # non-existent channel -> use arbitrary high value with self.assertRaises(can.CanInitializationError): can.Bus(interface="ixxat", channel=0xFFFF) From 2d609005b2b51391638b86fcb802544411c5e4cc Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 16 Oct 2023 23:14:39 +0200 Subject: [PATCH 1080/1235] Add BitTiming/BitTimingFd support to KvaserBus (#1510) * add BitTiming parameter to KvaserBus * implement tests for bittiming classes with kvaser * set default number of samples to 1 * undo last change --- can/interfaces/kvaser/canlib.py | 86 +++++++++++++++++++++++++-------- test/test_kvaser.py | 32 ++++++++++++ 2 files changed, 98 insertions(+), 20 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 38949137d..0983e28dc 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -10,11 +10,13 @@ import logging import sys import time +from typing import Optional, Union -from can import BusABC, CanProtocol, Message -from can.util import time_perfcounter_correlation +from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message +from can.exceptions import CanError, CanInitializationError, CanOperationError +from can.typechecking import CanFilters +from can.util import check_or_adjust_timing_clock, time_perfcounter_correlation -from ...exceptions import CanError, CanInitializationError, CanOperationError from . import constants as canstat from . import structures @@ -199,6 +201,17 @@ def __check_bus_handle_validity(handle, function, arguments): errcheck=__check_status_initialization, ) + canSetBusParamsC200 = __get_canlib_function( + "canSetBusParamsC200", + argtypes=[ + c_canHandle, + ctypes.c_byte, + ctypes.c_byte, + ], + restype=canstat.c_canStatus, + errcheck=__check_status_initialization, + ) + canSetBusParamsFd = __get_canlib_function( "canSetBusParamsFd", argtypes=[ @@ -360,7 +373,13 @@ class KvaserBus(BusABC): The CAN Bus implemented for the Kvaser interface. """ - def __init__(self, channel, can_filters=None, **kwargs): + def __init__( + self, + channel: int, + can_filters: Optional[CanFilters] = None, + timing: Optional[Union[BitTiming, BitTimingFd]] = None, + **kwargs, + ): """ :param int channel: The Channel id to create this bus with. @@ -370,6 +389,12 @@ def __init__(self, channel, can_filters=None, **kwargs): Backend Configuration + :param timing: + An instance of :class:`~can.BitTiming` or :class:`~can.BitTimingFd` + to specify the bit timing parameters for the Kvaser interface. If provided, it + takes precedence over the all other timing-related parameters. + Note that the `f_clock` property of the `timing` instance must be 16_000_000 (16MHz) + for standard CAN or 80_000_000 (80MHz) for CAN FD. :param int bitrate: Bitrate of channel in bit/s :param bool accept_virtual: @@ -427,7 +452,7 @@ def __init__(self, channel, can_filters=None, **kwargs): exclusive = kwargs.get("exclusive", False) override_exclusive = kwargs.get("override_exclusive", False) accept_virtual = kwargs.get("accept_virtual", True) - fd = kwargs.get("fd", False) + fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False) data_bitrate = kwargs.get("data_bitrate", None) try: @@ -468,22 +493,43 @@ def __init__(self, channel, can_filters=None, **kwargs): ctypes.byref(ctypes.c_long(TIMESTAMP_RESOLUTION)), 4, ) - - if fd: - if "tseg1" not in kwargs and bitrate in BITRATE_FD: - # Use predefined bitrate for arbitration - bitrate = BITRATE_FD[bitrate] - if data_bitrate in BITRATE_FD: - # Use predefined bitrate for data - data_bitrate = BITRATE_FD[data_bitrate] - elif not data_bitrate: - # Use same bitrate for arbitration and data phase - data_bitrate = bitrate - canSetBusParamsFd(self._read_handle, data_bitrate, tseg1, tseg2, sjw) + if isinstance(timing, BitTimingFd): + timing = check_or_adjust_timing_clock(timing, [80_000_000]) + canSetBusParams( + self._read_handle, + timing.nom_bitrate, + timing.nom_tseg1, + timing.nom_tseg2, + timing.nom_sjw, + 1, + 0, + ) + canSetBusParamsFd( + self._read_handle, + timing.data_bitrate, + timing.data_tseg1, + timing.data_tseg2, + timing.data_sjw, + ) + elif isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, [16_000_000]) + canSetBusParamsC200(self._read_handle, timing.btr0, timing.btr1) else: - if "tseg1" not in kwargs and bitrate in BITRATE_OBJS: - bitrate = BITRATE_OBJS[bitrate] - canSetBusParams(self._read_handle, bitrate, tseg1, tseg2, sjw, no_samp, 0) + if fd: + if "tseg1" not in kwargs and bitrate in BITRATE_FD: + # Use predefined bitrate for arbitration + bitrate = BITRATE_FD[bitrate] + if data_bitrate in BITRATE_FD: + # Use predefined bitrate for data + data_bitrate = BITRATE_FD[data_bitrate] + elif not data_bitrate: + # Use same bitrate for arbitration and data phase + data_bitrate = bitrate + canSetBusParamsFd(self._read_handle, data_bitrate, tseg1, tseg2, sjw) + else: + if "tseg1" not in kwargs and bitrate in BITRATE_OBJS: + bitrate = BITRATE_OBJS[bitrate] + canSetBusParams(self._read_handle, bitrate, tseg1, tseg2, sjw, no_samp, 0) # By default, use local echo if single handle is used (see #160) local_echo = single_handle or receive_own_messages diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 1254f2fc7..abaf7b38f 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -21,6 +21,7 @@ def setUp(self): canlib.canIoCtl = Mock(return_value=0) canlib.canIoCtlInit = Mock(return_value=0) canlib.kvReadTimer = Mock() + canlib.canSetBusParamsC200 = Mock() canlib.canSetBusParams = Mock() canlib.canSetBusParamsFd = Mock() canlib.canBusOn = Mock() @@ -179,6 +180,37 @@ def test_canfd_default_data_bitrate(self): 0, constants.canFD_BITRATE_500K_80P, 0, 0, 0 ) + def test_can_timing(self): + canlib.canSetBusParams.reset_mock() + canlib.canSetBusParamsFd.reset_mock() + timing = can.BitTiming.from_bitrate_and_segments( + f_clock=16_000_000, + bitrate=125_000, + tseg1=13, + tseg2=2, + sjw=1, + ) + can.Bus(channel=0, interface="kvaser", timing=timing) + canlib.canSetBusParamsC200.assert_called_once_with(0, timing.btr0, timing.btr1) + + def test_canfd_timing(self): + canlib.canSetBusParams.reset_mock() + canlib.canSetBusParamsFd.reset_mock() + timing = can.BitTimingFd.from_bitrate_and_segments( + f_clock=80_000_000, + nom_bitrate=500_000, + nom_tseg1=68, + nom_tseg2=11, + nom_sjw=10, + data_bitrate=2_000_000, + data_tseg1=10, + data_tseg2=9, + data_sjw=8, + ) + can.Bus(channel=0, interface="kvaser", timing=timing) + canlib.canSetBusParams.assert_called_once_with(0, 500_000, 68, 11, 10, 1, 0) + canlib.canSetBusParamsFd.assert_called_once_with(0, 2_000_000, 10, 9, 8) + def test_canfd_nondefault_data_bitrate(self): canlib.canSetBusParams.reset_mock() canlib.canSetBusParamsFd.reset_mock() From 61ee42b2ae61c882f40033b97773b59c37acac59 Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Tue, 17 Oct 2023 11:47:20 +0200 Subject: [PATCH 1081/1235] Update Changelog for Release v.4.3.0rc0 (#1680) * Upate Changelog for Release v.4.3.0 * Update CHANGELOG.md Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Make requested changes to CHANGELOG, sort by PR ID * Fix remaining comments * Add PR for Kvaser BitTiming support * add 1679 (bugfix for 1666) * Add PR #1607, change changelog version to 4.3.0rc * Bump project version to 4.3.0rc0 --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- CHANGELOG.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ can/__init__.py | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 493ac1966..651b222fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,58 @@ +Version 4.3.0rc0 +=========== + +Breaking Changes +---------------- +* Raise Minimum Python Version to 3.8 (#1597) +* Do not stop notifier if exception was handled (#1645) + +Bug Fixes +--------- +* Vector: channel detection fails, if there is an active flexray channel (#1634) +* ixxat: Fix exception in 'state' property on bus coupling errors (#1647) +* NeoVi: Fixed serial number range (#1650) +* PCAN: Fix timestamp offset due to timezone (#1651) +* Catch `pywintypes.error` in broadcast manager (#1659) +* Fix BLFReader error for incomplete or truncated stream (#1662) +* PCAN: remove Windows registry check to fix 32bit compatibility (#1672) +* Vector: Skip the `can_op_mode check` if the device reports `can_op_mode=0` (#1678) + +Features +-------- + +### API +* Add `modifier_callback` parameter to `BusABC.send_periodic` for auto-modifying cyclic tasks (#703) +* Add `protocol` property to BusABC to determine active CAN Protocol (#1532) +* Change Bus constructor implementation and typing (#1557) +* Add optional `strict` parameter to relax BitTiming & BitTimingFd Validation (#1618) +* Add `BitTiming.iterate_from_sample_point` static methods (#1671) + +### IO +* Can Player compatibility with interfaces that use additional configuration (#1610) + +### Interface Improvements +* Kvaser: Add BitTiming/BitTimingFd support to KvaserBus (#1510) +* Ixxat: Implement `detect_available_configs` for the Ixxat bus. (#1607) +* NeoVi: Enable send and receive on network ID above 255 (#1627) +* Vector: Send HighPriority Message to flush Tx buffer (#1636) +* PCAN: Optimize send performance (#1640) +* PCAN: Support version string of older PCAN basic API (#1644) +* Kvaser: add parameter exclusive and `override_exclusive` (#1660) + +### Miscellaneous +* Distinguish Text/Binary-IO for Reader/Writer classes. (#1585) +* Convert setup.py to pyproject.toml (#1592) +* activate ruff pycodestyle checks (#1602) +* Update linter instructions in development.rst (#1603) +* remove unnecessary script files (#1604) +* BigEndian test fixes (#1625) +* align `ID:` in can.Message string (#1635) +* Use same configuration file as Linux on macOS (#1657) +* We do not need to account for drift when we `USE_WINDOWS_EVENTS` (#1666, #1679) +* Update linters, activate more ruff rules (#1669) +* Add Python 3.12 Support / Test Python 3.12 (#1673) + + Version 4.2.2 ============= diff --git a/can/__init__.py b/can/__init__.py index 46a461b38..48dda308d 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.2.2" +__version__ = "4.3.0rc0" __all__ = [ "ASCReader", "ASCWriter", From 38c4dc4b9ff2a932cdd1b776fb6a62df8a3e63b6 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:55:23 +0200 Subject: [PATCH 1082/1235] Vector: use global channel_index if provided (#1681) * fix XL_ERR_INVALID_CHANNEL_MASK for multiple devices with the same serial * add CHANGELOG.md entry --- CHANGELOG.md | 1 + can/interfaces/vector/canlib.py | 21 ++++++++++++----- test/test_vector.py | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 651b222fb..0220b11a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Bug Fixes * Fix BLFReader error for incomplete or truncated stream (#1662) * PCAN: remove Windows registry check to fix 32bit compatibility (#1672) * Vector: Skip the `can_op_mode check` if the device reports `can_op_mode=0` (#1678) +* Vector: using the config from `detect_available_configs` might raise XL_ERR_INVALID_CHANNEL_MASK error (#1681) Features -------- diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 024f6a4c9..576fa26cf 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -202,12 +202,20 @@ def __init__( self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20 for channel in self.channels: - channel_index = self._find_global_channel_idx( - channel=channel, - serial=serial, - app_name=app_name, - channel_configs=channel_configs, - ) + if (_channel_index := kwargs.get("channel_index", None)) is not None: + # VectorBus._detect_available_configs() might return multiple + # devices with the same serial number, e.g. if a VN8900 is connected via both USB and Ethernet + # at the same time. If the VectorBus is instantiated with a config, that was returned from + # VectorBus._detect_available_configs(), then use the contained global channel_index + # to avoid any ambiguities. + channel_index = cast(int, _channel_index) + else: + channel_index = self._find_global_channel_idx( + channel=channel, + serial=serial, + app_name=app_name, + channel_configs=channel_configs, + ) LOG.debug("Channel index %d found", channel) channel_mask = 1 << channel_index @@ -950,6 +958,7 @@ def _detect_available_configs() -> List[AutoDetectedConfig]: "interface": "vector", "channel": channel_config.hw_channel, "serial": channel_config.serial_number, + "channel_index": channel_config.channel_index, # data for use in VectorBus.set_application_config(): "hw_type": channel_config.hw_type, "hw_index": channel_config.hw_index, diff --git a/test/test_vector.py b/test/test_vector.py index 3db43fbbb..3e53bdaff 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -118,6 +118,22 @@ def test_bus_creation() -> None: bus.shutdown() +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_bus_creation_channel_index() -> None: + channel_index = 3 + bus = can.Bus( + channel=0, + serial=_find_virtual_can_serial(), + channel_index=channel_index, + interface="vector", + ) + assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 + assert bus.channel_masks[0] == 1 << channel_index + + bus.shutdown() + + def test_bus_creation_bitrate_mocked(mock_xldriver) -> None: bus = can.Bus(channel=0, interface="vector", bitrate=200_000, _testing=True) assert isinstance(bus, canlib.VectorBus) @@ -833,6 +849,31 @@ def test_get_channel_configs() -> None: canlib._get_xl_driver_config = _original_func +@pytest.mark.skipif( + sys.byteorder != "little", reason="Test relies on little endian data." +) +def test_detect_available_configs() -> None: + _original_func = canlib._get_xl_driver_config + canlib._get_xl_driver_config = _get_predefined_xl_driver_config + + available_configs = canlib.VectorBus._detect_available_configs() + + assert len(available_configs) == 5 + + assert available_configs[0]["interface"] == "vector" + assert available_configs[0]["channel"] == 2 + assert available_configs[0]["serial"] == 1001 + assert available_configs[0]["channel_index"] == 2 + assert available_configs[0]["hw_type"] == xldefine.XL_HardwareType.XL_HWTYPE_VN8900 + assert available_configs[0]["hw_index"] == 0 + assert available_configs[0]["supports_fd"] is True + assert isinstance( + available_configs[0]["vector_channel_config"], VectorChannelConfig + ) + + canlib._get_xl_driver_config = _original_func + + @pytest.mark.skipif(not IS_WINDOWS, reason="Windows specific test") def test_winapi_availability() -> None: assert canlib.WaitForSingleObject is not None From 5c1c46fdbd5935c5a7a8b34aa6a8df3af201616b Mon Sep 17 00:00:00 2001 From: Faisal Shah <37458679+faisal-shah@users.noreply.github.com> Date: Mon, 30 Oct 2023 04:59:00 -0500 Subject: [PATCH 1083/1235] Configure socketcand TCP socket to reduce latency (#1683) * Configure TCP socket to reduce latency TCP_NODELAY disables Nagles algorithm. This improves latency (reduces), but worsens overall throughput. For the purpose of bridging a CAN bus over a network connection to socketcand (and given the relatively low overall bandwidth of CAN), optimizing for latency is more important. TCP_QUICKACK disables the default delayed ACK timer. This is ~40ms in linux (not sure about windows). The thing is, TCP_QUICKACK is reset when you send or receive on the socket, so it needs reenabling each time. Also, TCP_QUICKACK doesn't seem to be available in windows. Here's a comment by John Nagle himself that some may find useful: https://news.ycombinator.com/item?id=10608356 "That still irks me. The real problem is not tinygram prevention. It's ACK delays, and that stupid fixed timer. They both went into TCP around the same time, but independently. I did tinygram prevention (the Nagle algorithm) and Berkeley did delayed ACKs, both in the early 1980s. The combination of the two is awful. Unfortunately by the time I found about delayed ACKs, I had changed jobs, was out of networking, and doing a product for Autodesk on non-networked PCs. Delayed ACKs are a win only in certain circumstances - mostly character echo for Telnet. (When Berkeley installed delayed ACKs, they were doing a lot of Telnet from terminal concentrators in student terminal rooms to host VAX machines doing the work. For that particular situation, it made sense.) The delayed ACK timer is scaled to expected human response time. A delayed ACK is a bet that the other end will reply to what you just sent almost immediately. Except for some RPC protocols, this is unlikely. So the ACK delay mechanism loses the bet, over and over, delaying the ACK, waiting for a packet on which the ACK can be piggybacked, not getting it, and then sending the ACK, delayed. There's nothing in TCP to automatically turn this off. However, Linux (and I think Windows) now have a TCP_QUICKACK socket option. Turn that on unless you have a very unusual application. "Turning on TCP_NODELAY has similar effects, but can make throughput worse for small writes. If you write a loop which sends just a few bytes (worst case, one byte) to a socket with "write()", and the Nagle algorithm is disabled with TCP_NODELAY, each write becomes one IP packet. This increases traffic by a factor of 40, with IP and TCP headers for each payload. Tinygram prevention won't let you send a second packet if you have one in flight, unless you have enough data to fill the maximum sized packet. It accumulates bytes for one round trip time, then sends everything in the queue. That's almost always what you want. If you have TCP_NODELAY set, you need to be much more aware of buffering and flushing issues. "None of this matters for bulk one-way transfers, which is most HTTP today. (I've never looked at the impact of this on the SSL handshake, where it might matter.) "Short version: set TCP_QUICKACK. If you find a case where that makes things worse, let me know. John Nagle" * Make tune TCP for low latency optional * Move os check into __init__ * Add docstrings * Add Bus class documentation to docs/interfaces * Update changelog --- CHANGELOG.md | 1 + can/interfaces/socketcand/socketcand.py | 47 ++++++++++++++++++++++++- doc/interfaces/socketcand.rst | 8 +++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0220b11a6..35b3291c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Features * PCAN: Optimize send performance (#1640) * PCAN: Support version string of older PCAN basic API (#1644) * Kvaser: add parameter exclusive and `override_exclusive` (#1660) +* socketcand: Add parameter `tcp_tune` to reduce latency (#1683) ### Miscellaneous * Distinguish Text/Binary-IO for Reader/Writer classes. (#1585) diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 26ae63ca6..8b55eef3a 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -8,6 +8,7 @@ http://www.domologic.de """ import logging +import os import select import socket import time @@ -75,10 +76,42 @@ def connect_to_server(s, host, port): class SocketCanDaemonBus(can.BusABC): - def __init__(self, channel, host, port, can_filters=None, **kwargs): + def __init__(self, channel, host, port, tcp_tune=False, can_filters=None, **kwargs): + """Connects to a CAN bus served by socketcand. + + It will attempt to connect to the server for up to 10s, after which a + TimeoutError exception will be thrown. + + If the handshake with the socketcand server fails, a CanError exception + is thrown. + + :param channel: + The can interface name served by socketcand. + An example channel would be 'vcan0' or 'can0'. + :param host: + The host address of the socketcand server. + :param port: + The port of the socketcand server. + :param tcp_tune: + This tunes the TCP socket for low latency (TCP_NODELAY, and + TCP_QUICKACK). + This option is not available under windows. + :param can_filters: + See :meth:`can.BusABC.set_filters`. + """ self.__host = host self.__port = port + + self.__tcp_tune = tcp_tune self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + if self.__tcp_tune: + if os.name == "nt": + self.__tcp_tune = False + log.warning("'tcp_tune' not available in Windows. Setting to False") + else: + self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + self.__message_buffer = deque() self.__receive_buffer = "" # i know string is not the most efficient here self.channel = channel @@ -120,6 +153,8 @@ def _recv_internal(self, timeout): ascii_msg = self.__socket.recv(1024).decode( "ascii" ) # may contain multiple messages + if self.__tcp_tune: + self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) self.__receive_buffer += ascii_msg log.debug(f"Received Ascii Message: {ascii_msg}") buffer_view = self.__receive_buffer @@ -173,16 +208,26 @@ def _recv_internal(self, timeout): def _tcp_send(self, msg: str): log.debug(f"Sending TCP Message: '{msg}'") self.__socket.sendall(msg.encode("ascii")) + if self.__tcp_tune: + self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) def _expect_msg(self, msg): ascii_msg = self.__socket.recv(256).decode("ascii") + if self.__tcp_tune: + self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) if not ascii_msg == msg: raise can.CanError(f"{msg} message expected!") def send(self, msg, timeout=None): + """Transmit a message to the CAN bus. + + :param msg: A message object. + :param timeout: Ignored + """ ascii_msg = convert_can_message_to_ascii_message(msg) self._tcp_send(ascii_msg) def shutdown(self): + """Stops all active periodic tasks and closes the socket.""" super().shutdown() self.__socket.close() diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst index 0214f094a..a8a314521 100644 --- a/doc/interfaces/socketcand.rst +++ b/doc/interfaces/socketcand.rst @@ -37,6 +37,14 @@ The output may look like this:: Timestamp: 1637791111.609763 ID: 0000031d X Rx DLC: 8 16 27 d8 3d fe d8 31 24 Timestamp: 1637791111.634630 ID: 00000587 X Rx DLC: 8 4e 06 85 23 6f 81 2b 65 +Bus +--- + +.. autoclass:: can.interfaces.socketcand.SocketCanDaemonBus + :show-inheritance: + :member-order: bysource + :members: + Socketcand Quickstart --------------------- From bc3f95544e7a7dbec0da855a0873c6ea502147fc Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 17 Nov 2023 10:31:00 +1300 Subject: [PATCH 1084/1235] Release version 4.3.0 (#1688) --- CHANGELOG.md | 4 ++-- can/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b3291c8..9b3c2cbc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -Version 4.3.0rc0 -=========== +Version 4.3.0 +============= Breaking Changes ---------------- diff --git a/can/__init__.py b/can/__init__.py index 48dda308d..6f430b11e 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.3.0rc0" +__version__ = "4.3.0" __all__ = [ "ASCReader", "ASCWriter", From 2e58a21578cf4451538e4e1f45cddbd85d12b488 Mon Sep 17 00:00:00 2001 From: gRant Date: Tue, 28 Nov 2023 02:35:00 -0500 Subject: [PATCH 1085/1235] Correct install instructions for neovi(#1697) --- doc/interfaces/neovi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/interfaces/neovi.rst b/doc/interfaces/neovi.rst index 0baf08055..bc711d86b 100644 --- a/doc/interfaces/neovi.rst +++ b/doc/interfaces/neovi.rst @@ -23,7 +23,7 @@ package. - Install ``python-can`` with the ``neovi`` extras: .. code-block:: bash - pip install python-ics[neovi] + pip install python-can[neovi] Configuration From cc72abb370b0aa47ddb82dfe83e86d67673615aa Mon Sep 17 00:00:00 2001 From: Faisal Shah <37458679+faisal-shah@users.noreply.github.com> Date: Tue, 5 Dec 2023 02:40:07 -0600 Subject: [PATCH 1086/1235] Fix socketcand erroneously discarding frames (#1700) The __receive_buffer is always truncated by [chars_processed_successfully + 1:]. When a partial socketcand frame is received, chars_processed_successfully is 0, and this results in 1 character being discarded. This will be the '<' character, and thus when the rest of the frame is received, it will be treated as a bad frame, and discarded. --- can/interfaces/socketcand/socketcand.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 8b55eef3a..5a1f2570d 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -191,9 +191,7 @@ def _recv_internal(self, timeout): self.__message_buffer.append(parsed_can_message) buffer_view = buffer_view[end + 1 :] - self.__receive_buffer = self.__receive_buffer[ - chars_processed_successfully + 1 : - ] + self.__receive_buffer = self.__receive_buffer[chars_processed_successfully:] can_message = ( None if len(self.__message_buffer) == 0 From 033be12ab150c1c1db34fe5a2f30ac27976e65e0 Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Fri, 8 Dec 2023 09:48:06 +0100 Subject: [PATCH 1087/1235] Fix initialization order in EtasBus (#1704) super.__init__ calls set_filters, which is only permissible after the corresponding structures have been created in the child constructor. Hence, super.__init__ must be called after the child has finished initialization. Fixes #1693 --- can/interfaces/etas/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 62e060f48..b40f10b77 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -18,8 +18,6 @@ def __init__( data_bitrate: int = 2000000, **kwargs: Dict[str, any], ): - super().__init__(channel=channel, **kwargs) - self.receive_own_messages = receive_own_messages self._can_protocol = can.CanProtocol.CAN_FD if fd else can.CanProtocol.CAN_20 @@ -119,6 +117,9 @@ def __init__( self.channel_info = channel + # Super call must be after child init since super calls set_filters + super().__init__(channel=channel, **kwargs) + def _recv_internal( self, timeout: Optional[float] ) -> Tuple[Optional[can.Message], bool]: From 3b8ab2e48062183b47f260b7d54d2b891450f3d7 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 10 Dec 2023 14:20:09 +0100 Subject: [PATCH 1088/1235] Fix vector test (#1705) --- test/test_vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_vector.py b/test/test_vector.py index 3e53bdaff..16a79c6d1 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -120,7 +120,7 @@ def test_bus_creation() -> None: @pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") def test_bus_creation_channel_index() -> None: - channel_index = 3 + channel_index = 1 bus = can.Bus( channel=0, serial=_find_virtual_can_serial(), From f0634d2445f74f8c5c3b90236975f73fe3d8ba2a Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:34:09 +0100 Subject: [PATCH 1089/1235] Update CHANGELOG.md for 4.3.1 (#1706) --- CHANGELOG.md | 13 +++++++++++++ can/__init__.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b3c2cbc5..8798c19c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +Version 4.3.1 +============= + +Bug Fixes +--------- +* Fix socketcand erroneously discarding frames (#1700) +* Fix initialization order in EtasBus (#1693, #1704) + +Documentation +------------- +* Fix install instructions for neovi (#1694, #1697) + + Version 4.3.0 ============= diff --git a/can/__init__.py b/can/__init__.py index 6f430b11e..9b6a26f58 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.3.0" +__version__ = "4.3.1" __all__ = [ "ASCReader", "ASCWriter", From 35eef6e6349ad2b704b53d6c9e179ea2e908b24e Mon Sep 17 00:00:00 2001 From: Alon <110119940+AlonMoradov@users.noreply.github.com> Date: Fri, 29 Dec 2023 18:55:35 +0200 Subject: [PATCH 1090/1235] Fix typo in error message (#1715) --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index 63a4eea41..c26733087 100644 --- a/can/message.py +++ b/can/message.py @@ -245,7 +245,7 @@ def _check(self) -> None: if self.is_remote_frame: if self.is_error_frame: raise ValueError( - "a message cannot be a remote and an error frame at the sane time" + "a message cannot be a remote and an error frame at the same time" ) if self.is_fd: raise ValueError("CAN FD does not support remote frames") From 3f6e95148e87af3ce1cba9c79b58a3e184374c10 Mon Sep 17 00:00:00 2001 From: XXIN0 <41498132+XXIN0@users.noreply.github.com> Date: Sun, 31 Dec 2023 04:57:09 +0800 Subject: [PATCH 1091/1235] Improve the "ASCReader" read performance. (#1717) * Update asc.py improve the "ASCReader" performance. * Update asc.py Fix: the test error "error: Item "None" of "Optional[Match[str]]" has no attribute "group" [union-attr]" * Update asc.py style: format code with "Black Code Formatter" * improvement: make regular expression compile result as module constants. * style:format import --------- Co-authored-by: XXIN --- can/io/asc.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index f039cda32..07243cd5b 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -9,7 +9,7 @@ import re import time from datetime import datetime -from typing import Any, Dict, Generator, List, Optional, TextIO, Union +from typing import Any, Dict, Final, Generator, List, Optional, TextIO, Union from ..message import Message from ..typechecking import StringPathLike @@ -20,6 +20,14 @@ CAN_ID_MASK = 0x1FFFFFFF BASE_HEX = 16 BASE_DEC = 10 +ASC_TRIGGER_REGEX: Final = re.compile( + r"begin\s+triggerblock\s+\w+\s+(?P.+)", re.IGNORECASE +) +ASC_MESSAGE_REGEX: Final = re.compile( + r"\d+\.\d+\s+(\d+\s+(\w+\s+(Tx|Rx)|ErrorFrame)|CANFD)", + re.ASCII | re.IGNORECASE, +) + logger = logging.getLogger("can.io.asc") @@ -258,12 +266,7 @@ def __iter__(self) -> Generator[Message, None, None]: for _line in self.file: line = _line.strip() - trigger_match = re.match( - r"begin\s+triggerblock\s+\w+\s+(?P.+)", - line, - re.IGNORECASE, - ) - if trigger_match: + if trigger_match := ASC_TRIGGER_REGEX.match(line): datetime_str = trigger_match.group("datetime_string") self.start_time = ( 0.0 @@ -272,11 +275,7 @@ def __iter__(self) -> Generator[Message, None, None]: ) continue - if not re.match( - r"\d+\.\d+\s+(\d+\s+(\w+\s+(Tx|Rx)|ErrorFrame)|CANFD)", - line, - re.ASCII | re.IGNORECASE, - ): + if not ASC_MESSAGE_REGEX.match(line): # line might be a comment, chip status, # J1939 message or some other unsupported event continue From c61981399f5cb479ca52019ca0cd4f076d83da5b Mon Sep 17 00:00:00 2001 From: Teejay Date: Wed, 3 Jan 2024 05:03:44 -0800 Subject: [PATCH 1092/1235] Fix `send_periodic` duration (#1713) * Add test case * Fix bug * Improve unittest * MR feedback --- can/broadcastmanager.py | 6 ++++-- test/back2back_test.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 0ac9b6adc..a610b7a8a 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -297,6 +297,9 @@ def _run(self) -> None: win32event.WaitForSingleObject(self.event.handle, 0) while not self.stopped: + if self.end_time is not None and time.perf_counter() >= self.end_time: + break + # Prevent calling bus.send from multiple threads with self.send_lock: try: @@ -318,8 +321,7 @@ def _run(self) -> None: if not USE_WINDOWS_EVENTS: msg_due_time_ns += self.period_ns - if self.end_time is not None and time.perf_counter() >= self.end_time: - break + msg_index = (msg_index + 1) % len(self.messages) if USE_WINDOWS_EVENTS: diff --git a/test/back2back_test.py b/test/back2back_test.py index cd5aca6aa..d9896c19f 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -273,6 +273,23 @@ def test_sub_second_timestamp_resolution(self): self.bus2.recv(0) self.bus2.recv(0) + def test_send_periodic_duration(self): + """ + Verify that send_periodic only transmits for the specified duration. + + Regression test for #1713. + """ + for params in [(0.01, 0.003), (0.1, 0.011), (1, 0.4)]: + duration, period = params + messages = [] + + self.bus2.send_periodic(can.Message(), period, duration) + while (msg := self.bus1.recv(period * 1.25)) is not None: + messages.append(msg) + + delta_t = round(messages[-1].timestamp - messages[0].timestamp, 2) + assert delta_t <= duration + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class BasicTestSocketCan(Back2BackTestCase): From bcd2ee5a9479a609bffd4d0674681418c99a353d Mon Sep 17 00:00:00 2001 From: Faisal Shah <37458679+faisal-shah@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:56:00 -0600 Subject: [PATCH 1093/1235] Add feature to detect socketcand beacon (#1687) * Add feature to detect socketcand beacon * Fix imports and formatting * Return empty list if no beacon detected * Use %-format Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * black format * Use context manager for detect_beacon() udp sock * Add timeout as parameter, and set to 3.1s * Document detect_beacon * detect_beacon return empty if timed out * export detect_beacon * Update documentation for auto config * More documentation fixes * Trigger tests * Set default timeout Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Use default timeout Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Fix docstring indentation * Fix grammar, and make time units consistent in comments --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/socketcand/__init__.py | 3 +- can/interfaces/socketcand/socketcand.py | 114 ++++++++++++++++++++++++ doc/interfaces/socketcand.rst | 35 +++++++- 3 files changed, 148 insertions(+), 4 deletions(-) diff --git a/can/interfaces/socketcand/__init__.py b/can/interfaces/socketcand/__init__.py index ce18441bc..64950f7f4 100644 --- a/can/interfaces/socketcand/__init__.py +++ b/can/interfaces/socketcand/__init__.py @@ -8,7 +8,8 @@ __all__ = [ "SocketCanDaemonBus", + "detect_beacon", "socketcand", ] -from .socketcand import SocketCanDaemonBus +from .socketcand import SocketCanDaemonBus, detect_beacon diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 5a1f2570d..045882388 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -13,12 +13,115 @@ import socket import time import traceback +import urllib.parse as urlparselib +import xml.etree.ElementTree as ET from collections import deque +from typing import List import can log = logging.getLogger(__name__) +DEFAULT_SOCKETCAND_DISCOVERY_ADDRESS = "" +DEFAULT_SOCKETCAND_DISCOVERY_PORT = 42000 + + +def detect_beacon(timeout_ms: int = 3100) -> List[can.typechecking.AutoDetectedConfig]: + """ + Detects socketcand servers + + This is what :meth:`can.detect_available_configs` ends up calling to search + for available socketcand servers with a default timeout of 3100ms + (socketcand sends a beacon packet every 3000ms). + + Using this method directly allows for adjusting the timeout. Extending + the timeout beyond the default time period could be useful if UDP + packet loss is a concern. + + :param timeout_ms: + Timeout in milliseconds to wait for socketcand beacon packets + + :return: + See :meth:`~can.detect_available_configs` + """ + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.bind( + (DEFAULT_SOCKETCAND_DISCOVERY_ADDRESS, DEFAULT_SOCKETCAND_DISCOVERY_PORT) + ) + log.info( + "Listening on for socketcand UDP advertisement on %s:%s", + DEFAULT_SOCKETCAND_DISCOVERY_ADDRESS, + DEFAULT_SOCKETCAND_DISCOVERY_PORT, + ) + + now = time.time() * 1000 + end_time = now + timeout_ms + while (time.time() * 1000) < end_time: + try: + # get all sockets that are ready (can be a list with a single value + # being self.socket or an empty list if self.socket is not ready) + ready_receive_sockets, _, _ = select.select([sock], [], [], 1) + + if not ready_receive_sockets: + log.debug("No advertisement received") + continue + + msg = sock.recv(1024).decode("utf-8") + root = ET.fromstring(msg) + if root.tag != "CANBeacon": + log.debug("Unexpected message received over UDP") + continue + + det_devs = [] + det_host = None + det_port = None + for child in root: + if child.tag == "Bus": + bus_name = child.attrib["name"] + det_devs.append(bus_name) + elif child.tag == "URL": + url = urlparselib.urlparse(child.text) + det_host = url.hostname + det_port = url.port + + if not det_devs: + log.debug( + "Got advertisement, but no SocketCAN devices advertised by socketcand" + ) + continue + + if (det_host is None) or (det_port is None): + det_host = None + det_port = None + log.debug( + "Got advertisement, but no SocketCAN URL advertised by socketcand" + ) + continue + + log.info(f"Found SocketCAN devices: {det_devs}") + return [ + { + "interface": "socketcand", + "host": det_host, + "port": det_port, + "channel": channel, + } + for channel in det_devs + ] + + except ET.ParseError: + log.debug("Unexpected message received over UDP") + continue + + except Exception as exc: + # something bad happened (e.g. the interface went down) + log.error(f"Failed to detect beacon: {exc} {traceback.format_exc()}") + raise OSError( + f"Failed to detect beacon: {exc} {traceback.format_exc()}" + ) + + return [] + def convert_ascii_message_to_can_message(ascii_msg: str) -> can.Message: if not ascii_msg.startswith("< frame ") or not ascii_msg.endswith(" >"): @@ -79,6 +182,9 @@ class SocketCanDaemonBus(can.BusABC): def __init__(self, channel, host, port, tcp_tune=False, can_filters=None, **kwargs): """Connects to a CAN bus served by socketcand. + It implements :meth:`can.BusABC._detect_available_configs` to search for + available interfaces. + It will attempt to connect to the server for up to 10s, after which a TimeoutError exception will be thrown. @@ -229,3 +335,11 @@ def shutdown(self): """Stops all active periodic tasks and closes the socket.""" super().shutdown() self.__socket.close() + + @staticmethod + def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: + try: + return detect_beacon() + except Exception as e: + log.warning(f"Could not detect socketcand beacon: {e}") + return [] diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst index a8a314521..f861c81b9 100644 --- a/doc/interfaces/socketcand.rst +++ b/doc/interfaces/socketcand.rst @@ -2,8 +2,8 @@ socketcand Interface ==================== -`Socketcand `__ is part of the -`Linux-CAN `__ project, providing a +`Socketcand `__ is part of the +`Linux-CAN `__ project, providing a Network-to-CAN bridge as a Linux damon. It implements a specific `TCP/IP based communication protocol `__ to transfer CAN frames and control commands. @@ -11,7 +11,7 @@ to transfer CAN frames and control commands. The main advantage compared to UDP-based protocols (e.g. virtual interface) is, that TCP guarantees delivery and that the message order is kept. -Here is a small example dumping all can messages received by a socketcand +Here is a small example dumping all can messages received by a socketcand daemon running on a remote Raspberry Pi: .. code-block:: python @@ -37,6 +37,33 @@ The output may look like this:: Timestamp: 1637791111.609763 ID: 0000031d X Rx DLC: 8 16 27 d8 3d fe d8 31 24 Timestamp: 1637791111.634630 ID: 00000587 X Rx DLC: 8 4e 06 85 23 6f 81 2b 65 + +This interface also supports :meth:`~can.detect_available_configs`. + +.. code-block:: python + + import can + import can.interfaces.socketcand + + cfg = can.interfaces.socketcand._detect_available_configs() + if cfg: + bus = can.Bus(**cfg[0]) + +The socketcand daemon broadcasts UDP beacons every 3 seconds. The default +detection method waits for slightly more than 3 seconds to receive the beacon +packet. If you want to increase the timeout, you can use +:meth:`can.interfaces.socketcand.detect_beacon` directly. Below is an example +which detects the beacon and uses the configuration to create a socketcand bus. + +.. code-block:: python + + import can + import can.interfaces.socketcand + + cfg = can.interfaces.socketcand.detect_beacon(6000) + if cfg: + bus = can.Bus(**cfg[0]) + Bus --- @@ -45,6 +72,8 @@ Bus :member-order: bysource :members: +.. autofunction:: can.interfaces.socketcand.detect_beacon + Socketcand Quickstart --------------------- From 0178355adbaab454f2451edc2bc23171a69f5b79 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 13 Jan 2024 04:30:32 +0100 Subject: [PATCH 1094/1235] Extend ruff linter configuration (#1724) * update ruff configuration * skip test_send_periodic_duration on PyPy * remove format-code.yml --- .github/workflows/format-code.yml | 29 ------------ can/bus.py | 7 ++- can/ctypesutil.py | 8 ++-- can/interface.py | 2 +- can/interfaces/etas/__init__.py | 8 ++-- can/interfaces/gs_usb.py | 4 +- can/interfaces/ics_neovi/neovi_bus.py | 10 ++--- can/interfaces/ixxat/canlib_vcinpl.py | 15 +++---- can/interfaces/ixxat/canlib_vcinpl2.py | 21 ++++----- can/interfaces/kvaser/canlib.py | 2 +- can/interfaces/nixnet.py | 6 ++- can/interfaces/pcan/basic.py | 6 +-- can/interfaces/pcan/pcan.py | 6 ++- can/interfaces/robotell.py | 2 +- can/interfaces/seeedstudio/seeedstudio.py | 3 +- can/interfaces/serial/serial_can.py | 8 ++-- can/interfaces/slcan.py | 2 +- can/interfaces/socketcan/socketcan.py | 45 ++++--------------- can/interfaces/socketcand/socketcand.py | 8 ++-- can/interfaces/systec/structures.py | 8 ++-- can/interfaces/udp_multicast/bus.py | 6 ++- .../usb2can/usb2canabstractionlayer.py | 2 +- can/interfaces/vector/canlib.py | 11 +++-- can/io/logger.py | 29 ++++++++---- can/io/mf4.py | 2 +- can/io/player.py | 17 +++++-- can/io/printer.py | 2 +- can/io/trc.py | 30 ++++++------- can/listener.py | 20 ++++----- can/logger.py | 2 +- can/util.py | 15 ++----- can/viewer.py | 8 ++-- pyproject.toml | 35 ++++++++++----- test/back2back_test.py | 1 + test/network_test.py | 6 +-- 35 files changed, 188 insertions(+), 198 deletions(-) delete mode 100644 .github/workflows/format-code.yml diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml deleted file mode 100644 index 30f95c103..000000000 --- a/.github/workflows/format-code.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Format Code - -on: - push: - paths: - - '**.py' - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e .[lint] - - name: Code Format Check with Black - run: | - black --verbose . - - name: Commit Formated Code - uses: EndBug/add-and-commit@v9 - with: - message: "Format code with black" - # Ref https://git-scm.com/docs/git-add#_examples - add: './*.py' diff --git a/can/bus.py b/can/bus.py index 555389b0f..c3a906757 100644 --- a/can/bus.py +++ b/can/bus.py @@ -281,7 +281,7 @@ def wrapped_stop_method(remove_task: bool = True) -> None: pass # allow the task to be already removed original_stop_method() - task.stop = wrapped_stop_method # type: ignore + task.stop = wrapped_stop_method # type: ignore[method-assign] if store_task: self._periodic_tasks.append(task) @@ -401,7 +401,8 @@ def set_filters( messages based only on the arbitration ID and mask. """ self._filters = filters or None - self._apply_filters(self._filters) + with contextlib.suppress(NotImplementedError): + self._apply_filters(self._filters) def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: """ @@ -411,6 +412,7 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None :param filters: See :meth:`~can.BusABC.set_filters` for details. """ + raise NotImplementedError def _matches_filters(self, msg: Message) -> bool: """Checks whether the given message matches at least one of the @@ -450,6 +452,7 @@ def _matches_filters(self, msg: Message) -> bool: def flush_tx_buffer(self) -> None: """Discard every message that may be queued in the output buffer(s).""" + raise NotImplementedError def shutdown(self) -> None: """ diff --git a/can/ctypesutil.py b/can/ctypesutil.py index e624db6d2..fa59255d1 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -1,4 +1,3 @@ -# type: ignore """ This module contains common `ctypes` utils. """ @@ -11,11 +10,10 @@ __all__ = ["CLibrary", "HANDLE", "PHANDLE", "HRESULT"] - -try: +if sys.platform == "win32": _LibBase = ctypes.WinDLL _FUNCTION_TYPE = ctypes.WINFUNCTYPE -except AttributeError: +else: _LibBase = ctypes.CDLL _FUNCTION_TYPE = ctypes.CFUNCTYPE @@ -60,7 +58,7 @@ def map_symbol( f'Could not map function "{func_name}" from library {self._name}' ) from None - func._name = func_name # pylint: disable=protected-access + func._name = func_name # type: ignore[attr-defined] # pylint: disable=protected-access log.debug( 'Wrapped function "%s", result type: %s, error_check %s', func_name, diff --git a/can/interface.py b/can/interface.py index 9c828a608..2b7e27f8d 100644 --- a/can/interface.py +++ b/can/interface.py @@ -61,7 +61,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]: bustype="interface", context="config_context", ) -def Bus( +def Bus( # noqa: N802 channel: Optional[Channel] = None, interface: Optional[str] = None, config_context: Optional[str] = None, diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index b40f10b77..2bfcbf427 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -229,10 +229,10 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None self._oci_filters = (ctypes.POINTER(OCI_CANRxFilterEx) * len(filters))() - for i, filter in enumerate(filters): + for i, filter_ in enumerate(filters): f = OCI_CANRxFilterEx() - f.frameIDValue = filter["can_id"] - f.frameIDMask = filter["can_mask"] + f.frameIDValue = filter_["can_id"] + f.frameIDMask = filter_["can_mask"] f.tag = 0 f.flagsValue = 0 if self.receive_own_messages: @@ -241,7 +241,7 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None else: # enable the SR bit in the mask. since the bit is 0 in flagsValue -> do not self-receive f.flagsMask = OCI_CAN_MSG_FLAG_SELFRECEPTION - if filter.get("extended"): + if filter_.get("extended"): f.flagsValue |= OCI_CAN_MSG_FLAG_EXTENDED f.flagsMask |= OCI_CAN_MSG_FLAG_EXTENDED self._oci_filters[i].contents = f diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 38f9fe41a..21e199a30 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -95,8 +95,8 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): try: self.gs_usb.send(frame) - except usb.core.USBError: - raise CanOperationError("The message could not be sent") + except usb.core.USBError as exc: + raise CanOperationError("The message could not be sent") from exc def _recv_internal( self, timeout: Optional[float] diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 9270bfc90..66e109c97 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -109,8 +109,10 @@ def __reduce__(self): def error_number(self) -> int: """Deprecated. Renamed to :attr:`can.CanError.error_code`.""" warn( - "ICSApiError::error_number has been renamed to error_code defined by CanError", + "ICSApiError::error_number has been replaced by ICSApiError.error_code in python-can 4.0" + "and will be remove in version 5.0.", DeprecationWarning, + stacklevel=2, ) return self.error_code @@ -223,10 +225,8 @@ def __init__(self, channel, can_filters=None, **kwargs): self._use_system_timestamp = bool(kwargs.get("use_system_timestamp", False)) self._receive_own_messages = kwargs.get("receive_own_messages", True) - self.channel_info = "{} {} CH:{}".format( - self.dev.Name, - self.get_serial_number(self.dev), - self.channels, + self.channel_info = ( + f"{self.dev.Name} {self.get_serial_number(self.dev)} CH:{self.channels}" ) logger.info(f"Using device: {self.channel_info}") diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 334adee11..709780ceb 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -79,8 +79,8 @@ def __vciFormatErrorExtended( Formatted string """ # TODO: make sure we don't generate another exception - return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, vret), args + return ( + f"{__vciFormatError(library_instance, function, vret)} - arguments were {args}" ) @@ -512,13 +512,11 @@ def __init__( if unique_hardware_id is None: raise VCIDeviceNotFoundError( "No IXXAT device(s) connected or device(s) in use by other process(es)." - ) + ) from None else: raise VCIDeviceNotFoundError( - "Unique HW ID {} not connected or not available.".format( - unique_hardware_id - ) - ) + f"Unique HW ID {unique_hardware_id} not connected or not available." + ) from None else: if (unique_hardware_id is None) or ( self._device_info.UniqueHardwareId.AsChar @@ -815,7 +813,8 @@ def _send_periodic_internal( # fallback to thread based cyclic task warnings.warn( f"{self.__class__.__name__} falls back to a thread-based cyclic task, " - "when the `modifier_callback` argument is given." + "when the `modifier_callback` argument is given.", + stacklevel=3, ) return BusABC._send_periodic_internal( self, diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index aaefa1bf9..18b3a1e57 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -77,8 +77,8 @@ def __vciFormatErrorExtended( Formatted string """ # TODO: make sure we don't generate another exception - return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, vret), args + return ( + f"{__vciFormatError(library_instance, function, vret)} - arguments were {args}" ) @@ -553,13 +553,11 @@ def __init__( if unique_hardware_id is None: raise VCIDeviceNotFoundError( "No IXXAT device(s) connected or device(s) in use by other process(es)." - ) + ) from None else: raise VCIDeviceNotFoundError( - "Unique HW ID {} not connected or not available.".format( - unique_hardware_id - ) - ) + f"Unique HW ID {unique_hardware_id} not connected or not available." + ) from None else: if (unique_hardware_id is None) or ( self._device_info.UniqueHardwareId.AsChar @@ -579,7 +577,9 @@ def __init__( ctypes.byref(self._device_handle), ) except Exception as exception: - raise CanInitializationError(f"Could not open device: {exception}") + raise CanInitializationError( + f"Could not open device: {exception}" + ) from exception log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) @@ -600,7 +600,7 @@ def __init__( except Exception as exception: raise CanInitializationError( f"Could not open and initialize channel: {exception}" - ) + ) from exception # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize( @@ -959,7 +959,8 @@ def _send_periodic_internal( # fallback to thread based cyclic task warnings.warn( f"{self.__class__.__name__} falls back to a thread-based cyclic task, " - "when the `modifier_callback` argument is given." + "when the `modifier_callback` argument is given.", + stacklevel=3, ) return BusABC._send_periodic_internal( self, diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 0983e28dc..4c621ecf4 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -458,7 +458,7 @@ def __init__( try: channel = int(channel) except ValueError: - raise ValueError("channel must be an integer") + raise ValueError("channel must be an integer") from None self.channel = channel self.single_handle = single_handle diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index ba665442e..2b3cbd69a 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -192,10 +192,12 @@ def __init__( @property def fd(self) -> bool: + class_name = self.__class__.__name__ warnings.warn( - "The NiXNETcanBus.fd property is deprecated and superseded by " - "BusABC.protocol. It is scheduled for removal in version 5.0.", + f"The {class_name}.fd property is deprecated and superseded by " + f"{class_name}.protocol. It is scheduled for removal in python-can version 5.0.", DeprecationWarning, + stacklevel=2, ) return self._can_protocol is CanProtocol.CAN_FD diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index e003e83f9..b704ca9bd 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -694,9 +694,9 @@ def Initialize( self, Channel, Btr0Btr1, - HwType=TPCANType(0), - IOPort=c_uint(0), - Interrupt=c_ushort(0), + HwType=TPCANType(0), # noqa: B008 + IOPort=c_uint(0), # noqa: B008 + Interrupt=c_ushort(0), # noqa: B008 ): """Initializes a PCAN Channel diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 884c1680b..0fdca51bb 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -657,10 +657,12 @@ def shutdown(self): @property def fd(self) -> bool: + class_name = self.__class__.__name__ warnings.warn( - "The PcanBus.fd property is deprecated and superseded by BusABC.protocol. " - "It is scheduled for removal in version 5.0.", + f"The {class_name}.fd property is deprecated and superseded by {class_name}.protocol. " + "It is scheduled for removal in python-can version 5.0.", DeprecationWarning, + stacklevel=2, ) return self._can_protocol is CanProtocol.CAN_FD diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index bfe8f5774..16668bdda 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -376,7 +376,7 @@ def fileno(self): except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" - ) + ) from None except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 8e0dca8c7..a0817e932 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -63,7 +63,6 @@ def __init__( frame_type="STD", operation_mode="normal", bitrate=500000, - *args, **kwargs, ): """ @@ -115,7 +114,7 @@ def __init__( "could not create the serial device" ) from error - super().__init__(channel=channel, *args, **kwargs) + super().__init__(channel=channel, **kwargs) self.init_frame() def shutdown(self): diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 9de2da99c..476cbd624 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -131,13 +131,15 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: try: timestamp = struct.pack(" int: except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" - ) + ) from None except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 7ff12ce44..7851a0322 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -268,7 +268,7 @@ def fileno(self) -> int: except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" - ) + ) from None except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 377fe6478..cdf4afac6 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -536,7 +536,9 @@ def capture_message( else: channel = None except OSError as error: - raise can.CanOperationError(f"Error receiving: {error.strerror}", error.errno) + raise can.CanOperationError( + f"Error receiving: {error.strerror}", error.errno + ) from error can_id, can_dlc, flags, data = dissect_can_frame(cf) @@ -601,7 +603,7 @@ def capture_message( RECEIVED_ANCILLARY_BUFFER_SIZE = CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) -class SocketcanBus(BusABC): +class SocketcanBus(BusABC): # pylint: disable=abstract-method """A SocketCAN interface to CAN. It implements :meth:`can.BusABC._detect_available_configs` to search for @@ -737,7 +739,7 @@ def _recv_internal( # something bad happened (e.g. the interface went down) raise can.CanOperationError( f"Failed to receive: {error.strerror}", error.errno - ) + ) from error if ready_receive_sockets: # not empty get_channel = self.channel == "" @@ -798,7 +800,7 @@ def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: except OSError as error: raise can.CanOperationError( f"Failed to transmit: {error.strerror}", error.errno - ) + ) from error return sent def _send_periodic_internal( @@ -853,7 +855,8 @@ def _send_periodic_internal( # fallback to thread based cyclic task warnings.warn( f"{self.__class__.__name__} falls back to a thread-based cyclic task, " - "when the `modifier_callback` argument is given." + "when the `modifier_callback` argument is given.", + stacklevel=3, ) return BusABC._send_periodic_internal( self, @@ -897,35 +900,3 @@ def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: {"interface": "socketcan", "channel": channel} for channel in find_available_interfaces() ] - - -if __name__ == "__main__": - # This example demonstrates how to use the internal methods of this module. - # It creates two sockets on vcan0 to test sending and receiving. - # - # If you want to try it out you can do the following (possibly using sudo): - # - # modprobe vcan - # ip link add dev vcan0 type vcan - # ip link set vcan0 up - # - log.setLevel(logging.DEBUG) - - def receiver(event: threading.Event) -> None: - receiver_socket = create_socket() - bind_socket(receiver_socket, "vcan0") - print("Receiver is waiting for a message...") - event.set() - print(f"Receiver got: {capture_message(receiver_socket)}") - - def sender(event: threading.Event) -> None: - event.wait() - sender_socket = create_socket() - bind_socket(sender_socket, "vcan0") - msg = Message(arbitration_id=0x01, data=b"\x01\x02\x03") - sender_socket.send(build_can_frame(msg)) - print("Sender sent a message.") - - e = threading.Event() - threading.Thread(target=receiver, args=(e,)).start() - threading.Thread(target=sender, args=(e,)).start() diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 045882388..e852c5e14 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -118,7 +118,7 @@ def detect_beacon(timeout_ms: int = 3100) -> List[can.typechecking.AutoDetectedC log.error(f"Failed to detect beacon: {exc} {traceback.format_exc()}") raise OSError( f"Failed to detect beacon: {exc} {traceback.format_exc()}" - ) + ) from exc return [] @@ -248,7 +248,7 @@ def _recv_internal(self, timeout): except OSError as exc: # something bad happened (e.g. the interface went down) log.error(f"Failed to receive: {exc}") - raise can.CanError(f"Failed to receive: {exc}") + raise can.CanError(f"Failed to receive: {exc}") from exc try: if not ready_receive_sockets: @@ -307,7 +307,9 @@ def _recv_internal(self, timeout): except Exception as exc: log.error(f"Failed to receive: {exc} {traceback.format_exc()}") - raise can.CanError(f"Failed to receive: {exc} {traceback.format_exc()}") + raise can.CanError( + f"Failed to receive: {exc} {traceback.format_exc()}" + ) from exc def _tcp_send(self, msg: str): log.debug(f"Sending TCP Message: '{msg}'") diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index 699763989..9acc34c2c 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -52,9 +52,9 @@ class CanMsg(Structure): ), # Receive time stamp in ms (for transmit messages no meaning) ] - def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None): + def __init__(self, id_=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None): data = [] if data is None else data - super().__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) + super().__init__(id_, frame_format, len(data), (BYTE * 8)(*data), 0) def __eq__(self, other): if not isinstance(other, CanMsg): @@ -71,8 +71,8 @@ def id(self): return self.m_dwID @id.setter - def id(self, id): - self.m_dwID = id + def id(self, value): + self.m_dwID = value @property def frame_format(self): diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 089b8182f..8ca2d516b 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -114,10 +114,12 @@ def __init__( @property def is_fd(self) -> bool: + class_name = self.__class__.__name__ warnings.warn( - "The UdpMulticastBus.is_fd property is deprecated and superseded by " - "BusABC.protocol. It is scheduled for removal in version 5.0.", + f"The {class_name}.is_fd property is deprecated and superseded by " + f"{class_name}.protocol. It is scheduled for removal in python-can version 5.0.", DeprecationWarning, + stacklevel=2, ) return self._can_protocol is CanProtocol.CAN_FD diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index a894c3953..9fbf5c15c 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -150,7 +150,7 @@ def open(self, configuration: str, flags: int): # catch any errors thrown by this call and re-raise raise can.CanInitializationError( f'CanalOpen() failed, configuration: "{configuration}", error: {ex}' - ) + ) from ex else: # any greater-than-zero return value indicates a success # (see https://grodansparadis.gitbooks.io/the-vscp-daemon/canal_interface_specification.html) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 576fa26cf..797539c88 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -58,7 +58,10 @@ INFINITE: Optional[int] try: # Try builtin Python 3 Windows API - from _winapi import INFINITE, WaitForSingleObject # type: ignore + from _winapi import ( # type: ignore[attr-defined,no-redef,unused-ignore] + INFINITE, + WaitForSingleObject, + ) HAS_EVENTS = True except ImportError: @@ -333,10 +336,12 @@ def __init__( @property def fd(self) -> bool: + class_name = self.__class__.__name__ warnings.warn( - "The VectorBus.fd property is deprecated and superseded by " - "BusABC.protocol. It is scheduled for removal in version 5.0.", + f"The {class_name}.fd property is deprecated and superseded by " + f"{class_name}.protocol. It is scheduled for removal in python-can version 5.0.", DeprecationWarning, + stacklevel=2, ) return self._can_protocol is CanProtocol.CAN_FD diff --git a/can/io/logger.py b/can/io/logger.py index c3e83c883..0da0e25c6 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -8,7 +8,18 @@ from abc import ABC, abstractmethod from datetime import datetime from types import TracebackType -from typing import Any, Callable, Dict, Literal, Optional, Set, Tuple, Type, cast +from typing import ( + Any, + Callable, + ClassVar, + Dict, + Literal, + Optional, + Set, + Tuple, + Type, + cast, +) from typing_extensions import Self @@ -60,7 +71,7 @@ class Logger(MessageWriter): """ fetched_plugins = False - message_writers: Dict[str, Type[MessageWriter]] = { + message_writers: ClassVar[Dict[str, Type[MessageWriter]]] = { ".asc": ASCWriter, ".blf": BLFWriter, ".csv": CSVWriter, @@ -72,7 +83,7 @@ class Logger(MessageWriter): } @staticmethod - def __new__( # type: ignore + def __new__( # type: ignore[misc] cls: Any, filename: Optional[StringPathLike], **kwargs: Any ) -> MessageWriter: """ @@ -160,7 +171,7 @@ class BaseRotatingLogger(Listener, BaseIOHandler, ABC): Subclasses must set the `_writer` attribute upon initialization. """ - _supported_formats: Set[str] = set() + _supported_formats: ClassVar[Set[str]] = set() #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename` #: method delegates to this callable. The parameters passed to the callable are @@ -182,12 +193,14 @@ def __init__(self, **kwargs: Any) -> None: self.writer_kwargs = kwargs # Expected to be set by the subclass - self._writer: FileIOMessageWriter = None # type: ignore + self._writer: Optional[FileIOMessageWriter] = None @property def writer(self) -> FileIOMessageWriter: """This attribute holds an instance of a writer class which manages the actual file IO.""" - return self._writer + if self._writer is not None: + return self._writer + raise ValueError(f"{self.__class__.__name__}.writer is None.") def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: """Modify the filename of a log file when rotating. @@ -284,7 +297,7 @@ def __exit__( exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: - return self._writer.__exit__(exc_type, exc_val, exc_tb) + return self.writer.__exit__(exc_type, exc_val, exc_tb) @abstractmethod def should_rollover(self, msg: Message) -> bool: @@ -337,7 +350,7 @@ class SizedRotatingLogger(BaseRotatingLogger): :meth:`~can.Listener.stop` is called. """ - _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"} + _supported_formats: ClassVar[Set[str]] = {".asc", ".blf", ".csv", ".log", ".txt"} def __init__( self, diff --git a/can/io/mf4.py b/can/io/mf4.py index c7e71e816..7ab6ba8a7 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -68,7 +68,7 @@ ] ) except ImportError: - asammdf = None # type: ignore + asammdf = None CAN_MSG_EXT = 0x80000000 diff --git a/can/io/player.py b/can/io/player.py index 9fd9d9ed3..73ef3c356 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -6,7 +6,18 @@ import gzip import pathlib import time -from typing import Any, Dict, Generator, Iterable, Optional, Tuple, Type, Union, cast +from typing import ( + Any, + ClassVar, + Dict, + Generator, + Iterable, + Optional, + Tuple, + Type, + Union, + cast, +) from .._entry_points import read_entry_points from ..message import Message @@ -53,7 +64,7 @@ class LogReader(MessageReader): """ fetched_plugins = False - message_readers: Dict[str, Optional[Type[MessageReader]]] = { + message_readers: ClassVar[Dict[str, Optional[Type[MessageReader]]]] = { ".asc": ASCReader, ".blf": BLFReader, ".csv": CSVReader, @@ -64,7 +75,7 @@ class LogReader(MessageReader): } @staticmethod - def __new__( # type: ignore + def __new__( # type: ignore[misc] cls: Any, filename: StringPathLike, **kwargs: Any, diff --git a/can/io/printer.py b/can/io/printer.py index 40b42862d..00e4545df 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -46,7 +46,7 @@ def on_message_received(self, msg: Message) -> None: if self.write_to_file: cast(TextIO, self.file).write(str(msg) + "\n") else: - print(msg) + print(msg) # noqa: T201 def file_size(self) -> int: """Return an estimate of the current file size in bytes.""" diff --git a/can/io/trc.py b/can/io/trc.py index 889d55196..ce7bf6789 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -116,19 +116,19 @@ def _extract_header(self): logger.info( "TRCReader: No file version was found, so version 1.0 is assumed" ) - self._parse_cols = self._parse_msg_V1_0 + self._parse_cols = self._parse_msg_v1_0 elif self.file_version == TRCFileVersion.V1_0: - self._parse_cols = self._parse_msg_V1_0 + self._parse_cols = self._parse_msg_v1_0 elif self.file_version == TRCFileVersion.V1_1: - self._parse_cols = self._parse_cols_V1_1 + self._parse_cols = self._parse_cols_v1_1 elif self.file_version in [TRCFileVersion.V2_0, TRCFileVersion.V2_1]: - self._parse_cols = self._parse_cols_V2_x + self._parse_cols = self._parse_cols_v2_x else: raise NotImplementedError("File version not fully implemented for reading") return line - def _parse_msg_V1_0(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v1_0(self, cols: List[str]) -> Optional[Message]: arbit_id = cols[2] if arbit_id == "FFFFFFFF": logger.info("TRCReader: Dropping bus info line") @@ -143,7 +143,7 @@ def _parse_msg_V1_0(self, cols: List[str]) -> Optional[Message]: msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) return msg - def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v1_1(self, cols: List[str]) -> Optional[Message]: arbit_id = cols[3] msg = Message() @@ -161,7 +161,7 @@ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]: msg.is_rx = cols[2] == "Rx" return msg - def _parse_msg_V2_x(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v2_x(self, cols: List[str]) -> Optional[Message]: type_ = cols[self.columns["T"]] bus = self.columns.get("B", None) @@ -195,18 +195,18 @@ def _parse_msg_V2_x(self, cols: List[str]) -> Optional[Message]: return msg - def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]: + def _parse_cols_v1_1(self, cols: List[str]) -> Optional[Message]: dtype = cols[2] if dtype in ("Tx", "Rx"): - return self._parse_msg_V1_1(cols) + return self._parse_msg_v1_1(cols) else: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_V2_x(self, cols: List[str]) -> Optional[Message]: + def _parse_cols_v2_x(self, cols: List[str]) -> Optional[Message]: dtype = cols[self.columns["T"]] if dtype in ["DT", "FD", "FB"]: - return self._parse_msg_V2_x(cols) + return self._parse_msg_v2_x(cols) else: logger.info("TRCReader: Unsupported type '%s'", dtype) return None @@ -291,7 +291,7 @@ def __init__( self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 self._format_message = self._format_message_init - def _write_header_V1_0(self, start_time: datetime) -> None: + def _write_header_v1_0(self, start_time: datetime) -> None: lines = [ ";##########################################################################", f"; {self.filepath}", @@ -312,7 +312,7 @@ def _write_header_V1_0(self, start_time: datetime) -> None: ] self.file.writelines(line + "\n" for line in lines) - def _write_header_V2_1(self, start_time: datetime) -> None: + def _write_header_v2_1(self, start_time: datetime) -> None: header_time = start_time - datetime(year=1899, month=12, day=30) lines = [ ";$FILEVERSION=2.1", @@ -372,9 +372,9 @@ def write_header(self, timestamp: float) -> None: start_time = datetime.utcfromtimestamp(timestamp) if self.file_version == TRCFileVersion.V1_0: - self._write_header_V1_0(start_time) + self._write_header_v1_0(start_time) elif self.file_version == TRCFileVersion.V2_1: - self._write_header_V2_1(start_time) + self._write_header_v2_1(start_time) else: raise NotImplementedError("File format is not supported") self.header_written = True diff --git a/can/listener.py b/can/listener.py index d6f252d17..ff3672913 100644 --- a/can/listener.py +++ b/can/listener.py @@ -29,9 +29,6 @@ class Listener(metaclass=ABCMeta): listener.stop() """ - def __init__(self, *args: Any, **kwargs: Any) -> None: - pass - @abstractmethod def on_message_received(self, msg: Message) -> None: """This method is called to handle the given message. @@ -49,6 +46,7 @@ def on_error(self, exc: Exception) -> None: """ raise NotImplementedError() + @abstractmethod def stop(self) -> None: """ Stop handling new messages, carry out any final tasks to ensure @@ -85,9 +83,7 @@ class BufferedReader(Listener): # pylint: disable=abstract-method :attr is_stopped: ``True`` if the reader has been stopped """ - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - + def __init__(self) -> None: # set to "infinite" size self.buffer: SimpleQueue[Message] = SimpleQueue() self.is_stopped: bool = False @@ -139,9 +135,7 @@ class AsyncBufferedReader( print(msg) """ - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - + def __init__(self, **kwargs: Any) -> None: self.buffer: "asyncio.Queue[Message]" if "loop" in kwargs: @@ -149,19 +143,22 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: "The 'loop' argument is deprecated since python-can 4.0.0 " "and has no effect starting with Python 3.10", DeprecationWarning, + stacklevel=2, ) if sys.version_info < (3, 10): self.buffer = asyncio.Queue(loop=kwargs["loop"]) return self.buffer = asyncio.Queue() + self._is_stopped: bool = False def on_message_received(self, msg: Message) -> None: """Append a message to the buffer. Must only be called inside an event loop! """ - self.buffer.put_nowait(msg) + if not self._is_stopped: + self.buffer.put_nowait(msg) async def get_message(self) -> Message: """ @@ -178,3 +175,6 @@ def __aiter__(self) -> AsyncIterator[Message]: async def __anext__(self) -> Message: return await self.buffer.get() + + def stop(self) -> None: + self._is_stopped = True diff --git a/can/logger.py b/can/logger.py index f20965b04..7d1ae6f66 100644 --- a/can/logger.py +++ b/can/logger.py @@ -134,7 +134,7 @@ def _parse_additional_config( def _split_arg(_arg: str) -> Tuple[str, str]: left, right = _arg.split("=", 1) - return left.lstrip("--").replace("-", "_"), right + return left.lstrip("-").replace("-", "_"), right args: Dict[str, Union[str, int, float, bool]] = {} for key, string_val in map(_split_arg, unknown_args): diff --git a/can/util.py b/can/util.py index 42b1f49ac..c1eeca8b1 100644 --- a/can/util.py +++ b/can/util.py @@ -68,7 +68,7 @@ def load_file_config( config = ConfigParser() # make sure to not transform the entries such that capitalization is preserved - config.optionxform = lambda entry: entry # type: ignore + config.optionxform = lambda optionstr: optionstr # type: ignore[method-assign] if path is None: config.read([os.path.expanduser(path) for path in CONFIG_FILES]) @@ -411,7 +411,7 @@ def _rename_kwargs( ) kwargs[new] = value - warnings.warn(deprecation_notice, DeprecationWarning) + warnings.warn(deprecation_notice, DeprecationWarning, stacklevel=3) T2 = TypeVar("T2", BitTiming, BitTimingFd) @@ -444,7 +444,8 @@ def check_or_adjust_timing_clock(timing: T2, valid_clocks: Iterable[int]) -> T2: adjusted_timing = timing.recreate_with_f_clock(clock) warnings.warn( f"Adjusted f_clock in {timing.__class__.__name__} from " - f"{timing.f_clock} to {adjusted_timing.f_clock}" + f"{timing.f_clock} to {adjusted_timing.f_clock}", + stacklevel=2, ) return adjusted_timing except ValueError: @@ -506,11 +507,3 @@ def cast_from_string(string_val: str) -> Union[str, int, float, bool]: # value is string return string_val - - -if __name__ == "__main__": - print("Searching for configuration named:") - print("\n".join(CONFIG_FILES)) - print() - print("Settings:") - print(load_config()) diff --git a/can/viewer.py b/can/viewer.py index db19fd1f6..07752327d 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -43,14 +43,14 @@ try: import curses - from curses.ascii import ESC as KEY_ESC - from curses.ascii import SP as KEY_SPACE + from curses.ascii import ESC as KEY_ESC # type: ignore[attr-defined,unused-ignore] + from curses.ascii import SP as KEY_SPACE # type: ignore[attr-defined,unused-ignore] except ImportError: # Probably on Windows while windows-curses is not installed (e.g. in PyPy) logger.warning( "You won't be able to use the viewer program without curses installed!" ) - curses = None # type: ignore + curses = None # type: ignore[assignment] class CanViewer: # pylint: disable=too-many-instance-attributes @@ -554,7 +554,7 @@ def main() -> None: additional_config.update({"can_filters": can_filters}) bus = _create_bus(parsed_args, **additional_config) - curses.wrapper(CanViewer, bus, data_structs) + curses.wrapper(CanViewer, bus, data_structs) # type: ignore[attr-defined,unused-ignore] if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index a34508ee8..209305fba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,9 +59,9 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==2.17.*", - "ruff==0.0.292", - "black==23.9.*", - "mypy==1.5.*", + "ruff==0.1.13", + "black==23.12.*", + "mypy==1.8.*", ] seeedstudio = ["pyserial>=3.0"] serial = ["pyserial~=3.0"] @@ -100,13 +100,12 @@ ignore_missing_imports = true no_implicit_optional = true disallow_incomplete_defs = true warn_redundant_casts = true -warn_unused_ignores = false +warn_unused_ignores = true exclude = [ "venv", "^doc/conf.py$", "^build", "^test", - "^setup.py$", "^can/interfaces/__init__.py", "^can/interfaces/etas", "^can/interfaces/gs_usb", @@ -128,23 +127,39 @@ exclude = [ [tool.ruff] select = [ + "A", # flake8-builtins + "B", # flake8-bugbear + "C4", # flake8-comprehensions "F", # pyflakes - "UP", # pyupgrade - "I", # isort "E", # pycodestyle errors - "W", # pycodestyle warnings + "I", # isort + "N", # pep8-naming + "PGH", # pygrep-hooks "PL", # pylint "RUF", # ruff-specific rules - "C4", # flake8-comprehensions + "T20", # flake8-print "TCH", # flake8-type-checking + "UP", # pyupgrade + "W", # pycodestyle warnings + "YTT", # flake8-2020 ] ignore = [ + "B026", # star-arg-unpacking-after-keyword-arg + "PLR", # pylint refactor +] +line-length = 100 + +[tool.ruff.per-file-ignores] +"can/interfaces/*" = [ "E501", # Line too long "F403", # undefined-local-with-import-star "F405", # undefined-local-with-import-star-usage - "PLR", # pylint refactor + "N", # pep8-naming + "PGH003", # blanket-type-ignore "RUF012", # mutable-class-default ] +"can/logger.py" = ["T20"] # flake8-print +"can/player.py" = ["T20"] # flake8-print [tool.ruff.isort] known-first-party = ["can"] diff --git a/test/back2back_test.py b/test/back2back_test.py index d9896c19f..8269a73a5 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -273,6 +273,7 @@ def test_sub_second_timestamp_resolution(self): self.bus2.recv(0) self.bus2.recv(0) + @unittest.skipIf(IS_PYPY, "fails randomly when run on CI server") def test_send_periodic_duration(self): """ Verify that send_periodic only transmits for the specified duration. diff --git a/test/network_test.py b/test/network_test.py index 250976fb2..b0fcba37f 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -1,6 +1,5 @@ #!/usr/bin/env python - - +import contextlib import logging import random import threading @@ -117,7 +116,8 @@ def testProducerConsumer(self): i += 1 t.join() - self.server_bus.flush_tx_buffer() + with contextlib.suppress(NotImplementedError): + self.server_bus.flush_tx_buffer() self.server_bus.shutdown() From e173bf1179380dc2f5aa3803f738bdc165e53011 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 13 Jan 2024 21:08:55 +0100 Subject: [PATCH 1095/1235] move pylint config to pyproject.toml (#1725) --- .github/workflows/ci.yml | 2 +- .pylintrc | 504 --------------------------------------- can/io/logger.py | 2 +- can/io/trc.py | 6 +- doc/conf.py | 2 +- doc/development.rst | 2 +- pyproject.toml | 22 +- 7 files changed, 28 insertions(+), 512 deletions(-) delete mode 100644 .pylintrc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1aa8935a..639465176 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,7 +105,7 @@ jobs: ruff check can - name: pylint run: | - pylint --rcfile=.pylintrc \ + pylint \ can/**.py \ can/io \ doc/conf.py \ diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index de2e82a0d..000000000 --- a/.pylintrc +++ /dev/null @@ -1,504 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-whitelist= - -# Add files or directories to be ignored. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to be ignored. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=0 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=invalid-name, - missing-docstring, - empty-docstring, - wrong-import-order, - wrong-import-position, - too-few-public-methods, - too-many-public-methods, - too-many-branches, - too-many-locals, - too-many-statements, - no-else-raise, - wildcard-import, - no-else-return, - fixme, # We deliberately use TODO/FIXME inline - abstract-class-instantiated, # Needed for can.Bus - duplicate-code, # Needed due to https://github.com/PyCQA/pylint/issues/214 - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member, - useless-suppression, - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[LOGGING] - -# Format style used to check logging format string. `old` means using % -# formatting, while `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[STRING] - -# This flag controls whether the implicit-str-concat-in-sequence should -# generate a warning on implicit string concatenation in sequences defined over -# several lines. -check-str-concat-over-line-jumps=no - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -#variable-rgx= - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package.. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Allow explicit reexports by alias from a package __init__ -allow-reexport-from-package=no - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[DESIGN] - -# Maximum number of arguments for function / method. -max-args=10 - -# Maximum number of attributes for a class (see R0902). -max-attributes=10 - -# Maximum number of boolean expressions in an if statement. -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=builtins.BaseException, - builtins.Exception diff --git a/can/io/logger.py b/can/io/logger.py index 0da0e25c6..90e6bfc7c 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -270,7 +270,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: logger = Logger(filename=filename, **self.writer_kwargs) if isinstance(logger, FileIOMessageWriter): return logger - elif isinstance(logger, Printer) and logger.file is not None: + if isinstance(logger, Printer) and logger.file is not None: return cast(FileIOMessageWriter, logger) raise ValueError( diff --git a/can/io/trc.py b/can/io/trc.py index ce7bf6789..a498da992 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -407,7 +407,7 @@ def on_message_received(self, msg: Message) -> None: if msg.is_fd: logger.warning("TRCWriter: Logging CAN FD is not implemented") return - else: - serialized = self._format_message(msg, channel) - self.msgnr += 1 + + serialized = self._format_message(msg, channel) + self.msgnr += 1 self.log_event(serialized, msg.timestamp) diff --git a/doc/conf.py b/doc/conf.py index 4b490ee29..54318883f 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -17,7 +17,7 @@ sys.path.insert(0, os.path.abspath("..")) import can # pylint: disable=wrong-import-position -from can import ctypesutil +from can import ctypesutil # pylint: disable=wrong-import-position # -- General configuration ----------------------------------------------------- diff --git a/doc/development.rst b/doc/development.rst index fb717a52d..484c90c05 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -57,7 +57,7 @@ The linters can be run with:: black --check can mypy can ruff check can - pylint --rcfile=.pylintrc can/**.py + pylint can/**.py can/io doc/conf.py examples/**.py can/interfaces/socketcan Creating a new interface/backend diff --git a/pyproject.toml b/pyproject.toml index 209305fba..d3e496d28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ - "pylint==2.17.*", + "pylint==3.0.*", "ruff==0.1.13", "black==23.12.*", "mypy==1.8.*", @@ -163,3 +163,23 @@ line-length = 100 [tool.ruff.isort] known-first-party = ["can"] + +[tool.pylint] +disable = [ + "cyclic-import", + "duplicate-code", + "fixme", + "invalid-name", + "missing-class-docstring", + "missing-function-docstring", + "missing-module-docstring", + "no-else-raise", + "no-else-return", + "too-few-public-methods", + "too-many-arguments", + "too-many-branches", + "too-many-instance-attributes", + "too-many-locals", + "too-many-public-methods", + "too-many-statements", +] From ec105acea0d08eb0edd3bbfd5503b0a0cded28af Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 14 Jan 2024 13:44:51 +0100 Subject: [PATCH 1096/1235] Convert `can.Logger` and `can.LogReader` into functions, improve docs (#1703) * turn can.Logger and can.LogReader into functions * improve file io documentation * fix doctest * don't check if class is None, check length of suffixes * fix typo * fix issues after rebase * fix test issues, use context manager to fix random PyPy failures * align can.LogReader docstring to can.Logger * replace deprecated `context` with `config_context` --- can/io/__init__.py | 6 +- can/io/logger.py | 197 ++++++++++++----------- can/io/player.py | 174 +++++++++++---------- can/io/printer.py | 2 +- doc/api.rst | 5 +- doc/configuration.rst | 4 +- doc/development.rst | 4 +- doc/{listeners.rst => file_io.rst} | 142 +++++------------ doc/internal-api.rst | 1 + doc/notifier.rst | 86 ++++++++++ test/logformats_test.py | 4 +- test/test_rotating_loggers.py | 242 ++++++++++++++--------------- 12 files changed, 448 insertions(+), 419 deletions(-) rename doc/{listeners.rst => file_io.rst} (65%) create mode 100644 doc/notifier.rst diff --git a/can/io/__init__.py b/can/io/__init__.py index 263bbe235..5601f2591 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -15,6 +15,8 @@ "CSVWriter", "Logger", "LogReader", + "MESSAGE_READERS", + "MESSAGE_WRITERS", "MessageSync", "MF4Reader", "MF4Writer", @@ -39,8 +41,8 @@ ] # Generic -from .logger import BaseRotatingLogger, Logger, SizedRotatingLogger -from .player import LogReader, MessageSync +from .logger import MESSAGE_WRITERS, BaseRotatingLogger, Logger, SizedRotatingLogger +from .player import MESSAGE_READERS, LogReader, MessageSync # isort: split diff --git a/can/io/logger.py b/can/io/logger.py index 90e6bfc7c..ed7f15bd0 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -13,6 +13,7 @@ Callable, ClassVar, Dict, + Final, Literal, Optional, Set, @@ -24,7 +25,6 @@ from typing_extensions import Self from .._entry_points import read_entry_points -from ..listener import Listener from ..message import Message from ..typechecking import AcceptedIOType, FileLike, StringPathLike from .asc import ASCWriter @@ -32,7 +32,6 @@ from .canutils import CanutilsLogWriter from .csv import CSVWriter from .generic import ( - BaseIOHandler, BinaryIOMessageWriter, FileIOMessageWriter, MessageWriter, @@ -42,20 +41,85 @@ from .sqlite import SqliteWriter from .trc import TRCWriter +#: A map of file suffixes to their corresponding +#: :class:`can.io.generic.MessageWriter` class +MESSAGE_WRITERS: Final[Dict[str, Type[MessageWriter]]] = { + ".asc": ASCWriter, + ".blf": BLFWriter, + ".csv": CSVWriter, + ".db": SqliteWriter, + ".log": CanutilsLogWriter, + ".mf4": MF4Writer, + ".trc": TRCWriter, + ".txt": Printer, +} + + +def _update_writer_plugins() -> None: + """Update available message writer plugins from entry points.""" + for entry_point in read_entry_points("can.io.message_writer"): + if entry_point.key in MESSAGE_WRITERS: + continue + + writer_class = entry_point.load() + if issubclass(writer_class, MessageWriter): + MESSAGE_WRITERS[entry_point.key] = writer_class + + +def _get_logger_for_suffix(suffix: str) -> Type[MessageWriter]: + try: + return MESSAGE_WRITERS[suffix] + except KeyError: + raise ValueError( + f'No write support for unknown log format "{suffix}"' + ) from None + -class Logger(MessageWriter): +def _compress( + filename: StringPathLike, **kwargs: Any +) -> Tuple[Type[MessageWriter], FileLike]: """ - Logs CAN messages to a file. + Return the suffix and io object of the decompressed file. + File will automatically recompress upon close. + """ + suffixes = pathlib.Path(filename).suffixes + if len(suffixes) != 2: + raise ValueError( + f"No write support for unknown log format \"{''.join(suffixes)}\"" + ) from None + + real_suffix = suffixes[-2].lower() + if real_suffix in (".blf", ".db"): + raise ValueError( + f"The file type {real_suffix} is currently incompatible with gzip." + ) + logger_type = _get_logger_for_suffix(real_suffix) + append = kwargs.get("append", False) + + if issubclass(logger_type, BinaryIOMessageWriter): + mode = "ab" if append else "wb" + else: + mode = "at" if append else "wt" + + return logger_type, gzip.open(filename, mode) + + +def Logger( # noqa: N802 + filename: Optional[StringPathLike], **kwargs: Any +) -> MessageWriter: + """Find and return the appropriate :class:`~can.io.generic.MessageWriter` instance + for a given file suffix. The format is determined from the file suffix which can be one of: - * .asc: :class:`can.ASCWriter` + * .asc :class:`can.ASCWriter` * .blf :class:`can.BLFWriter` * .csv: :class:`can.CSVWriter` - * .db: :class:`can.SqliteWriter` + * .db :class:`can.SqliteWriter` * .log :class:`can.CanutilsLogWriter` + * .mf4 :class:`can.MF4Writer` + (optional, depends on `asammdf `_) * .trc :class:`can.TRCWriter` * .txt :class:`can.Printer` - * .mf4 :class:`can.MF4Writer` (optional, depends on asammdf) Any of these formats can be used with gzip compression by appending the suffix .gz (e.g. filename.asc.gz). However, third-party tools might not @@ -65,97 +129,33 @@ class Logger(MessageWriter): The log files may be incomplete until `stop()` is called due to buffering. + :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 + .. note:: - This class itself is just a dispatcher, and any positional and keyword + This function itself is just a dispatcher, and any positional and keyword arguments are passed on to the returned instance. """ - fetched_plugins = False - message_writers: ClassVar[Dict[str, Type[MessageWriter]]] = { - ".asc": ASCWriter, - ".blf": BLFWriter, - ".csv": CSVWriter, - ".db": SqliteWriter, - ".log": CanutilsLogWriter, - ".mf4": MF4Writer, - ".trc": TRCWriter, - ".txt": Printer, - } - - @staticmethod - def __new__( # type: ignore[misc] - cls: Any, filename: Optional[StringPathLike], **kwargs: Any - ) -> MessageWriter: - """ - :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 is None: - return Printer(**kwargs) - - if not Logger.fetched_plugins: - Logger.message_writers.update( - { - writer.key: cast(Type[MessageWriter], writer.load()) - for writer in read_entry_points("can.io.message_writer") - } - ) - Logger.fetched_plugins = True - - suffix = pathlib.PurePath(filename).suffix.lower() - - file_or_filename: AcceptedIOType = filename - if suffix == ".gz": - logger_type, file_or_filename = Logger.compress(filename, **kwargs) - else: - logger_type = cls._get_logger_for_suffix(suffix) - - return logger_type(file=file_or_filename, **kwargs) - - @classmethod - def _get_logger_for_suffix(cls, suffix: str) -> Type[MessageWriter]: - try: - logger_type = Logger.message_writers[suffix] - if logger_type is None: - raise ValueError(f'failed to import logger for extension "{suffix}"') - return logger_type - except KeyError: - raise ValueError( - f'No write support for this unknown log format "{suffix}"' - ) from None - - @classmethod - def compress( - cls, filename: StringPathLike, **kwargs: Any - ) -> Tuple[Type[MessageWriter], FileLike]: - """ - Return the suffix and io object of the decompressed file. - File will automatically recompress upon close. - """ - real_suffix = pathlib.Path(filename).suffixes[-2].lower() - if real_suffix in (".blf", ".db"): - raise ValueError( - f"The file type {real_suffix} is currently incompatible with gzip." - ) - logger_type = cls._get_logger_for_suffix(real_suffix) - append = kwargs.get("append", False) - - if issubclass(logger_type, BinaryIOMessageWriter): - mode = "ab" if append else "wb" - else: - mode = "at" if append else "wt" + if filename is None: + return Printer(**kwargs) - return logger_type, gzip.open(filename, mode) + _update_writer_plugins() - def on_message_received(self, msg: Message) -> None: - pass + suffix = pathlib.PurePath(filename).suffix.lower() + file_or_filename: AcceptedIOType = filename + if suffix == ".gz": + logger_type, file_or_filename = _compress(filename, **kwargs) + else: + logger_type = _get_logger_for_suffix(suffix) + return logger_type(file=file_or_filename, **kwargs) -class BaseRotatingLogger(Listener, BaseIOHandler, ABC): +class BaseRotatingLogger(MessageWriter, ABC): """ Base class for rotating CAN loggers. This class is not meant to be instantiated directly. Subclasses must implement the :meth:`should_rollover` @@ -187,20 +187,15 @@ class BaseRotatingLogger(Listener, BaseIOHandler, ABC): rollover_count: int = 0 def __init__(self, **kwargs: Any) -> None: - Listener.__init__(self) - BaseIOHandler.__init__(self, file=None) + super().__init__(**{**kwargs, "file": None}) self.writer_kwargs = kwargs - # Expected to be set by the subclass - self._writer: Optional[FileIOMessageWriter] = None - @property + @abstractmethod def writer(self) -> FileIOMessageWriter: """This attribute holds an instance of a writer class which manages the actual file IO.""" - if self._writer is not None: - return self._writer - raise ValueError(f"{self.__class__.__name__}.writer is None.") + raise NotImplementedError def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: """Modify the filename of a log file when rotating. @@ -270,7 +265,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: logger = Logger(filename=filename, **self.writer_kwargs) if isinstance(logger, FileIOMessageWriter): return logger - if isinstance(logger, Printer) and logger.file is not None: + elif isinstance(logger, Printer) and logger.file is not None: return cast(FileIOMessageWriter, logger) raise ValueError( @@ -373,6 +368,10 @@ def __init__( self._writer = self._get_new_writer(self.base_filename) + @property + def writer(self) -> FileIOMessageWriter: + return self._writer + def should_rollover(self, msg: Message) -> bool: if self.max_bytes <= 0: return False diff --git a/can/io/player.py b/can/io/player.py index 73ef3c356..4cbd7ce16 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -8,15 +8,13 @@ import time from typing import ( Any, - ClassVar, Dict, + Final, Generator, Iterable, - Optional, Tuple, Type, Union, - cast, ) from .._entry_points import read_entry_points @@ -31,106 +29,104 @@ from .sqlite import SqliteReader from .trc import TRCReader - -class LogReader(MessageReader): +#: A map of file suffixes to their corresponding +#: :class:`can.io.generic.MessageReader` class +MESSAGE_READERS: Final[Dict[str, Type[MessageReader]]] = { + ".asc": ASCReader, + ".blf": BLFReader, + ".csv": CSVReader, + ".db": SqliteReader, + ".log": CanutilsLogReader, + ".mf4": MF4Reader, + ".trc": TRCReader, +} + + +def _update_reader_plugins() -> None: + """Update available message reader plugins from entry points.""" + for entry_point in read_entry_points("can.io.message_reader"): + if entry_point.key in MESSAGE_READERS: + continue + + reader_class = entry_point.load() + if issubclass(reader_class, MessageReader): + MESSAGE_READERS[entry_point.key] = reader_class + + +def _get_logger_for_suffix(suffix: str) -> Type[MessageReader]: + """Find MessageReader class for given suffix.""" + try: + return MESSAGE_READERS[suffix] + except KeyError: + raise ValueError(f'No read support for unknown log format "{suffix}"') from None + + +def _decompress( + filename: StringPathLike, +) -> Tuple[Type[MessageReader], Union[str, FileLike]]: + """ + Return the suffix and io object of the decompressed file. """ - Replay logged CAN messages from a file. + suffixes = pathlib.Path(filename).suffixes + if len(suffixes) != 2: + raise ValueError( + f"No write support for unknown log format \"{''.join(suffixes)}\"" + ) from None + + real_suffix = suffixes[-2].lower() + reader_type = _get_logger_for_suffix(real_suffix) + + mode = "rb" if issubclass(reader_type, BinaryIOMessageReader) else "rt" + + return reader_type, gzip.open(filename, mode) + + +def LogReader(filename: StringPathLike, **kwargs: Any) -> MessageReader: # noqa: N802 + """Find and return the appropriate :class:`~can.io.generic.MessageReader` instance + for a given file suffix. The format is determined from the file suffix which can be one of: - * .asc - * .blf - * .csv - * .db - * .log - * .mf4 (optional, depends on asammdf) - * .trc + * .asc :class:`can.ASCReader` + * .blf :class:`can.BLFReader` + * .csv :class:`can.CSVReader` + * .db :class:`can.SqliteReader` + * .log :class:`can.CanutilsLogReader` + * .mf4 :class:`can.MF4Reader` + (optional, depends on `asammdf `_) + * .trc :class:`can.TRCReader` Gzip compressed files can be used as long as the original files suffix is one of the above (e.g. filename.asc.gz). - Exposes a simple iterator interface, to use simply: + Exposes a simple iterator interface, to use simply:: + + for msg in can.LogReader("some/path/to/my_file.log"): + print(msg) - >>> for msg in LogReader("some/path/to/my_file.log"): - ... print(msg) + :param filename: + the filename/path of the file to read from + :raises ValueError: + if the filename's suffix is of an unknown file type .. note:: There are no time delays, if you want to reproduce the measured delays between messages look at the :class:`can.MessageSync` class. .. note:: - This class itself is just a dispatcher, and any positional an keyword + This function itself is just a dispatcher, and any positional and keyword arguments are passed on to the returned instance. """ - fetched_plugins = False - message_readers: ClassVar[Dict[str, Optional[Type[MessageReader]]]] = { - ".asc": ASCReader, - ".blf": BLFReader, - ".csv": CSVReader, - ".db": SqliteReader, - ".log": CanutilsLogReader, - ".mf4": MF4Reader, - ".trc": TRCReader, - } - - @staticmethod - def __new__( # type: ignore[misc] - cls: Any, - filename: StringPathLike, - **kwargs: Any, - ) -> MessageReader: - """ - :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 not LogReader.fetched_plugins: - LogReader.message_readers.update( - { - reader.key: cast(Type[MessageReader], reader.load()) - for reader in read_entry_points("can.io.message_reader") - } - ) - LogReader.fetched_plugins = True - - suffix = pathlib.PurePath(filename).suffix.lower() - - file_or_filename: AcceptedIOType = filename - if suffix == ".gz": - reader_type, file_or_filename = LogReader.decompress(filename) - else: - reader_type = cls._get_logger_for_suffix(suffix) - return reader_type(file=file_or_filename, **kwargs) - - @classmethod - def _get_logger_for_suffix(cls, suffix: str) -> Type[MessageReader]: - try: - reader_type = LogReader.message_readers[suffix] - except KeyError: - raise ValueError( - f'No read support for this unknown log format "{suffix}"' - ) from None - if reader_type is None: - raise ImportError(f"failed to import reader for extension {suffix}") - return reader_type - - @classmethod - def decompress( - cls, - filename: StringPathLike, - ) -> Tuple[Type[MessageReader], Union[str, FileLike]]: - """ - Return the suffix and io object of the decompressed file. - """ - real_suffix = pathlib.Path(filename).suffixes[-2].lower() - reader_type = cls._get_logger_for_suffix(real_suffix) - - mode = "rb" if issubclass(reader_type, BinaryIOMessageReader) else "rt" + _update_reader_plugins() - return reader_type, gzip.open(filename, mode) - - def __iter__(self) -> Generator[Message, None, None]: - raise NotImplementedError() + suffix = pathlib.PurePath(filename).suffix.lower() + file_or_filename: AcceptedIOType = filename + if suffix == ".gz": + reader_type, file_or_filename = _decompress(filename) + else: + reader_type = _get_logger_for_suffix(suffix) + return reader_type(file=file_or_filename, **kwargs) class MessageSync: @@ -152,6 +148,16 @@ def __init__( 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). + + Example:: + + import can + + with can.LogReader("my_logfile.asc") as reader, can.Bus(interface="virtual") as bus: + for msg in can.MessageSync(messages=reader): + print(msg) + bus.send(msg) + """ self.raw_messages = messages self.timestamps = timestamps diff --git a/can/io/printer.py b/can/io/printer.py index 00e4545df..67c353cc6 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -33,7 +33,7 @@ def __init__( """ :param file: An optional path-like object or a file-like object to "print" to instead of writing to standard out (stdout). - If this is a file-like object, is has to be opened in text + If this is a file-like object, it has to be opened in text write mode, not binary write mode. :param append: If set to `True` messages, are appended to the file, else the file is truncated diff --git a/doc/api.rst b/doc/api.rst index 053bd34a4..50095589c 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -10,11 +10,12 @@ A form of CAN interface is also required. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 bus message - listeners + notifier + file_io asyncio bcm errors diff --git a/doc/configuration.rst b/doc/configuration.rst index 7b42017a9..2951a63b1 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -83,8 +83,8 @@ The configuration can also contain additional sections (or context): from can.interface import Bus - hs_bus = Bus(context='HS') - ms_bus = Bus(context='MS') + hs_bus = Bus(config_context='HS') + ms_bus = Bus(config_context='MS') Environment Variables --------------------- diff --git a/doc/development.rst b/doc/development.rst index 484c90c05..ec7b7dc24 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -9,7 +9,7 @@ Contribute to source code, documentation, examples and report issues: https://github.com/hardbyte/python-can Note that the latest released version on PyPi may be significantly behind the -``develop`` branch. Please open any feature requests against the ``develop`` branch +``main`` branch. Please open any feature requests against the ``main`` branch There is also a `python-can `__ mailing list for development discussion. @@ -100,7 +100,7 @@ The modules in ``python-can`` are: +---------------------------------+------------------------------------------------------+ |:doc:`message ` | Contains the interface independent Message object. | +---------------------------------+------------------------------------------------------+ -|:doc:`io ` | Contains a range of file readers and writers. | +|:doc:`io ` | Contains a range of file readers and writers. | +---------------------------------+------------------------------------------------------+ |:doc:`broadcastmanager ` | Contains interface independent broadcast manager | | | code. | diff --git a/doc/listeners.rst b/doc/file_io.rst similarity index 65% rename from doc/listeners.rst rename to doc/file_io.rst index 110e960d3..ff9431695 100644 --- a/doc/listeners.rst +++ b/doc/file_io.rst @@ -1,110 +1,20 @@ +File IO +======= -Reading and Writing Messages -============================ -.. _notifier: - -Notifier --------- - -The Notifier object is used as a message distributor for a bus. Notifier creates a thread to read messages from the bus and distributes them to listeners. - -.. autoclass:: can.Notifier - :members: - -.. _listeners_doc: - -Listener --------- - -The Listener class is an "abstract" base class for any objects which wish to -register to receive notifications of new messages on the bus. A Listener can -be used in two ways; the default is to **call** the Listener with a new -message, or by calling the method **on_message_received**. - -Listeners are registered with :ref:`notifier` object(s) which ensure they are -notified whenever a new message is received. - -.. literalinclude:: ../examples/print_notifier.py - :language: python - :linenos: - :emphasize-lines: 8,9 - - -Subclasses of Listener that do not override **on_message_received** will cause -:class:`NotImplementedError` to be thrown when a message is received on -the CAN bus. - -.. autoclass:: can.Listener - :members: - -There are some listeners that already ship together with `python-can` -and are listed below. -Some of them allow messages to be written to files, and the corresponding file -readers are also documented here. - -.. note :: - - Please note that writing and the reading a message might not always yield a - completely unchanged message again, since some properties are not (yet) - supported by some file formats. - -.. note :: - - Additional file formats for both reading/writing log files can be added via - a plugin reader/writer. An external package can register a new reader - by using the ``can.io.message_reader`` entry point. Similarly, a writer can - be added using the ``can.io.message_writer`` entry point. - - The format of the entry point is ``reader_name=module:classname`` where ``classname`` - is a :class:`can.io.generic.BaseIOHandler` concrete implementation. - - :: - - entry_points={ - 'can.io.message_reader': [ - '.asc = my_package.io.asc:ASCReader' - ] - }, - - -BufferedReader --------------- - -.. autoclass:: can.BufferedReader - :members: - -.. autoclass:: can.AsyncBufferedReader - :members: - - -RedirectReader --------------- - -.. autoclass:: can.RedirectReader - :members: - - -Logger ------- - -The :class:`can.Logger` uses the following :class:`can.Listener` types to -create log files with different file types of the messages received. - -.. autoclass:: can.Logger - :members: - -.. autoclass:: can.io.BaseRotatingLogger - :members: - -.. autoclass:: can.SizedRotatingLogger - :members: +Reading and Writing Files +------------------------- +.. autofunction:: can.LogReader +.. autofunction:: can.Logger +.. autodata:: can.io.logger.MESSAGE_WRITERS +.. autodata:: can.io.player.MESSAGE_READERS Printer ------- .. autoclass:: can.Printer + :show-inheritance: :members: @@ -112,9 +22,11 @@ CSVWriter --------- .. autoclass:: can.CSVWriter + :show-inheritance: :members: .. autoclass:: can.CSVReader + :show-inheritance: :members: @@ -122,9 +34,11 @@ SqliteWriter ------------ .. autoclass:: can.SqliteWriter + :show-inheritance: :members: .. autoclass:: can.SqliteReader + :show-inheritance: :members: @@ -164,6 +78,7 @@ engineered from existing log files. One description of the format can be found ` .. autoclass:: can.ASCWriter + :show-inheritance: :members: ASCReader reads CAN data from ASCII log files .asc, @@ -172,6 +87,7 @@ as further references can-utils can be used: `log2asc `_. .. autoclass:: can.ASCReader + :show-inheritance: :members: @@ -185,11 +101,13 @@ As specification following references can-utils can be used: .. autoclass:: can.CanutilsLogWriter + :show-inheritance: :members: **CanutilsLogReader** reads CAN data from ASCII log files .log .. autoclass:: can.CanutilsLogReader + :show-inheritance: :members: @@ -204,11 +122,13 @@ The data is stored in a compressed format which makes it very compact. .. note:: Channels will be converted to integers. .. autoclass:: can.BLFWriter + :show-inheritance: :members: The following class can be used to read messages from BLF file: .. autoclass:: can.BLFReader + :show-inheritance: :members: @@ -229,6 +149,7 @@ The data is stored in a compressed format which makes it compact. .. autoclass:: can.MF4Writer + :show-inheritance: :members: The MDF format is very flexible regarding the internal structure and it is used to handle data from multiple sources, not just CAN bus logging. @@ -239,6 +160,7 @@ Therefor MF4Reader can only replay files created with MF4Writer. The following class can be used to read messages from MF4 file: .. autoclass:: can.MF4Reader + :show-inheritance: :members: @@ -252,9 +174,31 @@ Implements basic support for the TRC file format. Comments and contributions are welcome on what file versions might be relevant. .. autoclass:: can.TRCWriter + :show-inheritance: :members: The following class can be used to read messages from TRC file: .. autoclass:: can.TRCReader + :show-inheritance: + :members: + + +Rotating Loggers +---------------- + +.. autoclass:: can.io.BaseRotatingLogger + :show-inheritance: + :members: + +.. autoclass:: can.SizedRotatingLogger + :show-inheritance: :members: + + +Replaying Files +--------------- + +.. autoclass:: can.MessageSync + :members: + diff --git a/doc/internal-api.rst b/doc/internal-api.rst index f4b6f875a..73984bf1a 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -127,6 +127,7 @@ IO Utilities .. automodule:: can.io.generic :members: + :member-order: bysource diff --git a/doc/notifier.rst b/doc/notifier.rst new file mode 100644 index 000000000..05edbd90d --- /dev/null +++ b/doc/notifier.rst @@ -0,0 +1,86 @@ +Notifier and Listeners +====================== + +.. _notifier: + +Notifier +-------- + +The Notifier object is used as a message distributor for a bus. The Notifier +uses an event loop or creates a thread to read messages from the bus and +distributes them to listeners. + +.. autoclass:: can.Notifier + :members: + +.. _listeners_doc: + +Listener +-------- + +The Listener class is an "abstract" base class for any objects which wish to +register to receive notifications of new messages on the bus. A Listener can +be used in two ways; the default is to **call** the Listener with a new +message, or by calling the method **on_message_received**. + +Listeners are registered with :ref:`notifier` object(s) which ensure they are +notified whenever a new message is received. + +.. literalinclude:: ../examples/print_notifier.py + :language: python + :linenos: + :emphasize-lines: 8,9 + + +Subclasses of Listener that do not override **on_message_received** will cause +:class:`NotImplementedError` to be thrown when a message is received on +the CAN bus. + +.. autoclass:: can.Listener + :members: + +There are some listeners that already ship together with `python-can` +and are listed below. +Some of them allow messages to be written to files, and the corresponding file +readers are also documented here. + +.. note :: + + Please note that writing and the reading a message might not always yield a + completely unchanged message again, since some properties are not (yet) + supported by some file formats. + +.. note :: + + Additional file formats for both reading/writing log files can be added via + a plugin reader/writer. An external package can register a new reader + by using the ``can.io.message_reader`` entry point. Similarly, a writer can + be added using the ``can.io.message_writer`` entry point. + + The format of the entry point is ``reader_name=module:classname`` where ``classname`` + is a :class:`can.io.generic.BaseIOHandler` concrete implementation. + + :: + + entry_points={ + 'can.io.message_reader': [ + '.asc = my_package.io.asc:ASCReader' + ] + }, + + +BufferedReader +-------------- + +.. autoclass:: can.BufferedReader + :members: + +.. autoclass:: can.AsyncBufferedReader + :members: + + +RedirectReader +-------------- + +.. autoclass:: can.RedirectReader + :members: diff --git a/test/logformats_test.py b/test/logformats_test.py index 50f48c391..8694fefdc 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -52,8 +52,8 @@ def _get_suffix_case_variants(self, suffix): ] def _test_extension(self, suffix): - WriterType = can.Logger.message_writers.get(suffix) - ReaderType = can.LogReader.message_readers.get(suffix) + WriterType = can.io.MESSAGE_WRITERS.get(suffix) + ReaderType = can.io.MESSAGE_READERS.get(suffix) for suffix_variant in self._get_suffix_case_variants(suffix): tmp_file = tempfile.NamedTemporaryFile(suffix=suffix_variant, delete=False) tmp_file.close() diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index 8230168b9..a6661280d 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -6,24 +6,34 @@ import os from pathlib import Path +from typing import cast from unittest.mock import Mock import can +from can.io.generic import FileIOMessageWriter +from can.typechecking import StringPathLike from .data.example_data import generate_message class TestBaseRotatingLogger: @staticmethod - def _get_instance(path, *args, **kwargs) -> can.io.BaseRotatingLogger: + def _get_instance(file: StringPathLike) -> can.io.BaseRotatingLogger: class SubClass(can.io.BaseRotatingLogger): """Subclass that implements abstract methods for testing.""" _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"} - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self._writer = can.Printer(file=path / "__unused.txt") + def __init__(self, file: StringPathLike, **kwargs) -> None: + super().__init__(**kwargs) + suffix = Path(file).suffix.lower() + if suffix not in self._supported_formats: + raise ValueError(f"Unsupported file format: {suffix}") + self._writer = can.Printer(file=file) + + @property + def writer(self) -> FileIOMessageWriter: + return cast(FileIOMessageWriter, self._writer) def should_rollover(self, msg: can.Message) -> bool: return False @@ -31,7 +41,7 @@ def should_rollover(self, msg: can.Message) -> bool: def do_rollover(self): ... - return SubClass(*args, **kwargs) + return SubClass(file=file) def test_import(self): assert hasattr(can.io, "BaseRotatingLogger") @@ -50,90 +60,82 @@ def test_attributes(self): assert hasattr(can.io.BaseRotatingLogger, "do_rollover") def test_get_new_writer(self, tmp_path): - logger_instance = self._get_instance(tmp_path) - - writer = logger_instance._get_new_writer(tmp_path / "file.ASC") - assert isinstance(writer, can.ASCWriter) - writer.stop() + with self._get_instance(tmp_path / "__unused.txt") as logger_instance: + writer = logger_instance._get_new_writer(tmp_path / "file.ASC") + assert isinstance(writer, can.ASCWriter) + writer.stop() - writer = logger_instance._get_new_writer(tmp_path / "file.BLF") - assert isinstance(writer, can.BLFWriter) - writer.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.BLF") + assert isinstance(writer, can.BLFWriter) + writer.stop() - writer = logger_instance._get_new_writer(tmp_path / "file.CSV") - assert isinstance(writer, can.CSVWriter) - writer.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.CSV") + assert isinstance(writer, can.CSVWriter) + writer.stop() - writer = logger_instance._get_new_writer(tmp_path / "file.LOG") - assert isinstance(writer, can.CanutilsLogWriter) - writer.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.LOG") + assert isinstance(writer, can.CanutilsLogWriter) + writer.stop() - writer = logger_instance._get_new_writer(tmp_path / "file.TXT") - assert isinstance(writer, can.Printer) - writer.stop() + writer = logger_instance._get_new_writer(tmp_path / "file.TXT") + assert isinstance(writer, can.Printer) + writer.stop() def test_rotation_filename(self, tmp_path): - logger_instance = self._get_instance(tmp_path) + with self._get_instance(tmp_path / "__unused.txt") as logger_instance: + default_name = "default" + assert logger_instance.rotation_filename(default_name) == "default" - default_name = "default" - assert logger_instance.rotation_filename(default_name) == "default" - - logger_instance.namer = lambda x: x + "_by_namer" - assert logger_instance.rotation_filename(default_name) == "default_by_namer" + logger_instance.namer = lambda x: x + "_by_namer" + assert logger_instance.rotation_filename(default_name) == "default_by_namer" def test_rotate_without_rotator(self, tmp_path): - logger_instance = self._get_instance(tmp_path) - - source = str(tmp_path / "source.txt") - dest = str(tmp_path / "dest.txt") + with self._get_instance(tmp_path / "__unused.txt") as logger_instance: + source = str(tmp_path / "source.txt") + dest = str(tmp_path / "dest.txt") - assert os.path.exists(source) is False - assert os.path.exists(dest) is False + assert os.path.exists(source) is False + assert os.path.exists(dest) is False - logger_instance._writer = logger_instance._get_new_writer(source) - logger_instance.stop() + logger_instance._writer = logger_instance._get_new_writer(source) + logger_instance.stop() - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + assert os.path.exists(source) is True + assert os.path.exists(dest) is False - logger_instance.rotate(source, dest) + logger_instance.rotate(source, dest) - assert os.path.exists(source) is False - assert os.path.exists(dest) is True + assert os.path.exists(source) is False + assert os.path.exists(dest) is True def test_rotate_with_rotator(self, tmp_path): - logger_instance = self._get_instance(tmp_path) - - rotator_func = Mock() - logger_instance.rotator = rotator_func + with self._get_instance(tmp_path / "__unused.txt") as logger_instance: + rotator_func = Mock() + logger_instance.rotator = rotator_func - source = str(tmp_path / "source.txt") - dest = str(tmp_path / "dest.txt") + source = str(tmp_path / "source.txt") + dest = str(tmp_path / "dest.txt") - assert os.path.exists(source) is False - assert os.path.exists(dest) is False + assert os.path.exists(source) is False + assert os.path.exists(dest) is False - logger_instance._writer = logger_instance._get_new_writer(source) - logger_instance.stop() + logger_instance._writer = logger_instance._get_new_writer(source) + logger_instance.stop() - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + assert os.path.exists(source) is True + assert os.path.exists(dest) is False - logger_instance.rotate(source, dest) - rotator_func.assert_called_with(source, dest) + logger_instance.rotate(source, dest) + rotator_func.assert_called_with(source, dest) - # assert that no rotation was performed since rotator_func - # does not do anything - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + # assert that no rotation was performed since rotator_func + # does not do anything + assert os.path.exists(source) is True + assert os.path.exists(dest) is False def test_stop(self, tmp_path): """Test if stop() method of writer is called.""" - with self._get_instance(tmp_path) as logger_instance: - logger_instance._writer = logger_instance._get_new_writer( - tmp_path / "file.ASC" - ) - + with self._get_instance(tmp_path / "file.ASC") as logger_instance: # replace stop method of writer with Mock original_stop = logger_instance.writer.stop mock_stop = Mock() @@ -146,44 +148,38 @@ def test_stop(self, tmp_path): original_stop() def test_on_message_received(self, tmp_path): - logger_instance = self._get_instance(tmp_path) + with self._get_instance(tmp_path / "file.ASC") as logger_instance: + # Test without rollover + should_rollover = Mock(return_value=False) + do_rollover = Mock() + writers_on_message_received = Mock() - logger_instance._writer = logger_instance._get_new_writer(tmp_path / "file.ASC") + logger_instance.should_rollover = should_rollover + logger_instance.do_rollover = do_rollover + logger_instance.writer.on_message_received = writers_on_message_received - # Test without rollover - should_rollover = Mock(return_value=False) - do_rollover = Mock() - writers_on_message_received = Mock() - - logger_instance.should_rollover = should_rollover - logger_instance.do_rollover = do_rollover - logger_instance.writer.on_message_received = writers_on_message_received - - msg = generate_message(0x123) - logger_instance.on_message_received(msg) - - should_rollover.assert_called_with(msg) - do_rollover.assert_not_called() - writers_on_message_received.assert_called_with(msg) + msg = generate_message(0x123) + logger_instance.on_message_received(msg) - # Test with rollover - should_rollover = Mock(return_value=True) - do_rollover = Mock() - writers_on_message_received = Mock() + should_rollover.assert_called_with(msg) + do_rollover.assert_not_called() + writers_on_message_received.assert_called_with(msg) - logger_instance.should_rollover = should_rollover - logger_instance.do_rollover = do_rollover - logger_instance.writer.on_message_received = writers_on_message_received + # Test with rollover + should_rollover = Mock(return_value=True) + do_rollover = Mock() + writers_on_message_received = Mock() - msg = generate_message(0x123) - logger_instance.on_message_received(msg) + logger_instance.should_rollover = should_rollover + logger_instance.do_rollover = do_rollover + logger_instance.writer.on_message_received = writers_on_message_received - should_rollover.assert_called_with(msg) - do_rollover.assert_called() - writers_on_message_received.assert_called_with(msg) + msg = generate_message(0x123) + logger_instance.on_message_received(msg) - # stop writer to enable cleanup of temp_dir - logger_instance.stop() + should_rollover.assert_called_with(msg) + do_rollover.assert_called() + writers_on_message_received.assert_called_with(msg) class TestSizedRotatingLogger: @@ -202,54 +198,48 @@ def test_create_instance(self, tmp_path): base_filename = "mylogfile.ASC" max_bytes = 512 - logger_instance = can.SizedRotatingLogger( + with can.SizedRotatingLogger( base_filename=tmp_path / base_filename, max_bytes=max_bytes - ) - assert Path(logger_instance.base_filename).name == base_filename - assert logger_instance.max_bytes == max_bytes - assert logger_instance.rollover_count == 0 - assert isinstance(logger_instance.writer, can.ASCWriter) - - logger_instance.stop() + ) as logger_instance: + assert Path(logger_instance.base_filename).name == base_filename + assert logger_instance.max_bytes == max_bytes + assert logger_instance.rollover_count == 0 + assert isinstance(logger_instance.writer, can.ASCWriter) def test_should_rollover(self, tmp_path): base_filename = "mylogfile.ASC" max_bytes = 512 - logger_instance = can.SizedRotatingLogger( + with can.SizedRotatingLogger( base_filename=tmp_path / base_filename, max_bytes=max_bytes - ) - msg = generate_message(0x123) - do_rollover = Mock() - logger_instance.do_rollover = do_rollover - - logger_instance.writer.file.tell = Mock(return_value=511) - assert logger_instance.should_rollover(msg) is False - logger_instance.on_message_received(msg) - do_rollover.assert_not_called() + ) as logger_instance: + msg = generate_message(0x123) + do_rollover = Mock() + logger_instance.do_rollover = do_rollover - logger_instance.writer.file.tell = Mock(return_value=512) - assert logger_instance.should_rollover(msg) is True - logger_instance.on_message_received(msg) - do_rollover.assert_called() + logger_instance.writer.file.tell = Mock(return_value=511) + assert logger_instance.should_rollover(msg) is False + logger_instance.on_message_received(msg) + do_rollover.assert_not_called() - logger_instance.stop() + logger_instance.writer.file.tell = Mock(return_value=512) + assert logger_instance.should_rollover(msg) is True + logger_instance.on_message_received(msg) + do_rollover.assert_called() def test_logfile_size(self, tmp_path): base_filename = "mylogfile.ASC" max_bytes = 1024 msg = generate_message(0x123) - logger_instance = can.SizedRotatingLogger( + with can.SizedRotatingLogger( base_filename=tmp_path / base_filename, max_bytes=max_bytes - ) - for _ in range(128): - logger_instance.on_message_received(msg) - - for file_path in os.listdir(tmp_path): - assert os.path.getsize(tmp_path / file_path) <= 1100 + ) as logger_instance: + for _ in range(128): + logger_instance.on_message_received(msg) - logger_instance.stop() + for file_path in os.listdir(tmp_path): + assert os.path.getsize(tmp_path / file_path) <= 1100 def test_logfile_size_context_manager(self, tmp_path): base_filename = "mylogfile.ASC" From 4e397684d7ba4851049d5e20a45ad417428b02fc Mon Sep 17 00:00:00 2001 From: tttech-ferdigg <156343150+tttech-ferdigg@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:59:01 +0100 Subject: [PATCH 1097/1235] Enable echo frames in PCAN driver if receive_own_messages is set (#1723) * Enable echo frames in PCAN driver if receive_own_messages is set * Appease the linter * Set message direction to TX if the received message is an echo frame --- can/interfaces/pcan/pcan.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 0fdca51bb..b1b089690 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -26,6 +26,7 @@ FEATURE_FD_CAPABLE, IS_LINUX, IS_WINDOWS, + PCAN_ALLOW_ECHO_FRAMES, PCAN_ALLOW_ERROR_FRAMES, PCAN_API_VERSION, PCAN_ATTACHED_CHANNELS, @@ -47,6 +48,7 @@ PCAN_LANBUS1, PCAN_LISTEN_ONLY, PCAN_MESSAGE_BRS, + PCAN_MESSAGE_ECHO, PCAN_MESSAGE_ERRFRAME, PCAN_MESSAGE_ESI, PCAN_MESSAGE_EXTENDED, @@ -120,6 +122,7 @@ def __init__( state: BusState = BusState.ACTIVE, timing: Optional[Union[BitTiming, BitTimingFd]] = None, bitrate: int = 500000, + receive_own_messages: bool = False, **kwargs: Any, ): """A PCAN USB interface to CAN. @@ -162,6 +165,9 @@ def __init__( Default is 500 kbit/s. Ignored if using CanFD. + :param receive_own_messages: + Enable self-reception of sent messages. + :param bool fd: Should the Bus be initialized in CAN-FD mode. @@ -316,6 +322,14 @@ def __init__( "Ignoring error. PCAN_ALLOW_ERROR_FRAMES is still unsupported by OSX Library PCANUSB v0.11.2" ) + if receive_own_messages: + result = self.m_objPCANBasic.SetValue( + self.m_PcanHandle, PCAN_ALLOW_ECHO_FRAMES, PCAN_PARAMETER_ON + ) + + if result != PCAN_ERROR_OK: + raise PcanCanInitializationError(self._get_formatted_error(result)) + if kwargs.get("auto_reset", False): result = self.m_objPCANBasic.SetValue( self.m_PcanHandle, PCAN_BUSOFF_AUTORESET, PCAN_PARAMETER_ON @@ -548,6 +562,7 @@ def _recv_internal( is_extended_id = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_EXTENDED.value) is_remote_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_RTR.value) is_fd = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_FD.value) + is_rx = not bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ECHO.value) bitrate_switch = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_BRS.value) error_state_indicator = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ESI.value) is_error_frame = bool(pcan_msg.MSGTYPE & PCAN_MESSAGE_ERRFRAME.value) @@ -575,6 +590,7 @@ def _recv_internal( dlc=dlc, data=pcan_msg.DATA[:dlc], is_fd=is_fd, + is_rx=is_rx, bitrate_switch=bitrate_switch, error_state_indicator=error_state_indicator, ) From a2ddb5167ed6be8a58fe4f168f8fc339b41a6e86 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:59:50 +0100 Subject: [PATCH 1098/1235] Use ctypes.util.find_library to find vxlapi.dll (#1731) --- can/interfaces/vector/canlib.py | 2 +- can/interfaces/vector/xldriver.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 797539c88..a6d8fb84b 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -51,7 +51,7 @@ xldriver: Optional[ModuleType] = None try: from . import xldriver -except Exception as exc: +except FileNotFoundError as exc: LOG.warning("Could not import vxlapi: %s", exc) WaitForSingleObject: Optional[Callable[[int, int], int]] diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 2af90c728..faed23b36 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -8,6 +8,7 @@ import ctypes import logging import platform +from ctypes.util import find_library from . import xlclass from .exceptions import VectorInitializationError, VectorOperationError @@ -16,8 +17,10 @@ # Load Windows DLL DLL_NAME = "vxlapi64" if platform.architecture()[0] == "64bit" else "vxlapi" -_xlapi_dll = ctypes.windll.LoadLibrary(DLL_NAME) - +if dll_path := find_library(DLL_NAME): + _xlapi_dll = ctypes.windll.LoadLibrary(dll_path) +else: + raise FileNotFoundError(f"Vector XL library not found: {DLL_NAME}") # ctypes wrapping for API functions xlGetErrorString = _xlapi_dll.xlGetErrorString From 7e2950414c765cb6a6cb399a6142de045b3b205a Mon Sep 17 00:00:00 2001 From: Lukas Magel Date: Sun, 21 Jan 2024 12:52:18 +0100 Subject: [PATCH 1099/1235] Fix ASCWriter millisecond handling (#1734) * Add _read_ prefix to ASC reader tests * Update the ASCWriter to use a 24h date format * Add test to check handling of ASCWriter date and time * Reimplement datetime formatting in ASCWriter * Use Path read methods for ASCWriter test * Appease ruff linter --- can/io/asc.py | 32 +++++++------- test/data/single_frame_us_locale.asc | 7 +++ test/logformats_test.py | 66 ++++++++++++++++++++++------ 3 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 test/data/single_frame_us_locale.asc diff --git a/can/io/asc.py b/can/io/asc.py index 07243cd5b..ceffaf5cb 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -7,7 +7,6 @@ """ import logging import re -import time from datetime import datetime from typing import Any, Dict, Final, Generator, List, Optional, TextIO, Union @@ -340,8 +339,7 @@ class ASCWriter(TextIOMessageWriter): "{bit_timing_conf_ext_data:>8}", ] ) - FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" - FORMAT_DATE = "%a %b %d %I:%M:%S.{} %p %Y" + FORMAT_DATE = "%a %b %d %H:%M:%S.{} %Y" FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" def __init__( @@ -367,12 +365,8 @@ def __init__( self.channel = channel # write start of file header - now = datetime.now().strftime(self.FORMAT_START_OF_FILE_DATE) - # Note: CANoe requires that the microsecond field only have 3 digits - idx = now.index(".") # Find the index in the string of the decimal - # Keep decimal and first three ms digits (4), remove remaining digits - now = now.replace(now[idx + 4 : now[idx:].index(" ") + idx], "") - self.file.write(f"date {now}\n") + start_time = self._format_header_datetime(datetime.now()) + self.file.write(f"date {start_time}\n") self.file.write("base hex timestamps absolute\n") self.file.write("internal events logged\n") @@ -381,6 +375,15 @@ def __init__( self.last_timestamp = 0.0 self.started = 0.0 + def _format_header_datetime(self, dt: datetime) -> str: + # Note: CANoe requires that the microsecond field only have 3 digits + # Since Python strftime only supports microsecond formatters, we must + # manually include the millisecond portion before passing the format + # to strftime + msec = dt.microsecond // 1000 % 1000 + format_w_msec = self.FORMAT_DATE.format(msec) + return dt.strftime(format_w_msec) + def stop(self) -> None: # This is guaranteed to not be None since we raise ValueError in __init__ if not self.file.closed: @@ -400,12 +403,11 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: # this is the case for the very first message: if not self.header_written: - self.last_timestamp = timestamp or 0.0 - self.started = self.last_timestamp - mlsec = repr(self.last_timestamp).split(".")[1][:3] - formatted_date = time.strftime( - self.FORMAT_DATE.format(mlsec), time.localtime(self.last_timestamp) - ) + self.started = self.last_timestamp = timestamp or 0.0 + + start_time = datetime.fromtimestamp(self.last_timestamp) + formatted_date = self._format_header_datetime(start_time) + self.file.write(f"Begin Triggerblock {formatted_date}\n") self.header_written = True self.log_event("Start of measurement") # caution: this is a recursive call! diff --git a/test/data/single_frame_us_locale.asc b/test/data/single_frame_us_locale.asc new file mode 100644 index 000000000..f6bfcc3db --- /dev/null +++ b/test/data/single_frame_us_locale.asc @@ -0,0 +1,7 @@ +date Sat Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +Begin Triggerblock Sat Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 0.000000 1 123x Rx d 1 68 +End TriggerBlock diff --git a/test/logformats_test.py b/test/logformats_test.py index 8694fefdc..a7e75d7c8 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -11,19 +11,22 @@ TODO: correctly set preserves_channel and adds_default_channel """ +import locale import logging import os import tempfile import unittest from abc import ABCMeta, abstractmethod +from contextlib import contextmanager from datetime import datetime from itertools import zip_longest +from pathlib import Path +from unittest.mock import patch from parameterized import parameterized import can from can.io import blf - from .data.example_data import ( TEST_COMMENTS, TEST_MESSAGES_BASE, @@ -42,6 +45,14 @@ asammdf = None +@contextmanager +def override_locale(category: int, locale_str: str) -> None: + prev_locale = locale.getlocale(category) + locale.setlocale(category, locale_str) + yield + locale.setlocale(category, prev_locale) + + class ReaderWriterExtensionTest(unittest.TestCase): def _get_suffix_case_variants(self, suffix): return [ @@ -403,12 +414,16 @@ def _setup_instance(self): adds_default_channel=0, ) + def _get_logfile_location(self, filename: str) -> Path: + my_dir = Path(__file__).parent + return my_dir / "data" / filename + def _read_log_file(self, filename, **kwargs): - logfile = os.path.join(os.path.dirname(__file__), "data", filename) + logfile = self._get_logfile_location(filename) with can.ASCReader(logfile, **kwargs) as reader: return list(reader) - def test_absolute_time(self): + def test_read_absolute_time(self): time_from_file = "Sat Sep 30 10:06:13.191 PM 2017" start_time = datetime.strptime( time_from_file, self.FORMAT_START_OF_FILE_DATE @@ -436,7 +451,7 @@ def test_absolute_time(self): actual = self._read_log_file("test_CanMessage.asc", relative_timestamp=False) self.assertMessagesEqual(actual, expected_messages) - def test_can_message(self): + def test_read_can_message(self): expected_messages = [ can.Message( timestamp=2.5010, @@ -459,7 +474,7 @@ def test_can_message(self): actual = self._read_log_file("test_CanMessage.asc") self.assertMessagesEqual(actual, expected_messages) - def test_can_remote_message(self): + def test_read_can_remote_message(self): expected_messages = [ can.Message( timestamp=2.510001, @@ -488,7 +503,7 @@ def test_can_remote_message(self): actual = self._read_log_file("test_CanRemoteMessage.asc") self.assertMessagesEqual(actual, expected_messages) - def test_can_fd_remote_message(self): + def test_read_can_fd_remote_message(self): expected_messages = [ can.Message( timestamp=30.300981, @@ -504,7 +519,7 @@ def test_can_fd_remote_message(self): actual = self._read_log_file("test_CanFdRemoteMessage.asc") self.assertMessagesEqual(actual, expected_messages) - def test_can_fd_message(self): + def test_read_can_fd_message(self): expected_messages = [ can.Message( timestamp=30.005021, @@ -541,7 +556,7 @@ def test_can_fd_message(self): actual = self._read_log_file("test_CanFdMessage.asc") self.assertMessagesEqual(actual, expected_messages) - def test_can_fd_message_64(self): + def test_read_can_fd_message_64(self): expected_messages = [ can.Message( timestamp=30.506898, @@ -566,7 +581,7 @@ def test_can_fd_message_64(self): actual = self._read_log_file("test_CanFdMessage64.asc") self.assertMessagesEqual(actual, expected_messages) - def test_can_and_canfd_error_frames(self): + def test_read_can_and_canfd_error_frames(self): expected_messages = [ can.Message(timestamp=2.501000, channel=0, is_error_frame=True), can.Message(timestamp=3.501000, channel=0, is_error_frame=True), @@ -582,16 +597,16 @@ def test_can_and_canfd_error_frames(self): actual = self._read_log_file("test_CanErrorFrames.asc") self.assertMessagesEqual(actual, expected_messages) - def test_ignore_comments(self): + def test_read_ignore_comments(self): _msg_list = self._read_log_file("logfile.asc") - def test_no_triggerblock(self): + def test_read_no_triggerblock(self): _msg_list = self._read_log_file("issue_1256.asc") - def test_can_dlc_greater_than_8(self): + def test_read_can_dlc_greater_than_8(self): _msg_list = self._read_log_file("issue_1299.asc") - def test_error_frame_channel(self): + def test_read_error_frame_channel(self): # gh-issue 1578 err_frame = can.Message(is_error_frame=True, channel=4) @@ -611,6 +626,31 @@ def test_error_frame_channel(self): finally: os.unlink(temp_file.name) + def test_write_millisecond_handling(self): + now = datetime( + year=2017, month=9, day=30, hour=15, minute=6, second=13, microsecond=191456 + ) + + # We temporarily set the locale to C to ensure test reproducibility + with override_locale(category=locale.LC_TIME, locale_str="C"): + # We mock datetime.now during ASCWriter __init__ for reproducibility + # Unfortunately, now() is a readonly attribute, so we mock datetime + with patch("can.io.asc.datetime") as mock_datetime: + mock_datetime.now.return_value = now + writer = can.ASCWriter(self.test_file_name) + + msg = can.Message( + timestamp=now.timestamp(), arbitration_id=0x123, data=b"h" + ) + writer.on_message_received(msg) + + writer.stop() + + actual_file = Path(self.test_file_name) + expected_file = self._get_logfile_location("single_frame_us_locale.asc") + + self.assertEqual(expected_file.read_text(), actual_file.read_text()) + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From 3b6f155148a376d3f28b7e11e5fdb9716b0810a2 Mon Sep 17 00:00:00 2001 From: cfsok <33849525+cfsok@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:51:30 +0900 Subject: [PATCH 1100/1235] Add "...Embedded Systems" and "...CAN" PyPI classifiers (#1735) --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index d3e496d28..ad59469da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Embedded Systems", + "Topic :: Software Development :: Embedded Systems :: Controller Area Network (CAN)", "Topic :: System :: Hardware :: Hardware Drivers", "Topic :: System :: Logging", "Topic :: System :: Monitoring", From d40915cae080716293b7293c9261adfe1fdc2936 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:04:31 +0100 Subject: [PATCH 1101/1235] Vector: Connect to CANFD bus without specifying timings (#1716) * refactor check of timings. Allow to connect to FD bus without setting new timings * add test for bus creation with multiple channels * add test for _iterate_channel_index --- can/interfaces/vector/canlib.py | 306 +++++++++++++++++--------------- test/test_vector.py | 32 ++++ 2 files changed, 194 insertions(+), 144 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index a6d8fb84b..3a25c99d8 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -15,6 +15,7 @@ Any, Callable, Dict, + Iterator, List, NamedTuple, Optional, @@ -205,7 +206,10 @@ def __init__( self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20 for channel in self.channels: - if (_channel_index := kwargs.get("channel_index", None)) is not None: + if ( + len(self.channels) == 1 + and (_channel_index := kwargs.get("channel_index", None)) is not None + ): # VectorBus._detect_available_configs() might return multiple # devices with the same serial number, e.g. if a VN8900 is connected via both USB and Ethernet # at the same time. If the VectorBus is instantiated with a config, that was returned from @@ -250,42 +254,62 @@ def __init__( self.permission_mask = permission_mask.value LOG.debug( - "Open Port: PortHandle: %d, PermissionMask: 0x%X", + "Open Port: PortHandle: %d, ChannelMask: 0x%X, PermissionMask: 0x%X", self.port_handle.value, - permission_mask.value, + self.mask, + self.permission_mask, ) + assert_timing = (bitrate or timing) and not self.__testing + # set CAN settings - for channel in self.channels: - if isinstance(timing, BitTiming): - timing = check_or_adjust_timing_clock(timing, [16_000_000, 8_000_000]) - self._set_bit_timing( - channel=channel, - timing=timing, + if isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, [16_000_000, 8_000_000]) + self._set_bit_timing(channel_mask=self.mask, timing=timing) + if assert_timing: + self._check_can_settings( + channel_mask=self.mask, + bitrate=timing.bitrate, + sample_point=timing.sample_point, ) - elif isinstance(timing, BitTimingFd): - timing = check_or_adjust_timing_clock(timing, [80_000_000]) - self._set_bit_timing_fd( - channel=channel, - timing=timing, + elif isinstance(timing, BitTimingFd): + timing = check_or_adjust_timing_clock(timing, [80_000_000]) + self._set_bit_timing_fd(channel_mask=self.mask, timing=timing) + if assert_timing: + self._check_can_settings( + channel_mask=self.mask, + bitrate=timing.nom_bitrate, + sample_point=timing.nom_sample_point, + fd=True, + data_bitrate=timing.data_bitrate, + data_sample_point=timing.data_sample_point, ) - elif fd: - self._set_bit_timing_fd( - channel=channel, - timing=BitTimingFd.from_bitrate_and_segments( - f_clock=80_000_000, - nom_bitrate=bitrate or 500_000, - nom_tseg1=tseg1_abr, - nom_tseg2=tseg2_abr, - nom_sjw=sjw_abr, - data_bitrate=data_bitrate or bitrate or 500_000, - data_tseg1=tseg1_dbr, - data_tseg2=tseg2_dbr, - data_sjw=sjw_dbr, - ), + elif fd: + timing = BitTimingFd.from_bitrate_and_segments( + f_clock=80_000_000, + nom_bitrate=bitrate or 500_000, + nom_tseg1=tseg1_abr, + nom_tseg2=tseg2_abr, + nom_sjw=sjw_abr, + data_bitrate=data_bitrate or bitrate or 500_000, + data_tseg1=tseg1_dbr, + data_tseg2=tseg2_dbr, + data_sjw=sjw_dbr, + ) + self._set_bit_timing_fd(channel_mask=self.mask, timing=timing) + if assert_timing: + self._check_can_settings( + channel_mask=self.mask, + bitrate=timing.nom_bitrate, + sample_point=timing.nom_sample_point, + fd=True, + data_bitrate=timing.data_bitrate, + data_sample_point=timing.data_sample_point, ) - elif bitrate: - self._set_bitrate(channel=channel, bitrate=bitrate) + elif bitrate: + self._set_bitrate(channel_mask=self.mask, bitrate=bitrate) + if assert_timing: + self._check_can_settings(channel_mask=self.mask, bitrate=bitrate) # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 @@ -405,45 +429,41 @@ def _find_global_channel_idx( def _has_init_access(self, channel: int) -> bool: return bool(self.permission_mask & self.channel_masks[channel]) - def _read_bus_params(self, channel: int) -> "VectorBusParams": - channel_mask = self.channel_masks[channel] - - vcc_list = get_channel_configs() + def _read_bus_params( + self, channel_index: int, vcc_list: List["VectorChannelConfig"] + ) -> "VectorBusParams": for vcc in vcc_list: - if vcc.channel_mask == channel_mask: + if vcc.channel_index == channel_index: bus_params = vcc.bus_params if bus_params is None: # for CAN channels, this should never be `None` raise ValueError("Invalid bus parameters.") return bus_params + channel = self.index_to_channel[channel_index] raise CanInitializationError( f"Channel configuration for channel {channel} not found." ) - def _set_bitrate(self, channel: int, bitrate: int) -> None: - # set parameters if channel has init access - if self._has_init_access(channel): + def _set_bitrate(self, channel_mask: int, bitrate: int) -> None: + # set parameters for channels with init access + channel_mask = channel_mask & self.permission_mask + if channel_mask: self.xldriver.xlCanSetChannelBitrate( self.port_handle, - self.channel_masks[channel], + channel_mask, bitrate, ) LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate) - if not self.__testing: - self._check_can_settings( - channel=channel, - bitrate=bitrate, - ) - - def _set_bit_timing(self, channel: int, timing: BitTiming) -> None: - # set parameters if channel has init access - if self._has_init_access(channel): + def _set_bit_timing(self, channel_mask: int, timing: BitTiming) -> None: + # set parameters for channels with init access + channel_mask = channel_mask & self.permission_mask + if channel_mask: if timing.f_clock == 8_000_000: self.xldriver.xlCanSetChannelParamsC200( self.port_handle, - self.channel_masks[channel], + channel_mask, timing.btr0, timing.btr1, ) @@ -461,7 +481,7 @@ def _set_bit_timing(self, channel: int, timing: BitTiming) -> None: chip_params.sam = timing.nof_samples self.xldriver.xlCanSetChannelParams( self.port_handle, - self.channel_masks[channel], + channel_mask, chip_params, ) LOG.info( @@ -476,20 +496,14 @@ def _set_bit_timing(self, channel: int, timing: BitTiming) -> None: f"timing.f_clock must be 8_000_000 or 16_000_000 (is {timing.f_clock})" ) - if not self.__testing: - self._check_can_settings( - channel=channel, - bitrate=timing.bitrate, - sample_point=timing.sample_point, - ) - def _set_bit_timing_fd( self, - channel: int, + channel_mask: int, timing: BitTimingFd, ) -> None: - # set parameters if channel has init access - if self._has_init_access(channel): + # set parameters for channels with init access + channel_mask = channel_mask & self.permission_mask + if channel_mask: canfd_conf = xlclass.XLcanFdConf() canfd_conf.arbitrationBitRate = timing.nom_bitrate canfd_conf.sjwAbr = timing.nom_sjw @@ -500,7 +514,7 @@ def _set_bit_timing_fd( canfd_conf.tseg1Dbr = timing.data_tseg1 canfd_conf.tseg2Dbr = timing.data_tseg2 self.xldriver.xlCanFdSetConfiguration( - self.port_handle, self.channel_masks[channel], canfd_conf + self.port_handle, channel_mask, canfd_conf ) LOG.info( "xlCanFdSetConfiguration.: ABaudr.=%u, DBaudr.=%u", @@ -520,19 +534,9 @@ def _set_bit_timing_fd( canfd_conf.tseg2Dbr, ) - if not self.__testing: - self._check_can_settings( - channel=channel, - bitrate=timing.nom_bitrate, - sample_point=timing.nom_sample_point, - fd=True, - data_bitrate=timing.data_bitrate, - data_sample_point=timing.data_sample_point, - ) - def _check_can_settings( self, - channel: int, + channel_mask: int, bitrate: int, sample_point: Optional[float] = None, fd: bool = False, @@ -540,83 +544,90 @@ def _check_can_settings( data_sample_point: Optional[float] = None, ) -> None: """Compare requested CAN settings to active settings in driver.""" - bus_params = self._read_bus_params(channel) - # use canfd even if fd==False, bus_params.can and bus_params.canfd are a C union - bus_params_data = bus_params.canfd - settings_acceptable = True - - # check bus type - settings_acceptable &= ( - bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN - ) - - # check CAN operation mode - # skip the check if can_op_mode is 0 - # as it happens for cancaseXL, VN7600 and sometimes on other hardware (VN1640) - if bus_params_data.can_op_mode: - if fd: - settings_acceptable &= bool( - bus_params_data.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD - ) - else: - settings_acceptable &= bool( - bus_params_data.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 - ) - - # check bitrates - if bitrate: - settings_acceptable &= ( - abs(bus_params_data.bitrate - bitrate) < bitrate / 256 - ) - if fd and data_bitrate: - settings_acceptable &= ( - abs(bus_params_data.data_bitrate - data_bitrate) < data_bitrate / 256 + vcc_list = get_channel_configs() + for channel_index in _iterate_channel_index(channel_mask): + bus_params = self._read_bus_params( + channel_index=channel_index, vcc_list=vcc_list ) + # use bus_params.canfd even if fd==False, bus_params.can and bus_params.canfd are a C union + bus_params_data = bus_params.canfd + settings_acceptable = True - # check sample points - if sample_point: - nom_sample_point_act = ( - 100 - * (1 + bus_params_data.tseg1_abr) - / (1 + bus_params_data.tseg1_abr + bus_params_data.tseg2_abr) - ) + # check bus type settings_acceptable &= ( - abs(nom_sample_point_act - sample_point) < 2.0 # 2 percent tolerance - ) - if fd and data_sample_point: - data_sample_point_act = ( - 100 - * (1 + bus_params_data.tseg1_dbr) - / (1 + bus_params_data.tseg1_dbr + bus_params_data.tseg2_dbr) - ) - settings_acceptable &= ( - abs(data_sample_point_act - data_sample_point) - < 2.0 # 2 percent tolerance + bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN ) - if not settings_acceptable: - # The error message depends on the currently active CAN settings. - # If the active operation mode is CAN FD, show the active CAN FD timings, - # otherwise show CAN 2.0 timings. - if bool( - bus_params_data.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD - ): - active_settings = bus_params.canfd._asdict() - active_settings["can_op_mode"] = "CAN FD" - else: - active_settings = bus_params.can._asdict() - active_settings["can_op_mode"] = "CAN 2.0" - settings_string = ", ".join( - [f"{key}: {val}" for key, val in active_settings.items()] - ) - raise CanInitializationError( - f"The requested settings could not be set for channel {channel}. " - f"Another application might have set incompatible settings. " - f"These are the currently active settings: {settings_string}." - ) + # check CAN operation mode + # skip the check if can_op_mode is 0 + # as it happens for cancaseXL, VN7600 and sometimes on other hardware (VN1640) + if bus_params_data.can_op_mode: + if fd: + settings_acceptable &= bool( + bus_params_data.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD + ) + else: + settings_acceptable &= bool( + bus_params_data.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 + ) + + # check bitrates + if bitrate: + settings_acceptable &= ( + abs(bus_params_data.bitrate - bitrate) < bitrate / 256 + ) + if fd and data_bitrate: + settings_acceptable &= ( + abs(bus_params_data.data_bitrate - data_bitrate) + < data_bitrate / 256 + ) + + # check sample points + if sample_point: + nom_sample_point_act = ( + 100 + * (1 + bus_params_data.tseg1_abr) + / (1 + bus_params_data.tseg1_abr + bus_params_data.tseg2_abr) + ) + settings_acceptable &= ( + abs(nom_sample_point_act - sample_point) + < 2.0 # 2 percent tolerance + ) + if fd and data_sample_point: + data_sample_point_act = ( + 100 + * (1 + bus_params_data.tseg1_dbr) + / (1 + bus_params_data.tseg1_dbr + bus_params_data.tseg2_dbr) + ) + settings_acceptable &= ( + abs(data_sample_point_act - data_sample_point) + < 2.0 # 2 percent tolerance + ) + + if not settings_acceptable: + # The error message depends on the currently active CAN settings. + # If the active operation mode is CAN FD, show the active CAN FD timings, + # otherwise show CAN 2.0 timings. + if bool( + bus_params_data.can_op_mode + & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD + ): + active_settings = bus_params.canfd._asdict() + active_settings["can_op_mode"] = "CAN FD" + else: + active_settings = bus_params.can._asdict() + active_settings["can_op_mode"] = "CAN 2.0" + settings_string = ", ".join( + [f"{key}: {val}" for key, val in active_settings.items()] + ) + channel = self.index_to_channel[channel_index] + raise CanInitializationError( + f"The requested settings could not be set for channel {channel}. " + f"Another application might have set incompatible settings. " + f"These are the currently active settings: {settings_string}." + ) def _apply_filters(self, filters: Optional[CanFilters]) -> None: if filters: @@ -1239,3 +1250,10 @@ def _hw_type(hw_type: int) -> Union[int, xldefine.XL_HardwareType]: except ValueError: LOG.warning(f'Unknown XL_HardwareType value "{hw_type}"') return hw_type + + +def _iterate_channel_index(channel_mask: int) -> Iterator[int]: + """Iterate over channel indexes in channel mask.""" + for channel_index, bit in enumerate(reversed(bin(channel_mask)[2:])): + if bit == "1": + yield channel_index diff --git a/test/test_vector.py b/test/test_vector.py index 16a79c6d1..99756c41d 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -134,6 +134,32 @@ def test_bus_creation_channel_index() -> None: bus.shutdown() +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_bus_creation_multiple_channels() -> None: + bus = can.Bus( + channel="0, 1", + bitrate=1_000_000, + serial=_find_virtual_can_serial(), + interface="vector", + ) + assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 + assert len(bus.channels) == 2 + assert bus.mask == 3 + + xl_channel_config_0 = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=0 + ) + assert xl_channel_config_0.busParams.data.can.bitRate == 1_000_000 + + xl_channel_config_1 = _find_xl_channel_config( + serial=_find_virtual_can_serial(), channel=1 + ) + assert xl_channel_config_1.busParams.data.can.bitRate == 1_000_000 + + bus.shutdown() + + def test_bus_creation_bitrate_mocked(mock_xldriver) -> None: bus = can.Bus(channel=0, interface="vector", bitrate=200_000, _testing=True) assert isinstance(bus, canlib.VectorBus) @@ -836,6 +862,12 @@ def test_vector_subtype_error_from_generic() -> None: raise specific +def test_iterate_channel_index() -> None: + channel_mask = 0x23 # 100011 + channels = list(canlib._iterate_channel_index(channel_mask)) + assert channels == [0, 1, 5] + + @pytest.mark.skipif( sys.byteorder != "little", reason="Test relies on little endian data." ) From ffb14a3915a3b8e72742f4465888d0051e6ca07f Mon Sep 17 00:00:00 2001 From: Atabey <55498083+1atabey1@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:36:44 +0100 Subject: [PATCH 1102/1235] Add trc 1.3 read support (#1753) --- can/io/trc.py | 30 ++++++++++++++++++++++++++++ test/data/test_CanMessage_V1_3.trc | 32 ++++++++++++++++++++++++++++++ test/logformats_test.py | 1 + 3 files changed, 63 insertions(+) create mode 100644 test/data/test_CanMessage_V1_3.trc diff --git a/can/io/trc.py b/can/io/trc.py index a498da992..f568f93a5 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -76,6 +76,8 @@ def _extract_header(self): file_version = line.split("=")[1] if file_version == "1.1": self.file_version = TRCFileVersion.V1_1 + elif file_version == "1.3": + self.file_version = TRCFileVersion.V1_3 elif file_version == "2.0": self.file_version = TRCFileVersion.V2_0 elif file_version == "2.1": @@ -121,6 +123,8 @@ def _extract_header(self): self._parse_cols = self._parse_msg_v1_0 elif self.file_version == TRCFileVersion.V1_1: self._parse_cols = self._parse_cols_v1_1 + elif self.file_version == TRCFileVersion.V1_3: + self._parse_cols = self._parse_cols_v1_3 elif self.file_version in [TRCFileVersion.V2_0, TRCFileVersion.V2_1]: self._parse_cols = self._parse_cols_v2_x else: @@ -161,6 +165,24 @@ def _parse_msg_v1_1(self, cols: List[str]) -> Optional[Message]: msg.is_rx = cols[2] == "Rx" return msg + def _parse_msg_v1_3(self, cols: List[str]) -> Optional[Message]: + arbit_id = cols[4] + + msg = Message() + if isinstance(self.start_time, datetime): + msg.timestamp = ( + self.start_time + timedelta(milliseconds=float(cols[1])) + ).timestamp() + else: + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(arbit_id, 16) + msg.is_extended_id = len(arbit_id) > 4 + msg.channel = int(cols[2]) + msg.dlc = int(cols[6]) + msg.data = bytearray([int(cols[i + 7], 16) for i in range(msg.dlc)]) + msg.is_rx = cols[3] == "Rx" + return msg + def _parse_msg_v2_x(self, cols: List[str]) -> Optional[Message]: type_ = cols[self.columns["T"]] bus = self.columns.get("B", None) @@ -203,6 +225,14 @@ def _parse_cols_v1_1(self, cols: List[str]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None + def _parse_cols_v1_3(self, cols: List[str]) -> Optional[Message]: + dtype = cols[3] + if dtype in ("Tx", "Rx"): + return self._parse_msg_v1_3(cols) + else: + logger.info("TRCReader: Unsupported type '%s'", dtype) + return None + def _parse_cols_v2_x(self, cols: List[str]) -> Optional[Message]: dtype = cols[self.columns["T"]] if dtype in ["DT", "FD", "FB"]: diff --git a/test/data/test_CanMessage_V1_3.trc b/test/data/test_CanMessage_V1_3.trc new file mode 100644 index 000000000..5b0bf060a --- /dev/null +++ b/test/data/test_CanMessage_V1_3.trc @@ -0,0 +1,32 @@ +;$FILEVERSION=1.3 +;$STARTTIME=44548.6028595139 +; +; C:\test.trc +; Start time: 18.12.2021 14:28:07.062.0 +; Generated by PCAN-Explorer v5.4.0 +;------------------------------------------------------------------------------- +; Bus Name Connection Protocol Bit rate +; 1 PCAN Untitled@pcan_usb CAN 500 kbit/s +; 2 PTCAN PCANLight_USB_16@pcan_usb CAN +;------------------------------------------------------------------------------- +; Message Number +; | Time Offset (ms) +; | | Bus +; | | | Type +; | | | | ID (hex) +; | | | | | Reserved +; | | | | | | Data Length Code +; | | | | | | | Data Bytes (hex) ... +; | | | | | | | | +; | | | | | | | | +;---+-- ------+------ +- --+-- ----+--- +- -+-- -+ -- -- -- -- -- -- -- + 1) 17535.4 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 2) 17700.3 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 3) 17873.8 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 4) 19295.4 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 + 5) 19500.6 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 + 6) 19705.2 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 + 7) 20592.7 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 8) 20798.6 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 9) 20956.0 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 10) 21097.1 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 diff --git a/test/logformats_test.py b/test/logformats_test.py index a7e75d7c8..71d392aa8 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -939,6 +939,7 @@ def test_can_message(self): [ ("V1_0", "test_CanMessage_V1_0_BUS1.trc", False), ("V1_1", "test_CanMessage_V1_1.trc", True), + ("V1_3", "test_CanMessage_V1_3.trc", True), ("V2_1", "test_CanMessage_V2_1.trc", True), ] ) From 71f54cc609db85a4468a94f5f74b1fc9427704e9 Mon Sep 17 00:00:00 2001 From: Jack Cook Date: Fri, 15 Mar 2024 13:34:51 -0500 Subject: [PATCH 1103/1235] Update Neousys available configs detection (#1744) The modification ensures that the Neousys interface's method '_detect_available_configs' function checks if NEOUSYS_CANLIB is None. If it's None, no configuration is returned, and if it's not, the usual configuration with the interface as 'neousys' and channel as 0 is returned. This provides an additional layer of error handling. --- can/interfaces/neousys/neousys.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index b7dd2117c..cb9d0174d 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -239,5 +239,8 @@ def shutdown(self): @staticmethod def _detect_available_configs(): - # There is only one channel - return [{"interface": "neousys", "channel": 0}] + if NEOUSYS_CANLIB is None: + return [] + else: + # There is only one channel + return [{"interface": "neousys", "channel": 0}] From 4a41409de8e1eefaa1aa003da7e4f84f018c6791 Mon Sep 17 00:00:00 2001 From: Duncan Bristow <154448426+dbristow-otc@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:42:00 -0600 Subject: [PATCH 1104/1235] Add ability to pass explicit dlc value to send() using the SYSTEC interface (#1756) * Add ability to pass explicit dlc value to SYSTEC interface when sending a message (closes #1755) * run linter --- can/interfaces/systec/structures.py | 7 +++++-- can/interfaces/systec/ucanbus.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index 9acc34c2c..c80f21d44 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -52,9 +52,12 @@ class CanMsg(Structure): ), # Receive time stamp in ms (for transmit messages no meaning) ] - def __init__(self, id_=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None): + def __init__( + self, id_=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None, dlc=None + ): data = [] if data is None else data - super().__init__(id_, frame_format, len(data), (BYTE * 8)(*data), 0) + dlc = len(data) if dlc is None else dlc + super().__init__(id_, frame_format, dlc, (BYTE * 8)(*data), 0) def __eq__(self, other): if not isinstance(other, CanMsg): diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index cb0dcc39e..3dff7fda5 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -207,6 +207,7 @@ def send(self, msg, timeout=None): | (MsgFrameFormat.MSG_FF_EXT if msg.is_extended_id else 0) | (MsgFrameFormat.MSG_FF_RTR if msg.is_remote_frame else 0), msg.data, + msg.dlc, ) self._ucan.write_can_msg(self.channel, [message]) except UcanException as exception: From 7dba4490c6c61385dc8cbe917e85492b4777b8f6 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Wed, 17 Apr 2024 09:37:03 -0400 Subject: [PATCH 1105/1235] Raising CanOperationError if the bus is not open on some methods call (#1765) --- can/interfaces/ics_neovi/neovi_bus.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 66e109c97..7863fcb65 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -8,6 +8,7 @@ * https://github.com/intrepidcs/python_ics """ +import functools import logging import os import tempfile @@ -129,6 +130,27 @@ class ICSOperationError(ICSApiError, CanOperationError): pass +def check_if_bus_open(func): + """ + Decorator that checks if the bus is open before executing the function. + + If the bus is not open, it raises a CanOperationError. + """ + + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + """ + Wrapper function that checks if the bus is open before executing the function. + + :raises CanOperationError: If the bus is not open. + """ + if self._is_shutdown: + raise CanOperationError("Cannot operate on a closed bus") + return func(self, *args, **kwargs) + + return wrapper + + class NeoViBus(BusABC): """ The CAN Bus implemented for the python_ics interface @@ -312,6 +334,7 @@ def _find_device(self, type_filter=None, serial=None): msg.append("found.") raise CanInitializationError(" ".join(msg)) + @check_if_bus_open def _process_msg_queue(self, timeout=0.1): try: messages, errors = ics.get_messages(self.dev, False, timeout) @@ -409,6 +432,7 @@ def _recv_internal(self, timeout=0.1): return None, False return msg, False + @check_if_bus_open def send(self, msg, timeout=0): """Transmit a message to the CAN bus. From 6e7a684132ec6040017014164ef49be3c8af0ba8 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:08:43 +0200 Subject: [PATCH 1106/1235] fix MacOS CI (#1772) --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 639465176..e70f0f8cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,14 @@ jobs: "pypy-3.8", "pypy-3.9", ] + # Python 3.9 is on macos-13 but not macos-latest (macos-14-arm64) + # https://github.com/actions/setup-python/issues/696#issuecomment-1637587760 + exclude: + - { python-version: "3.8", os: "macos-latest", experimental: false } + - { python-version: "3.9", os: "macos-latest", experimental: false } + include: + - { python-version: "3.8", os: "macos-13", experimental: false } + - { python-version: "3.9", os: "macos-13", experimental: false } # uncomment when python 3.13.0 alpha is available #include: # # Only test on a single configuration while there are just pre-releases From ed98a0febbb31628c94d1fea242cc0bd3943ed2c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:05:08 +0200 Subject: [PATCH 1107/1235] skip test_send_periodic_duration() in CI (#1773) --- test/back2back_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 8269a73a5..90cf8a9bf 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -273,7 +273,7 @@ def test_sub_second_timestamp_resolution(self): self.bus2.recv(0) self.bus2.recv(0) - @unittest.skipIf(IS_PYPY, "fails randomly when run on CI server") + @unittest.skipIf(IS_CI, "fails randomly when run on CI server") def test_send_periodic_duration(self): """ Verify that send_periodic only transmits for the specified duration. From 32c7640d25910aa3cf77e71aa2ac44eb78d68945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Mon, 29 Apr 2024 11:51:36 +0200 Subject: [PATCH 1108/1235] Invert default value logic for BusABC._is_shutdown. (#1774) The destructor of BusABC gives a warning when shutdown() was not previously called on the object after construction. However, if the construction fails (e.g. when the derived bus class constructor raises an exception), there is no way to call shutdown() on the unfinished object, and it is not necessary either. Initialize the _is_shutdown flag to False initially and flip it to True only when the parent class constructor runs, which usually happens last in derived classes. That avoids the shutdown warning for objects that failed to initialize at all. --- can/bus.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/can/bus.py b/can/bus.py index c3a906757..0b5c9e762 100644 --- a/can/bus.py +++ b/can/bus.py @@ -65,7 +65,8 @@ class BusABC(metaclass=ABCMeta): #: Log level for received messages RECV_LOGGING_LEVEL = 9 - _is_shutdown: bool = False + #: Assume that no cleanup is needed until something was initialized + _is_shutdown: bool = True _can_protocol: CanProtocol = CanProtocol.CAN_20 @abstractmethod @@ -97,6 +98,10 @@ def __init__( """ self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) + # Flip the class default value when the constructor finishes. That + # usually means the derived class constructor was also successful, + # since it calls this parent constructor last. + self._is_shutdown: bool = False def __str__(self) -> str: return self.channel_info From 7203b65673675f4220bcfd9dec58b6c5b6cfacd2 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 17 Mar 2024 09:01:53 +1300 Subject: [PATCH 1109/1235] =?UTF-8?q?=F0=9F=94=A7=20Update=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - update upload/download workflow actions - use OIDC credentials to release to PyPI --- .github/workflows/ci.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e70f0f8cf..e1d26c7c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,7 +155,7 @@ jobs: - name: Run doctest run: | python -m sphinx -b doctest -W --keep-going doc build - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: sphinx-out path: ./build/ @@ -174,9 +174,9 @@ jobs: - name: Check build artifacts run: pipx run twine check --strict dist/* - name: Save artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: python-can-dist + name: "${{ matrix.os }}-${{ matrix.python-version }}" path: ./dist upload_pypi: @@ -186,12 +186,9 @@ jobs: # upload to PyPI only on release if: github.event.release && github.event.action == 'published' steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: python-can-dist path: dist + merge-multiple: true - - uses: pypa/gh-action-pypi-publish@v1.4.2 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + - uses: pypa/gh-action-pypi-publish@release/v1 From db1d16ad4647e238f0737c4eabb9b0eb886ce8ad Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 17 Mar 2024 13:24:15 +1300 Subject: [PATCH 1110/1235] Draft release notes for v4.3.2 --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8798c19c7..548f14f0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,35 @@ +Version 4.3.2 +============= + +Features +-------- + +* TRC 1.3 Support: Added support for .trc log files as generated by PCAN Explorer v5 and other tools, expanding compatibility with common log file formats (#1753). +* ASCReader Performance: Significantly improved the read performance of ASCReader, optimizing log file processing and analysis (#1717). +* SYSTEC Interface Enhancements: Added the ability to pass an explicit DLC value to the send() method when using the SYSTEC interface, enhancing flexibility for message definitions (#1756). +* Socketcand Beacon Detection: Introduced a feature for detecting socketcand beacons, facilitating easier connection and configuration with socketcand servers (#1687). +* PCAN Driver Echo Frames: Enabled echo frames in the PCAN driver when receive_own_messages is set, improving feedback for message transmissions (#1723). +* CAN FD Bus Connection: Enabled connecting to CAN FD buses without specifying bus timings, simplifying the connection process for users (#1716). +* Neousys Configs Detection: Updated the detection mechanism for available Neousys configurations, ensuring more accurate and comprehensive configuration discovery (#1744). + + +Bug Fixes +--------- + +* Send Periodic Messages: Fixed an issue where fixed-duration periodic messages were sent one extra time beyond their intended count (#1713). +* Vector Interface on Windows 11: Addressed compatibility issues with the Vector interface on Windows 11, ensuring stable operation across the latest OS version (#1731). +* ASCWriter Millisecond Handling: Corrected the handling of milliseconds in ASCWriter, ensuring accurate time representation in log files (#1734). +* Various minor bug fixes: Addressed several minor bugs to improve overall stability and performance. + +Miscellaneous +------------- + +* Implemented various logging enhancements to provide more detailed and useful operational insights (#1703). +* Updated CI to use OIDC for connecting GitHub Actions to PyPi, improving security and access control for CI workflows. + +The release also includes various other minor enhancements and bug fixes aimed at improving the reliability and performance of the software. + + Version 4.3.1 ============= From f97ab8469ed7bacf44b4401ae6377c7f60661afc Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 17 Mar 2024 13:27:30 +1300 Subject: [PATCH 1111/1235] Set version to 4.3.2-rc.1 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 9b6a26f58..454947192 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.3.1" +__version__ = "4.3.2-rc.1" __all__ = [ "ASCReader", "ASCWriter", From 651fe17f38af10e3973f0987518e53fc85797ee6 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 17 Mar 2024 13:57:36 +1300 Subject: [PATCH 1112/1235] Fix CI - Add id-token permission to upload_pypi job --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1d26c7c6..12e7b6f27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -180,8 +180,10 @@ jobs: path: ./dist upload_pypi: - needs: [build] + needs: [build, test] runs-on: ubuntu-latest + permissions: + id-token: write # upload to PyPI only on release if: github.event.release && github.event.action == 'published' From 28e9061fb6fa78a905214f6af2f9fc68b1716c40 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 17 Mar 2024 14:30:53 +1300 Subject: [PATCH 1113/1235] 4.3.2-rc.2 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 454947192..4fc703c72 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.3.2-rc.1" +__version__ = "4.3.2-rc.2" __all__ = [ "ASCReader", "ASCWriter", From c02a665bba7f864ed5a4f177a21f4bc24ab1cc77 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 17 Mar 2024 14:54:05 +1300 Subject: [PATCH 1114/1235] Allow release without tests --- .github/workflows/ci.yml | 2 +- can/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12e7b6f27..7cf76e934 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -180,7 +180,7 @@ jobs: path: ./dist upload_pypi: - needs: [build, test] + needs: [build] runs-on: ubuntu-latest permissions: id-token: write diff --git a/can/__init__.py b/can/__init__.py index 4fc703c72..42cf2bc5d 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.3.2-rc.2" +__version__ = "4.3.2-rc.3" __all__ = [ "ASCReader", "ASCWriter", From c1b1152edeba7158dce6aef6b43bb6584727bb0b Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 17 Mar 2024 18:56:50 +1300 Subject: [PATCH 1115/1235] Update CI.yml workflow --- .github/workflows/ci.yml | 24 +++++++++++------------- can/__init__.py | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cf76e934..5c06ee94f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,9 +42,9 @@ jobs: # python-version: "3.13.0-alpha - 3.13.0" fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -139,9 +139,9 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install dependencies @@ -155,18 +155,14 @@ jobs: - name: Run doctest run: | python -m sphinx -b doctest -W --keep-going doc build - - uses: actions/upload-artifact@v4 - with: - name: sphinx-out - path: ./build/ - retention-days: 5 build: + name: Packaging runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Build wheel and sdist @@ -176,11 +172,12 @@ jobs: - name: Save artifacts uses: actions/upload-artifact@v4 with: - name: "${{ matrix.os }}-${{ matrix.python-version }}" + name: release path: ./dist upload_pypi: needs: [build] + name: Release to PyPi runs-on: ubuntu-latest permissions: id-token: write @@ -193,4 +190,5 @@ jobs: path: dist merge-multiple: true - - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Publish release distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/can/__init__.py b/can/__init__.py index 42cf2bc5d..44c6032f9 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.3.2-rc.3" +__version__ = "4.3.2-rc.4" __all__ = [ "ASCReader", "ASCWriter", From 9c50c89e52a9d71850f70df3325d392ca79b822a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Jun 2024 19:51:01 +1200 Subject: [PATCH 1116/1235] Update changelog for v4.4.0 --- CHANGELOG.md | 10 ++++++---- can/__init__.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 548f14f0a..404b68c3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,15 @@ -Version 4.3.2 +Version 4.4.0 ============= Features -------- * TRC 1.3 Support: Added support for .trc log files as generated by PCAN Explorer v5 and other tools, expanding compatibility with common log file formats (#1753). -* ASCReader Performance: Significantly improved the read performance of ASCReader, optimizing log file processing and analysis (#1717). +* ASCReader refactor: improved the ASCReader code (#1717). * SYSTEC Interface Enhancements: Added the ability to pass an explicit DLC value to the send() method when using the SYSTEC interface, enhancing flexibility for message definitions (#1756). * Socketcand Beacon Detection: Introduced a feature for detecting socketcand beacons, facilitating easier connection and configuration with socketcand servers (#1687). * PCAN Driver Echo Frames: Enabled echo frames in the PCAN driver when receive_own_messages is set, improving feedback for message transmissions (#1723). -* CAN FD Bus Connection: Enabled connecting to CAN FD buses without specifying bus timings, simplifying the connection process for users (#1716). +* CAN FD Bus Connection for VectorBus: Enabled connecting to CAN FD buses without specifying bus timings, simplifying the connection process for users (#1716). * Neousys Configs Detection: Updated the detection mechanism for available Neousys configurations, ensuring more accurate and comprehensive configuration discovery (#1744). @@ -24,9 +24,11 @@ Bug Fixes Miscellaneous ------------- +* Invert default value logic for BusABC._is_shutdown. (#1774) * Implemented various logging enhancements to provide more detailed and useful operational insights (#1703). * Updated CI to use OIDC for connecting GitHub Actions to PyPi, improving security and access control for CI workflows. - +* Fix CI to work for MacOS (#1772). +* The release also includes various other minor enhancements and bug fixes aimed at improving the reliability and performance of the software. diff --git a/can/__init__.py b/can/__init__.py index 44c6032f9..d504aa16b 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.3.2-rc.4" +__version__ = "4.4.0-rc.1" __all__ = [ "ASCReader", "ASCWriter", From c8202f6a478013102c21ce1d1abd31cdfa38aea4 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Jun 2024 20:14:57 +1200 Subject: [PATCH 1117/1235] Update release docs --- .github/workflows/ci.yml | 1 + can/__init__.py | 2 +- doc/development.rst | 14 ++++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c06ee94f..742ccca03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -181,6 +181,7 @@ jobs: runs-on: ubuntu-latest permissions: id-token: write + attestations: write # upload to PyPI only on release if: github.event.release && github.event.action == 'published' diff --git a/can/__init__.py b/can/__init__.py index d504aa16b..86e0f8a6c 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.4.0-rc.1" +__version__ = "4.4.0-rc.2" __all__ = [ "ASCReader", "ASCWriter", diff --git a/doc/development.rst b/doc/development.rst index ec7b7dc24..f635ffc06 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -117,13 +117,19 @@ Creating a new Release - Update ``CONTRIBUTORS.txt`` with any new contributors. - For larger changes update ``doc/history.rst``. - Sanity check that documentation has stayed inline with code. -- Create a temporary virtual environment. Run ``python setup.py install`` and ``tox``. -- Create and upload the distribution: ``python setup.py sdist bdist_wheel``. -- Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl``. -- Upload with twine ``twine upload dist/python-can-X.Y.Z*``. - In a new virtual env check that the package can be installed with pip: ``pip install python-can==X.Y.Z``. - Create a new tag in the repository. - Check the release on `PyPi `__, `Read the Docs `__ and `GitHub `__. + + +Manual release steps (deprecated) +--------------------------------- + +- Create a temporary virtual environment. +- Build with ``pipx run build`` +- Create and upload the distribution: ``python setup.py sdist bdist_wheel``. +- Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl``. +- Upload with twine ``twine upload dist/python-can-X.Y.Z*``. From 26a3f4bd4890436c5329d9fbd68c865e393bc821 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 8 Jun 2024 08:18:00 +1200 Subject: [PATCH 1118/1235] Set version to 4.4.0 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 86e0f8a6c..d0c19f89b 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.4.0-rc.2" +__version__ = "4.4.0" __all__ = [ "ASCReader", "ASCWriter", From 034b400cd41588a154d7b69468ed43a74b5d9c4a Mon Sep 17 00:00:00 2001 From: Tian-Jionglu Date: Fri, 7 Jun 2024 13:20:03 +0800 Subject: [PATCH 1119/1235] doc update update installation doc since `setup.py` method removed. --- doc/installation.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/installation.rst b/doc/installation.rst index 6b2a2cfb2..ff72ae21b 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -131,5 +131,7 @@ reinstall. Download or clone the source repository then: :: - python setup.py develop + # install in editable mode + cd + python3 -m pip install -e . From fc6ff2235b240117ab1e4ae585f007578e21e57e Mon Sep 17 00:00:00 2001 From: Caleb Perkinson <60443297+cperkulator@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:28:34 -0500 Subject: [PATCH 1120/1235] add silent mode support for vector (#1764) * add silent mode support for vector * fix: address comments and test works as expected * formatting --- can/interfaces/vector/canlib.py | 40 +++++++++++++++++++++++++++++---- test/test_vector.py | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 3a25c99d8..adf7b6c5d 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -104,6 +104,7 @@ def __init__( sjw_dbr: int = 2, tseg1_dbr: int = 6, tseg2_dbr: int = 3, + listen_only: Optional[bool] = False, **kwargs: Any, ) -> None: """ @@ -157,6 +158,8 @@ def __init__( Bus timing value tseg1 (data) :param tseg2_dbr: Bus timing value tseg2 (data) + :param listen_only: + if the bus should be set to listen only mode. :raise ~can.exceptions.CanInterfaceNotImplementedError: If the current operating system is not supported or the driver could not be loaded. @@ -205,6 +208,8 @@ def __init__( self.index_to_channel: Dict[int, int] = {} self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20 + self._listen_only = listen_only + for channel in self.channels: if ( len(self.channels) == 1 @@ -232,7 +237,7 @@ def __init__( permission_mask = xlclass.XLaccess() # Set mask to request channel init permission if needed - if bitrate or fd or timing: + if bitrate or fd or timing or self._listen_only: permission_mask.value = self.mask interface_version = ( @@ -311,6 +316,9 @@ def __init__( if assert_timing: self._check_can_settings(channel_mask=self.mask, bitrate=bitrate) + if self._listen_only: + self._set_output_mode(channel_mask=self.mask, listen_only=True) + # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 self.xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) @@ -445,6 +453,28 @@ def _read_bus_params( f"Channel configuration for channel {channel} not found." ) + def _set_output_mode(self, channel_mask: int, listen_only: bool) -> None: + # set parameters for channels with init access + channel_mask = channel_mask & self.permission_mask + + if channel_mask: + if listen_only: + self.xldriver.xlCanSetChannelOutput( + self.port_handle, + channel_mask, + xldefine.XL_OutputMode.XL_OUTPUT_MODE_SILENT, + ) + else: + self.xldriver.xlCanSetChannelOutput( + self.port_handle, + channel_mask, + xldefine.XL_OutputMode.XL_OUTPUT_MODE_NORMAL, + ) + + LOG.info("xlCanSetChannelOutput: listen_only=%u", listen_only) + else: + LOG.warning("No channels with init access to set listen only mode") + def _set_bitrate(self, channel_mask: int, bitrate: int) -> None: # set parameters for channels with init access channel_mask = channel_mask & self.permission_mask @@ -643,9 +673,11 @@ def _apply_filters(self, filters: Optional[CanFilters]) -> None: self.mask, can_filter["can_id"], can_filter["can_mask"], - xldefine.XL_AcceptanceFilter.XL_CAN_EXT - if can_filter.get("extended") - else xldefine.XL_AcceptanceFilter.XL_CAN_STD, + ( + xldefine.XL_AcceptanceFilter.XL_CAN_EXT + if can_filter.get("extended") + else xldefine.XL_AcceptanceFilter.XL_CAN_STD + ), ) except VectorOperationError as exception: LOG.warning("Could not set filters: %s", exception) diff --git a/test/test_vector.py b/test/test_vector.py index 99756c41d..ca46526fb 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -48,6 +48,7 @@ def mock_xldriver() -> None: xldriver_mock.xlCanSetChannelAcceptance = Mock(return_value=0) xldriver_mock.xlCanSetChannelBitrate = Mock(return_value=0) xldriver_mock.xlSetNotification = Mock(side_effect=xlSetNotification) + xldriver_mock.xlCanSetChannelOutput = Mock(return_value=0) # bus deactivation functions xldriver_mock.xlDeactivateChannel = Mock(return_value=0) @@ -78,6 +79,44 @@ def mock_xldriver() -> None: canlib.HAS_EVENTS = real_has_events +def test_listen_only_mocked(mock_xldriver) -> None: + bus = can.Bus(channel=0, interface="vector", listen_only=True, _testing=True) + assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 + + can.interfaces.vector.canlib.xldriver.xlCanSetChannelOutput.assert_called() + xlCanSetChannelOutput_args = ( + can.interfaces.vector.canlib.xldriver.xlCanSetChannelOutput.call_args[0] + ) + assert xlCanSetChannelOutput_args[2] == xldefine.XL_OutputMode.XL_OUTPUT_MODE_SILENT + + +@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") +def test_listen_only() -> None: + bus = can.Bus( + channel=0, + serial=_find_virtual_can_serial(), + interface="vector", + receive_own_messages=True, + listen_only=True, + ) + assert isinstance(bus, canlib.VectorBus) + assert bus.protocol == can.CanProtocol.CAN_20 + + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + + bus.send(msg) + + received_msg = bus.recv() + + assert received_msg.arbitration_id == msg.arbitration_id + assert received_msg.data == msg.data + + bus.shutdown() + + def test_bus_creation_mocked(mock_xldriver) -> None: bus = can.Bus(channel=0, interface="vector", _testing=True) assert isinstance(bus, canlib.VectorBus) From 96754c1025bd1011d8957cd111ca38dd41414a70 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Mon, 10 Jun 2024 13:35:38 -0400 Subject: [PATCH 1121/1235] Return timestamps relative to epoch in neovi interface (#1789) --- can/interfaces/ics_neovi/neovi_bus.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 7863fcb65..815ed6fa0 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -13,6 +13,7 @@ import os import tempfile from collections import Counter, defaultdict, deque +from datetime import datetime from functools import partial from itertools import cycle from threading import Event @@ -68,6 +69,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): open_lock = FileLock(os.path.join(tempfile.gettempdir(), "neovi.lock")) description_id = cycle(range(1, 0x8000)) +ICS_EPOCH = datetime.fromisoformat("2007-01-01") +ICS_EPOCH_DELTA = (ICS_EPOCH - datetime.fromisoformat("1970-01-01")).total_seconds() + class ICSApiError(CanError): """ @@ -384,7 +388,7 @@ def _get_timestamp_for_msg(self, ics_msg): return ics_msg.TimeSystem else: # This is the hardware time stamp. - return ics.get_timestamp_for_msg(self.dev, ics_msg) + return ics.get_timestamp_for_msg(self.dev, ics_msg) + ICS_EPOCH_DELTA def _ics_msg_to_message(self, ics_msg): is_fd = ics_msg.Protocol == ics.SPY_PROTOCOL_CANFD From 1ce550c8bf75790eadf23eae4a197588d764fe4f Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 12 Jun 2024 23:17:26 +0200 Subject: [PATCH 1122/1235] Update linters and gh actions (#1794) * update black * update ruff * update mypy and pylint * make pylint happy * update actions --- .github/workflows/ci.yml | 12 ++++++------ can/ctypesutil.py | 1 + can/interfaces/ixxat/structures.py | 22 +++++++++++----------- can/interfaces/kvaser/canlib.py | 1 - can/interfaces/kvaser/structures.py | 17 +++++++---------- can/interfaces/pcan/pcan.py | 1 + can/interfaces/socketcan/socketcan.py | 13 +++++++------ can/interfaces/socketcand/socketcand.py | 1 + can/interfaces/systec/exceptions.py | 3 +-- can/interfaces/systec/ucanbus.py | 10 +++++----- can/io/asc.py | 1 + can/io/generic.py | 1 + can/io/mf4.py | 1 + can/io/player.py | 1 + can/listener.py | 2 +- can/notifier.py | 10 ++++++---- can/typechecking.py | 1 + can/util.py | 1 + pyproject.toml | 16 +++++++++------- test/test_rotating_loggers.py | 3 +-- 20 files changed, 63 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 742ccca03..4afb4c1ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: # See: https://foss.heptapod.net/pypy/pypy/-/issues/3809 TEST_SOCKETCAN: "${{ matrix.os == 'ubuntu-latest' && ! startsWith(matrix.python-version, 'pypy' ) }}" - name: Coveralls Parallel - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} flag-name: Unittests-${{ matrix.os }}-${{ matrix.python-version }} @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Coveralls Finished - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} parallel-finished: true @@ -84,9 +84,9 @@ jobs: static-code-analysis: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install dependencies @@ -123,9 +123,9 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install dependencies diff --git a/can/ctypesutil.py b/can/ctypesutil.py index fa59255d1..0336b03d3 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -1,6 +1,7 @@ """ This module contains common `ctypes` utils. """ + import ctypes import logging import sys diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index 419a52973..611955db7 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -51,17 +51,17 @@ class UniqueHardwareId(ctypes.Union): ] def __str__(self): - return "Mfg: {}, Dev: {} HW: {}.{}.{}.{} Drv: {}.{}.{}.{}".format( - self.Manufacturer, - self.Description, - self.HardwareBranchVersion, - self.HardwareMajorVersion, - self.HardwareMinorVersion, - self.HardwareBuildVersion, - self.DriverReleaseVersion, - self.DriverMajorVersion, - self.DriverMinorVersion, - self.DriverBuildVersion, + return ( + f"Mfg: {self.Manufacturer}, " + f"Dev: {self.Description} " + f"HW: {self.HardwareBranchVersion}" + f".{self.HardwareMajorVersion}" + f".{self.HardwareMinorVersion}" + f".{self.HardwareBuildVersion} " + f"Drv: {self.DriverReleaseVersion}" + f".{self.DriverMajorVersion}" + f".{self.DriverMinorVersion}" + f".{self.DriverBuildVersion}" ) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 4c621ecf4..ccc03a696 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -64,7 +64,6 @@ def __get_canlib_function(func_name, argtypes=None, restype=None, errcheck=None) class CANLIBError(CanError): - """ Try to display errors that occur within the wrapped C library nicely. """ diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index 996f16c37..0229cc10a 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -23,16 +23,13 @@ class BusStatistics(ctypes.Structure): def __str__(self): return ( - "std_data: {}, std_remote: {}, ext_data: {}, ext_remote: {}, " - "err_frame: {}, bus_load: {:.1f}%, overruns: {}" - ).format( - self.std_data, - self.std_remote, - self.ext_data, - self.ext_remote, - self.err_frame, - self.bus_load / 100.0, - self.overruns, + f"std_data: {self.std_data}, " + f"std_remote: {self.std_remote}, " + f"ext_data: {self.ext_data}, " + f"ext_remote: {self.ext_remote}, " + f"err_frame: {self.err_frame}, " + f"bus_load: {self.bus_load / 100.0:.1f}%, " + f"overruns: {self.overruns}" ) @property diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index b1b089690..52dbb49b0 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -1,6 +1,7 @@ """ Enable basic CAN over a PCAN USB device. """ + import logging import platform import time diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index cdf4afac6..79a46ab2c 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -41,6 +41,13 @@ log.error("socket.CMSG_SPACE not available on this platform") +# Constants needed for precise handling of timestamps +RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@ll") +RECEIVED_ANCILLARY_BUFFER_SIZE = ( + CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) if CMSG_SPACE_available else 0 +) + + # Setup BCM struct def bcm_header_factory( fields: List[Tuple[str, Union[Type[ctypes.c_uint32], Type[ctypes.c_long]]]], @@ -597,12 +604,6 @@ def capture_message( return msg -# Constants needed for precise handling of timestamps -if CMSG_SPACE_available: - RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@ll") - RECEIVED_ANCILLARY_BUFFER_SIZE = CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) - - class SocketcanBus(BusABC): # pylint: disable=abstract-method """A SocketCAN interface to CAN. diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index e852c5e14..2cfdf54e5 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -7,6 +7,7 @@ Copyright (C) 2021 DOMOLOGIC GmbH http://www.domologic.de """ + import logging import os import select diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index 9f7d4e2e5..dcd94bdbf 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -22,8 +22,7 @@ def __init__(self, result, func, arguments): @property @abstractmethod - def _error_message_mapping(self) -> Dict[ReturnCode, str]: - ... + def _error_message_mapping(self) -> Dict[ReturnCode, str]: ... class UcanError(UcanException): diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index 3dff7fda5..00f101e4e 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -143,11 +143,11 @@ def __init__(self, channel, can_filters=None, **kwargs): self._ucan.init_hardware(device_number=device_number) self._ucan.init_can(self.channel, **self._params) hw_info_ex, _, _ = self._ucan.get_hardware_info() - self.channel_info = "{}, S/N {}, CH {}, BTR {}".format( - self._ucan.get_product_code_message(hw_info_ex.product_code), - hw_info_ex.serial, - self.channel, - self._ucan.get_baudrate_message(self.BITRATES[bitrate]), + self.channel_info = ( + f"{self._ucan.get_product_code_message(hw_info_ex.product_code)}, " + f"S/N {hw_info_ex.serial}, " + f"CH {self.channel}, " + f"BTR {self._ucan.get_baudrate_message(self.BITRATES[bitrate])}" ) except UcanException as exception: raise CanInitializationError() from exception diff --git a/can/io/asc.py b/can/io/asc.py index ceffaf5cb..2955ad7f9 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -5,6 +5,7 @@ - https://bitbucket.org/tobylorenz/vector_asc/src/master/src/Vector/ASC/tests/unittests/data/ - under `test/data/logfile.asc` """ + import logging import re from datetime import datetime diff --git a/can/io/generic.py b/can/io/generic.py index 4d877865e..55468ff16 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,4 +1,5 @@ """Contains generic base classes for file IO.""" + import gzip import locale from abc import ABCMeta diff --git a/can/io/mf4.py b/can/io/mf4.py index 7ab6ba8a7..042bf8765 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -4,6 +4,7 @@ MF4 files represent Measurement Data Format (MDF) version 4 as specified by the ASAM MDF standard (see https://www.asam.net/standards/detail/mdf/) """ + import logging from datetime import datetime from hashlib import md5 diff --git a/can/io/player.py b/can/io/player.py index 4cbd7ce16..214112164 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -3,6 +3,7 @@ well as :class:`MessageSync` which plays back messages in the recorded order and time intervals. """ + import gzip import pathlib import time diff --git a/can/listener.py b/can/listener.py index ff3672913..e0024c683 100644 --- a/can/listener.py +++ b/can/listener.py @@ -136,7 +136,7 @@ class AsyncBufferedReader( """ def __init__(self, **kwargs: Any) -> None: - self.buffer: "asyncio.Queue[Message]" + self.buffer: asyncio.Queue[Message] if "loop" in kwargs: warnings.warn( diff --git a/can/notifier.py b/can/notifier.py index 1c8b77c5d..34fcf74fa 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -112,10 +112,12 @@ def _rx_thread(self, bus: BusABC) -> None: # determine message handling callable early, not inside while loop handle_message = cast( Callable[[Message], None], - self._on_message_received - if self._loop is None - else functools.partial( - self._loop.call_soon_threadsafe, self._on_message_received + ( + self._on_message_received + if self._loop is None + else functools.partial( + self._loop.call_soon_threadsafe, self._on_message_received + ) ), ) diff --git a/can/typechecking.py b/can/typechecking.py index 29c760d31..89f978a1a 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -1,5 +1,6 @@ """Types for mypy type-checking """ + import gzip import typing diff --git a/can/util.py b/can/util.py index c1eeca8b1..23ab142fb 100644 --- a/can/util.py +++ b/can/util.py @@ -1,6 +1,7 @@ """ Utilities and configuration file parsing. """ + import copy import functools import json diff --git a/pyproject.toml b/pyproject.toml index ad59469da..08b501c60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,10 +60,10 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ - "pylint==3.0.*", - "ruff==0.1.13", - "black==23.12.*", - "mypy==1.8.*", + "pylint==3.2.*", + "ruff==0.4.8", + "black==24.4.*", + "mypy==1.10.*", ] seeedstudio = ["pyserial>=3.0"] serial = ["pyserial~=3.0"] @@ -128,6 +128,9 @@ exclude = [ ] [tool.ruff] +line-length = 100 + +[tool.ruff.lint] select = [ "A", # flake8-builtins "B", # flake8-bugbear @@ -149,9 +152,8 @@ ignore = [ "B026", # star-arg-unpacking-after-keyword-arg "PLR", # pylint refactor ] -line-length = 100 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "can/interfaces/*" = [ "E501", # Line too long "F403", # undefined-local-with-import-star @@ -163,7 +165,7 @@ line-length = 100 "can/logger.py" = ["T20"] # flake8-print "can/player.py" = ["T20"] # flake8-print -[tool.ruff.isort] +[tool.ruff.lint.isort] known-first-party = ["can"] [tool.pylint] diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index a6661280d..032106e5f 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -38,8 +38,7 @@ def writer(self) -> FileIOMessageWriter: def should_rollover(self, msg: can.Message) -> bool: return False - def do_rollover(self): - ... + def do_rollover(self): ... return SubClass(file=file) From 25d6999c20d6dee3595f586abc55b69c04fb2ea9 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 12 Jun 2024 23:43:02 +0200 Subject: [PATCH 1123/1235] remove abstractmethod (#1795) --- can/listener.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/can/listener.py b/can/listener.py index e0024c683..1f19c3acd 100644 --- a/can/listener.py +++ b/can/listener.py @@ -46,8 +46,7 @@ def on_error(self, exc: Exception) -> None: """ raise NotImplementedError() - @abstractmethod - def stop(self) -> None: + def stop(self) -> None: # noqa: B027 """ Stop handling new messages, carry out any final tasks to ensure data is persisted and cleanup any open resources. From 35e847f6cb799c4c4ae381f8c0e7cc1ccd791499 Mon Sep 17 00:00:00 2001 From: Federico Spada <160390475+FedericoSpada@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:43:15 +0200 Subject: [PATCH 1124/1235] Fixed pcan Unpack error (#1767) * Fixed pcan Unpack error PCANBasic.GetValue() must always return a Tuple with 2 elements, otherwise PcanBus._detect_available_configs() raises a "not enough values to unpack" error at line 718. * Fixed typo --- can/interfaces/pcan/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index b704ca9bd..4d175c645 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -967,7 +967,7 @@ def GetValue(self, Channel, Parameter): elif Parameter == PCAN_ATTACHED_CHANNELS: res = self.GetValue(Channel, PCAN_ATTACHED_CHANNELS_COUNT) if TPCANStatus(res[0]) != PCAN_ERROR_OK: - return (TPCANStatus(res[0]),) + return TPCANStatus(res[0]), () mybuffer = (TPCANChannelInformation * res[1])() elif ( From 046af70e7c8aeb377df61768251314c6b714cf42 Mon Sep 17 00:00:00 2001 From: Werner Wolfrum <42910294+wolfraven@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:11:13 +0200 Subject: [PATCH 1125/1235] Extented slcan.py to use "L" command optional to "O" command. (#1496) * Update slcan.py Added additional parameter "listen_only" to open interface/channel with "L" command (in opposite to "O" command). * make listen_only attribute private --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/slcan.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 7851a0322..f32d82fd0 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -62,6 +62,7 @@ def __init__( btr: Optional[str] = None, sleep_after_open: float = _SLEEP_AFTER_SERIAL_OPEN, rtscts: bool = False, + listen_only: bool = False, timeout: float = 0.001, **kwargs: Any, ) -> None: @@ -81,12 +82,18 @@ def __init__( Time to wait in seconds after opening serial connection :param rtscts: turn hardware handshake (RTS/CTS) on and off + :param listen_only: + If True, open interface/channel in listen mode with ``L`` command. + Otherwise, the (default) ``O`` command is still used. See ``open`` method. :param timeout: Timeout for the serial or usb device in seconds (default 0.001) + :raise ValueError: if both ``bitrate`` and ``btr`` are set or the channel is invalid :raise CanInterfaceNotImplementedError: if the serial module is missing :raise CanInitializationError: if the underlying serial connection could not be established """ + self._listen_only = listen_only + if serial is None: raise CanInterfaceNotImplementedError("The serial module is not installed") @@ -188,7 +195,10 @@ def flush(self) -> None: self.serialPortOrig.reset_input_buffer() def open(self) -> None: - self._write("O") + if self._listen_only: + self._write("L") + else: + self._write("O") def close(self) -> None: self._write("C") From 4fe3881add06faca4062be197bfa82db78e624cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Wed, 24 Apr 2024 12:46:19 +0200 Subject: [PATCH 1126/1235] Gracefully handle errors when binding SocketCAN fails. The parent class BusABC expects to be shutdown properly, checked via its self._is_shutdown flag during object deletion. But that flag is only set when the base class shutdown() is called, which doesn't happen if instantiation of the concrete class failed in can.Bus(). So there is no way to avoid the warning message "SocketcanBus was not properly shut down" if the constructor raised an exception. This change addresses that issue for the SocketCAN interface, by catching an OSError exception in bind_socket(), logging it, and calling self.shutdown(). --- can/interfaces/socketcan/socketcan.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 79a46ab2c..3c1178ebf 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -706,14 +706,20 @@ def __init__( # so this is always supported by the kernel self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1) - bind_socket(self.socket, channel) - kwargs.update( - { - "receive_own_messages": receive_own_messages, - "fd": fd, - "local_loopback": local_loopback, - } - ) + try: + bind_socket(self.socket, channel) + kwargs.update( + { + "receive_own_messages": receive_own_messages, + "fd": fd, + "local_loopback": local_loopback, + } + ) + except OSError as error: + log.error("Could not access SocketCAN device %s (%s)", channel, error) + # Clean up so the parent class doesn't complain about not being shut down properly + self.shutdown() + raise super().__init__( channel=channel, can_filters=can_filters, From 2bc87c0155b19bef1364b5e50523e2d1ea5f3ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Tue, 30 Apr 2024 15:42:23 +0200 Subject: [PATCH 1127/1235] Remove shutdown call and obsolete comment. --- can/interfaces/socketcan/socketcan.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 3c1178ebf..03bd8c3f8 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -717,8 +717,6 @@ def __init__( ) except OSError as error: log.error("Could not access SocketCAN device %s (%s)", channel, error) - # Clean up so the parent class doesn't complain about not being shut down properly - self.shutdown() raise super().__init__( channel=channel, From 8ea242593ea341caed03324543fd9c3ebc237d15 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 18 Jun 2024 13:20:33 +0200 Subject: [PATCH 1128/1235] Fix SizedRotatingLogger file format bug (#1793) --- can/io/logger.py | 10 ++++++---- test/test_rotating_loggers.py | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index ed7f15bd0..f54223741 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -259,9 +259,11 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: :return: An instance of a writer class. """ - suffix = "".join(pathlib.Path(filename).suffixes[-2:]).lower() - - if suffix in self._supported_formats: + suffixes = pathlib.Path(filename).suffixes + for suffix_length in range(len(suffixes), 0, -1): + suffix = "".join(suffixes[-suffix_length:]).lower() + if suffix not in self._supported_formats: + continue logger = Logger(filename=filename, **self.writer_kwargs) if isinstance(logger, FileIOMessageWriter): return logger @@ -269,7 +271,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: return cast(FileIOMessageWriter, logger) raise ValueError( - f'The log format "{suffix}" ' + f'The log format of "{pathlib.Path(filename).name}" ' f"is not supported by {self.__class__.__name__}. " f"{self.__class__.__name__} supports the following formats: " f"{', '.join(self._supported_formats)}" diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index 032106e5f..ab977e3ce 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -180,6 +180,14 @@ def test_on_message_received(self, tmp_path): do_rollover.assert_called() writers_on_message_received.assert_called_with(msg) + def test_issue_1792(self, tmp_path): + with self._get_instance(tmp_path / "__unused.log") as logger_instance: + writer = logger_instance._get_new_writer( + tmp_path / "2017_Jeep_Grand_Cherokee_3.6L_V6.log" + ) + assert isinstance(writer, can.CanutilsLogWriter) + writer.stop() + class TestSizedRotatingLogger: def test_import(self): From 25c6fe0d2a6daa2bb7520bc4913d0db41854aab3 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:23:03 +0200 Subject: [PATCH 1129/1235] avoid logging socketcan exception on non-linux platforms (#1800) --- can/interfaces/socketcan/utils.py | 3 +++ test/test_socketcan_helpers.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 91878f9b6..679c9cefc 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -8,6 +8,7 @@ import os import struct import subprocess +import sys from typing import List, Optional, cast from can import typechecking @@ -46,6 +47,8 @@ def find_available_interfaces() -> List[str]: :return: The list of available and active CAN interfaces or an empty list of the command failed """ + if sys.platform != "linux": + return [] try: command = ["ip", "-json", "link", "list", "up"] diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index a1d0bc8af..710922290 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -45,9 +45,11 @@ def test_find_available_interfaces_w_patch(self): with mock.patch("subprocess.check_output") as check_output: check_output.return_value = ip_output - ifs = find_available_interfaces() - self.assertEqual(["vcan0", "mycustomCan123"], ifs) + with mock.patch("sys.platform", "linux"): + ifs = find_available_interfaces() + + self.assertEqual(["vcan0", "mycustomCan123"], ifs) def test_find_available_interfaces_exception(self): with mock.patch("subprocess.check_output") as check_output: From 7366c4289f7222b61b6d7d0481b7c66522c73930 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:47:53 +0200 Subject: [PATCH 1130/1235] fix PCAN test DeprecationWarnings (#1801) --- test/test_pcan.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_pcan.py b/test/test_pcan.py index 9f4e36fc4..31c541f0a 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -54,7 +54,9 @@ def test_bus_creation(self) -> None: self.assertIsInstance(self.bus, PcanBus) self.assertEqual(self.bus.protocol, CanProtocol.CAN_20) - self.assertFalse(self.bus.fd) + + with pytest.deprecated_call(): + self.assertFalse(self.bus.fd) self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_called_once() @@ -83,7 +85,9 @@ def test_bus_creation_fd(self, clock_param: str, clock_val: int) -> None: self.assertIsInstance(self.bus, PcanBus) self.assertEqual(self.bus.protocol, CanProtocol.CAN_FD) - self.assertTrue(self.bus.fd) + + with pytest.deprecated_call(): + self.assertTrue(self.bus.fd) self.MockPCANBasic.assert_called_once() self.mock_pcan.Initialize.assert_not_called() From 621cd7ddc12400b4f53de84c19b5d04e5c1a9798 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:02:09 +0200 Subject: [PATCH 1131/1235] Activate channel after CAN filters were applied (#1796) --- can/interfaces/kvaser/canlib.py | 11 +++++++---- can/interfaces/vector/canlib.py | 17 +++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index ccc03a696..58baa1040 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -552,7 +552,6 @@ def __init__( else: flags_ = flags self._write_handle = canOpenChannel(channel, flags_) - canBusOn(self._read_handle) can_driver_mode = ( canstat.canDRIVER_SILENT @@ -560,8 +559,6 @@ def __init__( else canstat.canDRIVER_NORMAL ) canSetBusOutputControl(self._write_handle, can_driver_mode) - log.debug("Going bus on TX handle") - canBusOn(self._write_handle) timer = ctypes.c_uint(0) try: @@ -587,6 +584,12 @@ def __init__( **kwargs, ) + # activate channel after CAN filters were applied + log.debug("Go on bus") + if not self.single_handle: + canBusOn(self._read_handle) + canBusOn(self._write_handle) + def _apply_filters(self, filters): if filters and len(filters) == 1: can_id = filters[0]["can_id"] @@ -610,7 +613,7 @@ def _apply_filters(self, filters): for extended in (0, 1): canSetAcceptanceFilter(handle, 0, 0, extended) except (NotImplementedError, CANLIBError) as e: - log.error("An error occured while disabling filtering: %s", e) + log.error("An error occurred while disabling filtering: %s", e) def flush_tx_buffer(self): """Wipeout the transmit buffer on the Kvaser.""" diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index adf7b6c5d..d307d076f 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -329,14 +329,6 @@ def __init__( else: LOG.info("Install pywin32 to avoid polling") - try: - self.xldriver.xlActivateChannel( - self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0 - ) - except VectorOperationError as error: - self.shutdown() - raise VectorInitializationError.from_generic(error) from None - # Calculate time offset for absolute timestamps offset = xlclass.XLuint64() try: @@ -366,6 +358,15 @@ def __init__( **kwargs, ) + # activate channels after CAN filters were applied + try: + self.xldriver.xlActivateChannel( + self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, 0 + ) + except VectorOperationError as error: + self.shutdown() + raise VectorInitializationError.from_generic(error) from None + @property def fd(self) -> bool: class_name = self.__class__.__name__ From 867fd92cc367c1960a32972d1225263de6b8d950 Mon Sep 17 00:00:00 2001 From: Jacob Schaer Date: Sat, 22 Jun 2024 03:02:10 -0700 Subject: [PATCH 1132/1235] Add non-ISO CANFD support to kvaser (#1752) * Add basic non-iso support * Update test_kvaser.py Add unit test * Update test_kvaser.py Black to pass premerge checklist * Made code changes New enum for non-iso mode Documentation sections fixes Test update * Update bus.py Black cleanup --- can/bus.py | 3 ++- can/interfaces/kvaser/canlib.py | 16 ++++++++++++++-- doc/interfaces/pcan.rst | 7 +++++++ doc/interfaces/socketcan.rst | 5 +++++ test/test_kvaser.py | 27 +++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/can/bus.py b/can/bus.py index 0b5c9e762..954c78c5f 100644 --- a/can/bus.py +++ b/can/bus.py @@ -44,7 +44,8 @@ class CanProtocol(Enum): """The CAN protocol type supported by a :class:`can.BusABC` instance""" CAN_20 = auto() - CAN_FD = auto() + CAN_FD = auto() # ISO Mode + CAN_FD_NON_ISO = auto() CAN_XL = auto() diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 58baa1040..e7409b668 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -428,6 +428,10 @@ def __init__( computer, set this to True or set single_handle to True. :param bool fd: If CAN-FD frames should be supported. + :param bool fd_non_iso: + Open the channel in Non-ISO (Bosch) FD mode. Only applies for FD buses. + This changes the handling of the stuff-bit counter and the CRC. Defaults + to False (ISO mode) :param bool exclusive: Don't allow sharing of this CANlib channel. :param bool override_exclusive: @@ -453,6 +457,7 @@ def __init__( accept_virtual = kwargs.get("accept_virtual", True) fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False) data_bitrate = kwargs.get("data_bitrate", None) + fd_non_iso = kwargs.get("fd_non_iso", False) try: channel = int(channel) @@ -461,7 +466,11 @@ def __init__( self.channel = channel self.single_handle = single_handle - self._can_protocol = CanProtocol.CAN_FD if fd else CanProtocol.CAN_20 + self._can_protocol = CanProtocol.CAN_20 + if fd_non_iso: + self._can_protocol = CanProtocol.CAN_FD_NON_ISO + elif fd: + self._can_protocol = CanProtocol.CAN_FD log.debug("Initialising bus instance") num_channels = ctypes.c_int(0) @@ -482,7 +491,10 @@ def __init__( if accept_virtual: flags |= canstat.canOPEN_ACCEPT_VIRTUAL if fd: - flags |= canstat.canOPEN_CAN_FD + if fd_non_iso: + flags |= canstat.canOPEN_CAN_FD_NONISO + else: + flags |= canstat.canOPEN_CAN_FD log.debug("Creating read handle to bus channel: %s", channel) self._read_handle = canOpenChannel(channel, flags) diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index 790264627..88ab6838d 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -37,7 +37,14 @@ Here is an example configuration file for using `PCAN-USB Date: Sat, 22 Jun 2024 17:23:46 +0700 Subject: [PATCH 1133/1235] gs_usb: Use BitTiming internally to configure bitrate (#1748) * gs_usb: Use BitTiming to configure bitrate. * use kwargs and format black --------- Co-authored-by: Tuwuh S Wibowo Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/gs_usb.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 21e199a30..5efbd3da6 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -54,7 +54,19 @@ def __init__( self.channel_info = channel self._can_protocol = can.CanProtocol.CAN_20 - self.gs_usb.set_bitrate(bitrate) + bit_timing = can.BitTiming.from_sample_point( + f_clock=self.gs_usb.device_capability.fclk_can, + bitrate=bitrate, + sample_point=87.5, + ) + props_seg = 1 + self.gs_usb.set_timing( + prop_seg=props_seg, + phase_seg1=bit_timing.tseg1 - props_seg, + phase_seg2=bit_timing.tseg2, + sjw=bit_timing.sjw, + brp=bit_timing.brp, + ) self.gs_usb.start() super().__init__( From 0de8ca82f1c18afff438a7c0efba5f512ea6581e Mon Sep 17 00:00:00 2001 From: BroderickJack <39026705+BroderickJack@users.noreply.github.com> Date: Sat, 22 Jun 2024 07:03:11 -0400 Subject: [PATCH 1134/1235] Support CANdapter extended length arbitration ID (#1528) * Support CANdapter extended length arbitration ID * Update can/interfaces/slcan.py More efficient check of arbitration ID Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * Add description of x extended arbitration identifier * Add test for CANDapter extended arbitration ID * Update test/test_slcan.py Type fix in slcan test Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> * format black --------- Co-authored-by: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> --- can/interfaces/slcan.py | 5 ++++- test/test_slcan.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index f32d82fd0..f023e084c 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -215,7 +215,10 @@ def _recv_internal( if not string: pass - elif string[0] == "T": + elif string[0] in ( + "T", + "x", # x is an alternative extended message identifier for CANDapter + ): # extended frame canId = int(string[1:9], 16) dlc = int(string[9]) diff --git a/test/test_slcan.py b/test/test_slcan.py index f74207b9f..af7ae60c4 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -43,6 +43,16 @@ def test_recv_extended(self): self.assertEqual(msg.dlc, 2) self.assertSequenceEqual(msg.data, [0xAA, 0x55]) + # Ewert Energy Systems CANDapter specific + self.serial.write(b"x12ABCDEF2AA55\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 2) + self.assertSequenceEqual(msg.data, [0xAA, 0x55]) + def test_send_extended(self): msg = can.Message( arbitration_id=0x12ABCDEF, is_extended_id=True, data=[0xAA, 0x55] From 5508f533ab8767fd3b0e6cbdef97d4633fe24bfb Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:01:15 +0200 Subject: [PATCH 1135/1235] Update Changelog for 4.4.1 (#1802) * undo unnecessary changes * update CHANGELOG.md for 4.4.1 --- CHANGELOG.md | 23 +++++++++++++++++++++++ doc/interfaces/pcan.rst | 8 -------- doc/interfaces/socketcan.rst | 4 ---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 404b68c3e..0d371c104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +Version 4.4.1 +============= + +Bug Fixes +--------- +* Remove `abstractmethod` decorator from `Listener.stop()` (#1770, #1795) +* Fix `SizedRotatingLogger` file suffix bug (#1792, #1793) +* gs_usb: Use `BitTiming` class internally to configure bitrate (#1747, #1748) +* pcan: Fix unpack error in `PcanBus._detect_available_configs()` (#1767) +* socketcan: Improve error handling in `SocketcanBus.__init__()` (#1771) +* socketcan: Do not log exception on non-linux platforms (#1800) +* vector, kvaser: Activate channels after CAN filters were applied (#1413, #1708, #1796) + +Features +-------- + +* kvaser: Add support for non-ISO CAN FD (#1752) +* neovi: Return timestamps relative to epoch (#1789) +* slcan: Support CANdapter extended length arbitration ID (#1506, #1528) +* slcan: Add support for `listen_only` mode (#1496) +* vector: Add support for `listen_only` mode (#1764) + + Version 4.4.0 ============= diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index 88ab6838d..2f73dd3a7 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -37,14 +37,6 @@ Here is an example configuration file for using `PCAN-USB Date: Sun, 23 Jun 2024 14:23:06 +0200 Subject: [PATCH 1136/1235] bump version to 4.4.1 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index d0c19f89b..4499edfcc 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.4.0" +__version__ = "4.4.1" __all__ = [ "ASCReader", "ASCWriter", From 478f8b8749ce08906b0386d13db289cf98e3246e Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:41:14 +0200 Subject: [PATCH 1137/1235] set version to 4.4.2 (#1803) --- CHANGELOG.md | 2 +- can/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d371c104..1d2581d9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -Version 4.4.1 +Version 4.4.2 ============= Bug Fixes diff --git a/can/__init__.py b/can/__init__.py index 4499edfcc..53aec7160 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging from typing import Any, Dict -__version__ = "4.4.1" +__version__ = "4.4.2" __all__ = [ "ASCReader", "ASCWriter", From 2c90f9f8e548a204e531e427864da6b6240f0b9c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:46:12 +0200 Subject: [PATCH 1138/1235] improve TestBusConfig (#1804) --- can/util.py | 49 ++++++++++++++++++++-------------- test/test_util.py | 68 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/can/util.py b/can/util.py index 23ab142fb..7f2f824db 100644 --- a/can/util.py +++ b/can/util.py @@ -2,6 +2,7 @@ Utilities and configuration file parsing. """ +import contextlib import copy import functools import json @@ -243,26 +244,9 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: if not 0 < port < 65535: raise ValueError("Port config must be inside 0-65535 range!") - if config.get("timing", None) is None: - try: - if set(typechecking.BitTimingFdDict.__annotations__).issubset(config): - config["timing"] = can.BitTimingFd( - **{ - key: int(config[key]) - for key in typechecking.BitTimingFdDict.__annotations__ - }, - strict=False, - ) - elif set(typechecking.BitTimingDict.__annotations__).issubset(config): - config["timing"] = can.BitTiming( - **{ - key: int(config[key]) - for key in typechecking.BitTimingDict.__annotations__ - }, - strict=False, - ) - except (ValueError, TypeError): - pass + if "timing" not in config: + if timing := _dict2timing(config): + config["timing"] = timing if "fd" in config: config["fd"] = config["fd"] not in (0, False) @@ -270,6 +254,31 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: return cast(typechecking.BusConfig, config) +def _dict2timing(data: Dict[str, Any]) -> Union[BitTiming, BitTimingFd, None]: + """Try to instantiate a :class:`~can.BitTiming` or :class:`~can.BitTimingFd` from + a dictionary. Return `None` if not possible.""" + + with contextlib.suppress(ValueError, TypeError): + if set(typechecking.BitTimingFdDict.__annotations__).issubset(data): + return BitTimingFd( + **{ + key: int(data[key]) + for key in typechecking.BitTimingFdDict.__annotations__ + }, + strict=False, + ) + elif set(typechecking.BitTimingDict.__annotations__).issubset(data): + return BitTiming( + **{ + key: int(data[key]) + for key in typechecking.BitTimingDict.__annotations__ + }, + strict=False, + ) + + return None + + def set_logging_level(level_name: str) -> None: """Set the logging level for the `"can"` logger. diff --git a/test/test_util.py b/test/test_util.py index ac8c87d9e..f3524468b 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -5,6 +5,7 @@ import pytest +import can from can import BitTiming, BitTimingFd from can.exceptions import CanInitializationError from can.util import ( @@ -132,10 +133,7 @@ def _test_func3(a): class TestBusConfig(unittest.TestCase): - base_config = dict(interface="socketcan", bitrate=500_000) - port_alpha_config = dict(interface="socketcan", bitrate=500_000, port="fail123") - port_to_high_config = dict(interface="socketcan", bitrate=500_000, port="999999") - port_wrong_type_config = dict(interface="socketcan", bitrate=500_000, port=(1234,)) + base_config = {"interface": "socketcan", "bitrate": 500_000} def test_timing_can_use_int(self): """ @@ -147,17 +145,69 @@ def test_timing_can_use_int(self): _create_bus_config({**self.base_config, **timing_conf}) except TypeError as e: self.fail(e) + + def test_port_datatype(self): self.assertRaises( - ValueError, _create_bus_config, {**self.port_alpha_config, **timing_conf} + ValueError, _create_bus_config, {**self.base_config, "port": "fail123"} ) self.assertRaises( - ValueError, _create_bus_config, {**self.port_to_high_config, **timing_conf} + ValueError, _create_bus_config, {**self.base_config, "port": "999999"} ) self.assertRaises( - TypeError, - _create_bus_config, - {**self.port_wrong_type_config, **timing_conf}, + TypeError, _create_bus_config, {**self.base_config, "port": (1234,)} + ) + + try: + _create_bus_config({**self.base_config, "port": "1234"}) + except TypeError as e: + self.fail(e) + + def test_bit_timing_cfg(self): + can_cfg = _create_bus_config( + { + **self.base_config, + "f_clock": "8000000", + "brp": "1", + "tseg1": "5", + "tseg2": "2", + "sjw": "1", + "nof_samples": "1", + } + ) + timing = can_cfg["timing"] + assert isinstance(timing, can.BitTiming) + assert timing.f_clock == 8_000_000 + assert timing.brp == 1 + assert timing.tseg1 == 5 + assert timing.tseg2 == 2 + assert timing.sjw == 1 + + def test_bit_timing_fd_cfg(self): + canfd_cfg = _create_bus_config( + { + **self.base_config, + "f_clock": "80000000", + "nom_brp": "1", + "nom_tseg1": "119", + "nom_tseg2": "40", + "nom_sjw": "40", + "data_brp": "1", + "data_tseg1": "29", + "data_tseg2": "10", + "data_sjw": "10", + } ) + timing = canfd_cfg["timing"] + assert isinstance(timing, can.BitTimingFd) + assert timing.f_clock == 80_000_000 + assert timing.nom_brp == 1 + assert timing.nom_tseg1 == 119 + assert timing.nom_tseg2 == 40 + assert timing.nom_sjw == 40 + assert timing.data_brp == 1 + assert timing.data_tseg1 == 29 + assert timing.data_tseg2 == 10 + assert timing.data_sjw == 10 class TestChannel2Int(unittest.TestCase): From b552f1dd64f4cd8a561f83b306a31203c8990c9e Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 25 Jun 2024 12:07:26 +0200 Subject: [PATCH 1139/1235] Refactor CLI filter parsing, add tests (#1805) * refactor filter parsing, add tests * change chapter title to "Command Line Tools" * upper case --- can/logger.py | 75 ++++++++++++++++++++++++++++++--------------- can/typechecking.py | 15 +++++++++ can/viewer.py | 25 ++++++--------- doc/other-tools.rst | 2 +- doc/scripts.rst | 4 +-- test/test_logger.py | 35 +++++++++++++++++++-- test/test_viewer.py | 75 ++++++++++++++++++++++----------------------- 7 files changed, 147 insertions(+), 84 deletions(-) diff --git a/can/logger.py b/can/logger.py index 7d1ae6f66..35c3db20b 100644 --- a/can/logger.py +++ b/can/logger.py @@ -3,17 +3,26 @@ import re import sys from datetime import datetime -from typing import TYPE_CHECKING, Any, Dict, List, Sequence, Tuple, Union +from typing import ( + TYPE_CHECKING, + Any, + Dict, + List, + Optional, + Sequence, + Tuple, + Union, +) import can +from can import Bus, BusState, Logger, SizedRotatingLogger +from can.typechecking import TAdditionalCliArgs from can.util import cast_from_string -from . import Bus, BusState, Logger, SizedRotatingLogger -from .typechecking import CanFilter, CanFilters - if TYPE_CHECKING: from can.io import BaseRotatingLogger from can.io.generic import MessageWriter + from can.typechecking import CanFilter def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: @@ -60,10 +69,7 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: def _append_filter_argument( - parser: Union[ - argparse.ArgumentParser, - argparse._ArgumentGroup, - ], + parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup], *args: str, **kwargs: Any, ) -> None: @@ -78,16 +84,17 @@ def _append_filter_argument( "\n ~ (matches when & mask !=" " can_id & mask)" "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" - "\n python -m can.viewer -f 100:7FC 200:7F0" + "\n python -m can.viewer --filter 100:7FC 200:7F0" "\nNote that the ID and mask are always interpreted as hex values", metavar="{:,~}", nargs=argparse.ONE_OR_MORE, - default="", + action=_CanFilterAction, + dest="can_filters", **kwargs, ) -def _create_bus(parsed_args: Any, **kwargs: Any) -> can.BusABC: +def _create_bus(parsed_args: argparse.Namespace, **kwargs: Any) -> can.BusABC: logging_level_names = ["critical", "error", "warning", "info", "debug", "subdebug"] can.set_logging_level(logging_level_names[min(5, parsed_args.verbosity)]) @@ -100,16 +107,27 @@ def _create_bus(parsed_args: Any, **kwargs: Any) -> can.BusABC: config["fd"] = True if parsed_args.data_bitrate: config["data_bitrate"] = parsed_args.data_bitrate + if getattr(parsed_args, "can_filters", None): + config["can_filters"] = parsed_args.can_filters return Bus(parsed_args.channel, **config) -def _parse_filters(parsed_args: Any) -> CanFilters: - can_filters: List[CanFilter] = [] +class _CanFilterAction(argparse.Action): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, + ) -> None: + if not isinstance(values, list): + raise argparse.ArgumentError(None, "Invalid filter argument") + + print(f"Adding filter(s): {values}") + can_filters: List[CanFilter] = [] - if parsed_args.filter: - print(f"Adding filter(s): {parsed_args.filter}") - for filt in parsed_args.filter: + for filt in values: if ":" in filt: parts = filt.split(":") can_id = int(parts[0], base=16) @@ -122,12 +140,10 @@ def _parse_filters(parsed_args: Any) -> CanFilters: raise argparse.ArgumentError(None, "Invalid filter argument") can_filters.append({"can_id": can_id, "can_mask": can_mask}) - return can_filters + setattr(namespace, self.dest, can_filters) -def _parse_additional_config( - unknown_args: Sequence[str], -) -> Dict[str, Union[str, int, float, bool]]: +def _parse_additional_config(unknown_args: Sequence[str]) -> TAdditionalCliArgs: for arg in unknown_args: if not re.match(r"^--[a-zA-Z\-]*?=\S*?$", arg): raise ValueError(f"Parsing argument {arg} failed") @@ -142,12 +158,18 @@ def _split_arg(_arg: str) -> Tuple[str, str]: return args -def main() -> None: +def _parse_logger_args( + args: List[str], +) -> Tuple[argparse.Namespace, TAdditionalCliArgs]: + """Parse command line arguments for logger script.""" + parser = argparse.ArgumentParser( description="Log CAN traffic, printing messages to stdout or to a " "given file.", ) + # Generate the standard arguments: + # Channel, bitrate, data_bitrate, interface, app_name, CAN-FD support _create_base_argument_parser(parser) parser.add_argument( @@ -200,13 +222,18 @@ def main() -> None: ) # print help message when no arguments were given - if len(sys.argv) < 2: + if not args: parser.print_help(sys.stderr) raise SystemExit(errno.EINVAL) - results, unknown_args = parser.parse_known_args() + results, unknown_args = parser.parse_known_args(args) additional_config = _parse_additional_config([*results.extra_args, *unknown_args]) - bus = _create_bus(results, can_filters=_parse_filters(results), **additional_config) + return results, additional_config + + +def main() -> None: + results, additional_config = _parse_logger_args(sys.argv[1:]) + bus = _create_bus(results, **additional_config) if results.active: bus.state = BusState.ACTIVE diff --git a/can/typechecking.py b/can/typechecking.py index 89f978a1a..284dd8aba 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -2,8 +2,16 @@ """ import gzip +import struct +import sys import typing +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + + if typing.TYPE_CHECKING: import os @@ -40,6 +48,13 @@ class CanFilterExtended(typing.TypedDict): BusConfig = typing.NewType("BusConfig", typing.Dict[str, typing.Any]) +# Used by CLI scripts +TAdditionalCliArgs: TypeAlias = typing.Dict[str, typing.Union[str, int, float, bool]] +TDataStructs: TypeAlias = typing.Dict[ + typing.Union[int, typing.Tuple[int, ...]], + typing.Union[struct.Struct, typing.Tuple, None], +] + class AutoDetectedConfig(typing.TypedDict): interface: str diff --git a/can/viewer.py b/can/viewer.py index 07752327d..45c313b07 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -27,17 +27,16 @@ import struct import sys import time -from typing import Dict, List, Tuple, Union +from typing import Dict, List, Tuple from can import __version__ - -from .logger import ( +from can.logger import ( _append_filter_argument, _create_base_argument_parser, _create_bus, _parse_additional_config, - _parse_filters, ) +from can.typechecking import TAdditionalCliArgs, TDataStructs logger = logging.getLogger("can.viewer") @@ -391,7 +390,9 @@ def _fill_text(self, text, width, indent): return super()._fill_text(text, width, indent) -def parse_args(args: List[str]) -> Tuple: +def _parse_viewer_args( + args: List[str], +) -> Tuple[argparse.Namespace, TDataStructs, TAdditionalCliArgs]: # Parse command line arguments parser = argparse.ArgumentParser( "python -m can.viewer", @@ -489,8 +490,6 @@ def parse_args(args: List[str]) -> Tuple: parsed_args, unknown_args = parser.parse_known_args(args) - can_filters = _parse_filters(parsed_args) - # Dictionary used to convert between Python values and C structs represented as Python strings. # If the value is 'None' then the message does not contain any data package. # @@ -511,9 +510,7 @@ def parse_args(args: List[str]) -> Tuple: # similarly the values # are divided by the value in order to convert from real units to raw integer values. - data_structs: Dict[ - Union[int, Tuple[int, ...]], Union[struct.Struct, Tuple, None] - ] = {} + data_structs: TDataStructs = {} if parsed_args.decode: if os.path.isfile(parsed_args.decode[0]): with open(parsed_args.decode[0], encoding="utf-8") as f: @@ -544,16 +541,12 @@ def parse_args(args: List[str]) -> Tuple: additional_config = _parse_additional_config( [*parsed_args.extra_args, *unknown_args] ) - return parsed_args, can_filters, data_structs, additional_config + return parsed_args, data_structs, additional_config def main() -> None: - parsed_args, can_filters, data_structs, additional_config = parse_args(sys.argv[1:]) - - if can_filters: - additional_config.update({"can_filters": can_filters}) + parsed_args, data_structs, additional_config = _parse_viewer_args(sys.argv[1:]) bus = _create_bus(parsed_args, **additional_config) - curses.wrapper(CanViewer, bus, data_structs) # type: ignore[attr-defined,unused-ignore] diff --git a/doc/other-tools.rst b/doc/other-tools.rst index eab3c4f43..db06812ca 100644 --- a/doc/other-tools.rst +++ b/doc/other-tools.rst @@ -1,4 +1,4 @@ -Other CAN bus tools +Other CAN Bus Tools =================== In order to keep the project maintainable, the scope of the package is limited to providing common diff --git a/doc/scripts.rst b/doc/scripts.rst index e3a59a409..520b19177 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -1,5 +1,5 @@ -Scripts -======= +Command Line Tools +================== The following modules are callable from ``python-can``. diff --git a/test/test_logger.py b/test/test_logger.py index 083e4d19c..32fc987b4 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -16,8 +16,6 @@ import can import can.logger -from .config import * - class TestLoggerScriptModule(unittest.TestCase): def setUp(self) -> None: @@ -108,6 +106,39 @@ def test_log_virtual_sizedlogger(self): self.assertSuccessfullCleanup() self.mock_logger_sized.assert_called_once() + def test_parse_logger_args(self): + args = self.baseargs + [ + "--bitrate", + "250000", + "--fd", + "--data_bitrate", + "2000000", + "--receive-own-messages=True", + ] + results, additional_config = can.logger._parse_logger_args(args[1:]) + assert results.interface == "virtual" + assert results.bitrate == 250_000 + assert results.fd is True + assert results.data_bitrate == 2_000_000 + assert additional_config["receive_own_messages"] is True + + def test_parse_can_filters(self): + expected_can_filters = [{"can_id": 0x100, "can_mask": 0x7FC}] + results, additional_config = can.logger._parse_logger_args( + ["--filter", "100:7FC", "--bitrate", "250000"] + ) + assert results.can_filters == expected_can_filters + + def test_parse_can_filters_list(self): + expected_can_filters = [ + {"can_id": 0x100, "can_mask": 0x7FC}, + {"can_id": 0x200, "can_mask": 0x7F0}, + ] + results, additional_config = can.logger._parse_logger_args( + ["--filter", "100:7FC", "200:7F0", "--bitrate", "250000"] + ) + assert results.can_filters == expected_can_filters + def test_parse_additional_config(self): unknown_args = [ "--app-name=CANalyzer", diff --git a/test/test_viewer.py b/test/test_viewer.py index ecc594915..3bd32b25a 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -36,7 +36,7 @@ import pytest import can -from can.viewer import CanViewer, parse_args +from can.viewer import CanViewer, _parse_viewer_args # Allow the curses module to be missing (e.g. on PyPy on Windows) try: @@ -397,19 +397,19 @@ def test_pack_unpack(self): ) def test_parse_args(self): - parsed_args, _, _, _ = parse_args(["-b", "250000"]) + parsed_args, _, _ = _parse_viewer_args(["-b", "250000"]) self.assertEqual(parsed_args.bitrate, 250000) - parsed_args, _, _, _ = parse_args(["--bitrate", "500000"]) + parsed_args, _, _ = _parse_viewer_args(["--bitrate", "500000"]) self.assertEqual(parsed_args.bitrate, 500000) - parsed_args, _, _, _ = parse_args(["-c", "can0"]) + parsed_args, _, _ = _parse_viewer_args(["-c", "can0"]) self.assertEqual(parsed_args.channel, "can0") - parsed_args, _, _, _ = parse_args(["--channel", "PCAN_USBBUS1"]) + parsed_args, _, _ = _parse_viewer_args(["--channel", "PCAN_USBBUS1"]) self.assertEqual(parsed_args.channel, "PCAN_USBBUS1") - parsed_args, _, data_structs, _ = parse_args(["-d", "100: Date: Wed, 26 Jun 2024 23:00:12 -0700 Subject: [PATCH 1140/1235] Resolve AttributeError within NicanError (#1806) Co-authored-by: Vijayakumar Subbiah --- can/interfaces/nican.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index f4b1a37f0..8a2efade7 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -89,7 +89,7 @@ class NicanError(CanError): def __init__(self, function, error_code: int, arguments) -> None: super().__init__( - message=f"{function} failed: {get_error_message(self.error_code)}", + message=f"{function} failed: {get_error_message(error_code)}", error_code=error_code, ) From 5cc25344e801b023f6155ef865f8c77f1abda446 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Wed, 3 Jul 2024 22:06:07 +1000 Subject: [PATCH 1141/1235] socketcand: show actual result as well as expectation (#1807) --- can/interfaces/socketcand/socketcand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 2cfdf54e5..7a2cc6fd0 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -323,7 +323,7 @@ def _expect_msg(self, msg): if self.__tcp_tune: self.__socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) if not ascii_msg == msg: - raise can.CanError(f"{msg} message expected!") + raise can.CanError(f"Expected '{msg}' got: '{ascii_msg}'") def send(self, msg, timeout=None): """Transmit a message to the CAN bus. From c87db06a456e0c391018fedf3f18bb67f5c9e27c Mon Sep 17 00:00:00 2001 From: Federico Spada <160390475+FedericoSpada@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:40:56 +0200 Subject: [PATCH 1142/1235] Added extra info for Kvaser dongles (#1797) * Added extra info for Kvaser dongles Changed Kvaser auto-detection function to return additional info regarding the dongles found, such as Serial Number, Name and Dongle Channel number. In this way it's possible to discern between different physical dongles connected to the PC and if they are Virtual Channels or not. * Updated key value for dongle name Changed "name" into "device_name" as suggested by python-can owner. * Fixed retrocompatibility problem Changed " with ' for retrocompatibility problem with older Python versions. * Fixed failed Test I've updated the format using Black and modified test_kvaser.py\test_available_configs to consider the new _detect_available_configs output. * Fixed missed changes --- can/interfaces/kvaser/canlib.py | 23 +++++++++++++++-------- test/test_kvaser.py | 16 ++++++++++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index e7409b668..5501c311d 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -479,6 +479,7 @@ def __init__( log.info("Found %d available channels", num_channels) for idx in range(num_channels): channel_info = get_channel_info(idx) + channel_info = f'{channel_info["device_name"]}, S/N {channel_info["serial"]} (#{channel_info["dongle_channel"]})' log.info("%d: %s", idx, channel_info) if idx == channel: self.channel_info = channel_info @@ -766,16 +767,19 @@ def get_stats(self) -> structures.BusStatistics: @staticmethod def _detect_available_configs(): - num_channels = ctypes.c_int(0) + config_list = [] + try: + num_channels = ctypes.c_int(0) canGetNumberOfChannels(ctypes.byref(num_channels)) + + for channel in range(0, int(num_channels.value)): + info = get_channel_info(channel) + + config_list.append({"interface": "kvaser", "channel": channel, **info}) except (CANLIBError, NameError): pass - - return [ - {"interface": "kvaser", "channel": channel} - for channel in range(num_channels.value) - ] + return config_list def get_channel_info(channel): @@ -802,8 +806,11 @@ def get_channel_info(channel): ctypes.sizeof(number), ) - name_decoded = name.value.decode("ascii", errors="replace") - return f"{name_decoded}, S/N {serial.value} (#{number.value + 1})" + return { + "device_name": name.value.decode("ascii", errors="replace"), + "serial": serial.value, + "dongle_channel": number.value + 1, + } init_kvaser_library() diff --git a/test/test_kvaser.py b/test/test_kvaser.py index d7b43b55b..8d18976aa 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -163,8 +163,20 @@ def test_recv_standard(self): def test_available_configs(self): configs = canlib.KvaserBus._detect_available_configs() expected = [ - {"interface": "kvaser", "channel": 0}, - {"interface": "kvaser", "channel": 1}, + { + "interface": "kvaser", + "channel": 0, + "dongle_channel": 1, + "device_name": "", + "serial": 0, + }, + { + "interface": "kvaser", + "channel": 1, + "dongle_channel": 1, + "device_name": "", + "serial": 0, + }, ] self.assertListEqual(configs, expected) From acc90c6072d7ece0a4afbab2b9dd3d1ba2857275 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 11 Jul 2024 09:00:43 +0200 Subject: [PATCH 1143/1235] Stop notifier in examples (#1814) --- examples/print_notifier.py | 6 ++++-- examples/vcan_filtered.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/print_notifier.py b/examples/print_notifier.py index cb4a02799..8d55ca1dc 100755 --- a/examples/print_notifier.py +++ b/examples/print_notifier.py @@ -1,19 +1,21 @@ #!/usr/bin/env python import time + import can def main(): - with can.Bus(receive_own_messages=True) as bus: + with can.Bus(interface="virtual", receive_own_messages=True) as bus: print_listener = can.Printer() - can.Notifier(bus, [print_listener]) + notifier = can.Notifier(bus, [print_listener]) bus.send(can.Message(arbitration_id=1, is_extended_id=True)) bus.send(can.Message(arbitration_id=2, is_extended_id=True)) bus.send(can.Message(arbitration_id=1, is_extended_id=False)) time.sleep(1.0) + notifier.stop() if __name__ == "__main__": diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index f022759fa..9c67390ab 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -25,6 +25,7 @@ def main(): bus.send(can.Message(arbitration_id=1, is_extended_id=False)) time.sleep(1.0) + notifier.stop() if __name__ == "__main__": From 78e4f81f9a3bec1ce4e32b5e85e7b5545053a579 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:16:03 +0200 Subject: [PATCH 1144/1235] use setuptools_scm (#1810) --- .github/workflows/ci.yml | 2 ++ can/__init__.py | 6 +++++- doc/conf.py | 7 ++++--- doc/development.rst | 2 +- pyproject.toml | 7 ++++--- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4afb4c1ca..de5601ebf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,6 +161,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # fetch tags for setuptools-scm - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/can/__init__.py b/can/__init__.py index 53aec7160..324803b9e 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -5,10 +5,11 @@ messages on a can bus. """ +import contextlib import logging +from importlib.metadata import PackageNotFoundError, version from typing import Any, Dict -__version__ = "4.4.2" __all__ = [ "ASCReader", "ASCWriter", @@ -124,6 +125,9 @@ from .thread_safe_bus import ThreadSafeBus from .util import set_logging_level +with contextlib.suppress(PackageNotFoundError): + __version__ = version("python-can") + log = logging.getLogger("can") rc: Dict[str, Any] = {} diff --git a/doc/conf.py b/doc/conf.py index 54318883f..1322a2f83 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -9,6 +9,7 @@ import ctypes import os import sys +from importlib.metadata import version as get_version from unittest.mock import MagicMock # If extensions (or modules to document with autodoc) are in another directory, @@ -16,7 +17,6 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath("..")) -import can # pylint: disable=wrong-import-position from can import ctypesutil # pylint: disable=wrong-import-position # -- General configuration ----------------------------------------------------- @@ -27,9 +27,10 @@ # |version| and |release|, also used in various other places throughout the # built documents. # +# The full version, including alpha/beta/rc tags. +release: str = get_version("python-can") # The short X.Y version. -version = can.__version__.split("-", maxsplit=1)[0] -release = can.__version__ +version = ".".join(release.split(".")[:2]) # General information about the project. project = "python-can" diff --git a/doc/development.rst b/doc/development.rst index f635ffc06..c83f0f213 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -129,7 +129,7 @@ Manual release steps (deprecated) --------------------------------- - Create a temporary virtual environment. +- Create a new tag in the repository. Use `semantic versioning `__. - Build with ``pipx run build`` -- Create and upload the distribution: ``python setup.py sdist bdist_wheel``. - Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl``. - Upload with twine ``twine upload dist/python-can-X.Y.Z*``. diff --git a/pyproject.toml b/pyproject.toml index 08b501c60..99a54a5df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 67.7"] +requires = ["setuptools >= 67.7", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] @@ -84,8 +84,6 @@ mf4 = ["asammdf>=6.0.0"] [tool.setuptools.dynamic] readme = { file = "README.rst" } -version = { attr = "can.__version__" } - [tool.setuptools.package-data] "*" = ["README.rst", "CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.md"] doc = ["*.*"] @@ -95,6 +93,9 @@ can = ["py.typed"] [tool.setuptools.packages.find] include = ["can*"] +[tool.setuptools_scm] +# can be empty if no extra settings are needed, presence enables setuptools_scm + [tool.mypy] warn_return_any = true warn_unused_configs = true From a598e22f4d2d8897c943a7ebb909c0c2892fef5d Mon Sep 17 00:00:00 2001 From: RitheeshBaradwaj Date: Mon, 8 Jul 2024 22:38:51 +0900 Subject: [PATCH 1145/1235] fix: handle "Start of measurement" line in ASCReader --- CONTRIBUTORS.txt | 3 ++- can/io/asc.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index ae7792e42..389d26412 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -81,4 +81,5 @@ Felix Nieuwenhuizen @fjburgos @pkess @felixn -@Tbruno25 \ No newline at end of file +@Tbruno25 +@RitheeshBaradwaj diff --git a/can/io/asc.py b/can/io/asc.py index 2955ad7f9..af9812c43 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -275,6 +275,11 @@ def __iter__(self) -> Generator[Message, None, None]: ) continue + # Handle the "Start of measurement" line + if line.startswith("0.000000") and "Start of measurement" in line: + # Skip this line as it's just an indicator + continue + if not ASC_MESSAGE_REGEX.match(line): # line might be a comment, chip status, # J1939 message or some other unsupported event From d34b2d62fe103d203967e8ce2d3481d89e9afc18 Mon Sep 17 00:00:00 2001 From: RitheeshBaradwaj Date: Tue, 9 Jul 2024 08:44:41 +0900 Subject: [PATCH 1146/1235] chore: use regex to identify start line --- can/io/asc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index af9812c43..3a14f0a88 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -276,7 +276,7 @@ def __iter__(self) -> Generator[Message, None, None]: continue # Handle the "Start of measurement" line - if line.startswith("0.000000") and "Start of measurement" in line: + if re.match(r"^\d+\.\d+\s+Start of measurement", line): # Skip this line as it's just an indicator continue From aeff58dc9504c4a1b8d6cdb0a2a08f7a2e7384fa Mon Sep 17 00:00:00 2001 From: Ben Gardiner Date: Sat, 10 Aug 2024 05:44:59 -0400 Subject: [PATCH 1147/1235] gs_usb command-line support (and documentation updates and stability fixes) (#1790) * gs_usb, doc: correct docs to indicate libusbK not libusb-win32 * gs_usb, interface: fix bug on second .start() by repeating in shutdown() v2: updates as per review https://github.com/hardbyte/python-can/pull/1790#pullrequestreview-2121408123 by @zariiii9003 * gs_usb, interface: set a default bitrate of 500k (like many other interfaces) * gs_usb, interface: support this interface on command-line with e.g. can.logger by treating channel like index when all other arguments are missing * gs_usb: improve docs, don't use dev reference before assignment as requested in review https://github.com/hardbyte/python-can/pull/1790#pullrequestreview-2121408123 by @zariiii9003 * gs_usb: fix bug in transmit when frame timestamp is set (causes failure to pack 64bit host timestamp into gs_usb field) --- can/interfaces/gs_usb.py | 27 +++++++++++++++++++++++++-- doc/interfaces/gs_usb.rst | 7 +++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 5efbd3da6..6268350ee 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -17,7 +17,7 @@ class GsUsbBus(can.BusABC): def __init__( self, channel, - bitrate, + bitrate: int = 500_000, index=None, bus=None, address=None, @@ -33,11 +33,16 @@ def __init__( :param can_filters: not supported :param bitrate: CAN network bandwidth (bits/s) """ + self._is_shutdown = False if (index is not None) and ((bus or address) is not None): raise CanInitializationError( "index and bus/address cannot be used simultaneously" ) + if index is None and address is None and bus is None: + index = channel + + self._index = None if index is not None: devs = GsUsb.scan() if len(devs) <= index: @@ -45,6 +50,7 @@ def __init__( f"Cannot find device {index}. Devices found: {len(devs)}" ) gs_usb = devs[index] + self._index = index else: gs_usb = GsUsb.find(bus=bus, address=address) if not gs_usb: @@ -68,6 +74,7 @@ def __init__( brp=bit_timing.brp, ) self.gs_usb.start() + self._bitrate = bitrate super().__init__( channel=channel, @@ -102,7 +109,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): frame = GsUsbFrame() frame.can_id = can_id frame.can_dlc = msg.dlc - frame.timestamp_us = int(msg.timestamp * 1000000) + frame.timestamp_us = 0 # timestamp frame field is only useful on receive frame.data = list(msg.data) try: @@ -154,5 +161,21 @@ def _recv_internal( return msg, False def shutdown(self): + if self._is_shutdown: + return + super().shutdown() self.gs_usb.stop() + if self._index is not None: + # Avoid errors on subsequent __init() by repeating the .scan() and .start() that would otherwise fail + # the next time the device is opened in __init__() + devs = GsUsb.scan() + if self._index < len(devs): + gs_usb = devs[self._index] + try: + gs_usb.set_bitrate(self._bitrate) + gs_usb.start() + gs_usb.stop() + except usb.core.USBError: + pass + self._is_shutdown = True diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index 3a869911c..e9c0131c5 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -8,13 +8,16 @@ and candleLight USB CAN interfaces. Install: ``pip install "python-can[gs_usb]"`` -Usage: pass device ``index`` (starting from 0) if using automatic device detection: +Usage: pass device ``index`` or ``channel`` (starting from 0) if using automatic device detection: :: import can + import usb + dev = usb.core.find(idVendor=0x1D50, idProduct=0x606F) bus = can.Bus(interface="gs_usb", channel=dev.product, index=0, bitrate=250000) + bus = can.Bus(interface="gs_usb", channel=0, bitrate=250000) # same Alternatively, pass ``bus`` and ``address`` to open a specific device. The parameters can be got by ``pyusb`` as shown below: @@ -50,7 +53,7 @@ Windows, Linux and Mac. ``libusb`` must be installed. On Windows a tool such as `Zadig `_ can be used to set the USB device driver to - ``libusb-win32``. + ``libusbK``. Supplementary Info From 971c3319f6543e461cb6b9205424226d3426222e Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 10 Aug 2024 16:26:45 +0200 Subject: [PATCH 1148/1235] Test on Python 3.13 (#1833) * update linters * test python 3.13 * make pywin32 optional, refactor broadcastmanager.py * fix pylint * update pytest to fix python3.12 CI * fix test * fix deprecation warning * make _Pywin32Event private * try to fix PyPy * add classifier for 3.13 * reduce scope of send_lock context manager --- .github/workflows/ci.yml | 8 +- can/broadcastmanager.py | 158 ++++++++++++++-------- can/interfaces/usb2can/serial_selector.py | 4 +- can/io/trc.py | 6 +- can/notifier.py | 19 ++- pyproject.toml | 10 +- test/network_test.py | 2 +- test/simplecyclic_test.py | 4 +- tox.ini | 5 +- 9 files changed, 133 insertions(+), 83 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de5601ebf..cd535d4e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: "3.10", "3.11", "3.12", + "3.13", "pypy-3.8", "pypy-3.9", ] @@ -34,12 +35,6 @@ jobs: include: - { python-version: "3.8", os: "macos-13", experimental: false } - { python-version: "3.9", os: "macos-13", experimental: false } - # uncomment when python 3.13.0 alpha is available - #include: - # # Only test on a single configuration while there are just pre-releases - # - os: ubuntu-latest - # experimental: true - # python-version: "3.13.0-alpha - 3.13.0" fail-fast: false steps: - uses: actions/checkout@v4 @@ -47,6 +42,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index a610b7a8a..6ca9c61b3 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -7,10 +7,21 @@ import abc import logging +import platform import sys import threading import time -from typing import TYPE_CHECKING, Callable, Final, Optional, Sequence, Tuple, Union +import warnings +from typing import ( + TYPE_CHECKING, + Callable, + Final, + Optional, + Sequence, + Tuple, + Union, + cast, +) from can import typechecking from can.message import Message @@ -19,22 +30,61 @@ from can.bus import BusABC -# try to import win32event for event-based cyclic send task (needs the pywin32 package) -USE_WINDOWS_EVENTS = False -try: - import pywintypes - import win32event +log = logging.getLogger("can.bcm") +NANOSECONDS_IN_SECOND: Final[int] = 1_000_000_000 - # Python 3.11 provides a more precise sleep implementation on Windows, so this is not necessary. - # Put version check here, so mypy does not complain about `win32event` not being defined. - if sys.version_info < (3, 11): - USE_WINDOWS_EVENTS = True -except ImportError: - pass -log = logging.getLogger("can.bcm") +class _Pywin32Event: + handle: int -NANOSECONDS_IN_SECOND: Final[int] = 1_000_000_000 + +class _Pywin32: + def __init__(self) -> None: + import pywintypes # pylint: disable=import-outside-toplevel,import-error + import win32event # pylint: disable=import-outside-toplevel,import-error + + self.pywintypes = pywintypes + self.win32event = win32event + + def create_timer(self) -> _Pywin32Event: + try: + event = self.win32event.CreateWaitableTimerEx( + None, + None, + self.win32event.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + self.win32event.TIMER_ALL_ACCESS, + ) + except ( + AttributeError, + OSError, + self.pywintypes.error, # pylint: disable=no-member + ): + event = self.win32event.CreateWaitableTimer(None, False, None) + + return cast(_Pywin32Event, event) + + def set_timer(self, event: _Pywin32Event, period_ms: int) -> None: + self.win32event.SetWaitableTimer(event.handle, 0, period_ms, None, None, False) + + def stop_timer(self, event: _Pywin32Event) -> None: + self.win32event.SetWaitableTimer(event.handle, 0, 0, None, None, False) + + def wait_0(self, event: _Pywin32Event) -> None: + self.win32event.WaitForSingleObject(event.handle, 0) + + def wait_inf(self, event: _Pywin32Event) -> None: + self.win32event.WaitForSingleObject( + event.handle, + self.win32event.INFINITE, + ) + + +PYWIN32: Optional[_Pywin32] = None +if sys.platform == "win32" and sys.version_info < (3, 11): + try: + PYWIN32 = _Pywin32() + except ImportError: + pass class CyclicTask(abc.ABC): @@ -254,25 +304,30 @@ def __init__( self.on_error = on_error self.modifier_callback = modifier_callback - if USE_WINDOWS_EVENTS: - self.period_ms = int(round(period * 1000, 0)) - try: - self.event = win32event.CreateWaitableTimerEx( - None, - None, - win32event.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, - win32event.TIMER_ALL_ACCESS, - ) - except (AttributeError, OSError, pywintypes.error): - self.event = win32event.CreateWaitableTimer(None, False, None) + self.period_ms = int(round(period * 1000, 0)) + + self.event: Optional[_Pywin32Event] = None + if PYWIN32: + self.event = PYWIN32.create_timer() + elif ( + sys.platform == "win32" + and sys.version_info < (3, 11) + and platform.python_implementation() == "CPython" + ): + warnings.warn( + f"{self.__class__.__name__} may achieve better timing accuracy " + f"if the 'pywin32' package is installed.", + RuntimeWarning, + stacklevel=1, + ) self.start() def stop(self) -> None: self.stopped = True - if USE_WINDOWS_EVENTS: + if self.event and PYWIN32: # Reset and signal any pending wait by setting the timer to 0 - win32event.SetWaitableTimer(self.event.handle, 0, 0, None, None, False) + PYWIN32.stop_timer(self.event) def start(self) -> None: self.stopped = False @@ -281,10 +336,8 @@ def start(self) -> None: self.thread = threading.Thread(target=self._run, name=name) self.thread.daemon = True - if USE_WINDOWS_EVENTS: - win32event.SetWaitableTimer( - self.event.handle, 0, self.period_ms, None, None, False - ) + if self.event and PYWIN32: + PYWIN32.set_timer(self.event, self.period_ms) self.thread.start() @@ -292,43 +345,40 @@ def _run(self) -> None: msg_index = 0 msg_due_time_ns = time.perf_counter_ns() - if USE_WINDOWS_EVENTS: + if self.event and PYWIN32: # Make sure the timer is non-signaled before entering the loop - win32event.WaitForSingleObject(self.event.handle, 0) + PYWIN32.wait_0(self.event) while not self.stopped: if self.end_time is not None and time.perf_counter() >= self.end_time: break - # Prevent calling bus.send from multiple threads - with self.send_lock: - try: - if self.modifier_callback is not None: - self.modifier_callback(self.messages[msg_index]) + try: + if self.modifier_callback is not None: + self.modifier_callback(self.messages[msg_index]) + with self.send_lock: + # Prevent calling bus.send from multiple threads self.bus.send(self.messages[msg_index]) - except Exception as exc: # pylint: disable=broad-except - log.exception(exc) + except Exception as exc: # pylint: disable=broad-except + log.exception(exc) - # stop if `on_error` callback was not given - if self.on_error is None: - self.stop() - raise exc + # stop if `on_error` callback was not given + if self.on_error is None: + self.stop() + raise exc - # stop if `on_error` returns False - if not self.on_error(exc): - self.stop() - break + # stop if `on_error` returns False + if not self.on_error(exc): + self.stop() + break - if not USE_WINDOWS_EVENTS: + if not self.event: msg_due_time_ns += self.period_ns msg_index = (msg_index + 1) % len(self.messages) - if USE_WINDOWS_EVENTS: - win32event.WaitForSingleObject( - self.event.handle, - win32event.INFINITE, - ) + if self.event and PYWIN32: + PYWIN32.wait_inf(self.event) else: # Compensate for the time it takes to send the message delay_ns = msg_due_time_ns - time.perf_counter_ns() diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index c2e48ff97..92a3a07a2 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -9,7 +9,9 @@ try: import win32com.client except ImportError: - log.warning("win32com.client module required for usb2can") + log.warning( + "win32com.client module required for usb2can. Install the 'pywin32' package." + ) raise diff --git a/can/io/trc.py b/can/io/trc.py index f568f93a5..f0595c23e 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -343,7 +343,9 @@ def _write_header_v1_0(self, start_time: datetime) -> None: self.file.writelines(line + "\n" for line in lines) def _write_header_v2_1(self, start_time: datetime) -> None: - header_time = start_time - datetime(year=1899, month=12, day=30) + header_time = start_time - datetime( + year=1899, month=12, day=30, tzinfo=timezone.utc + ) lines = [ ";$FILEVERSION=2.1", f";$STARTTIME={header_time/timedelta(days=1)}", @@ -399,7 +401,7 @@ def _format_message_init(self, msg, channel): def write_header(self, timestamp: float) -> None: # write start of file header - start_time = datetime.utcfromtimestamp(timestamp) + start_time = datetime.fromtimestamp(timestamp, timezone.utc) if self.file_version == TRCFileVersion.V1_0: self._write_header_v1_0(start_time) diff --git a/can/notifier.py b/can/notifier.py index 34fcf74fa..088f0802e 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -7,7 +7,7 @@ import logging import threading import time -from typing import Awaitable, Callable, Iterable, List, Optional, Union, cast +from typing import Any, Awaitable, Callable, Iterable, List, Optional, Union from can.bus import BusABC from can.listener import Listener @@ -110,16 +110,13 @@ def stop(self, timeout: float = 5) -> None: def _rx_thread(self, bus: BusABC) -> None: # determine message handling callable early, not inside while loop - handle_message = cast( - Callable[[Message], None], - ( - self._on_message_received - if self._loop is None - else functools.partial( - self._loop.call_soon_threadsafe, self._on_message_received - ) - ), - ) + if self._loop: + handle_message: Callable[[Message], Any] = functools.partial( + self._loop.call_soon_threadsafe, + self._on_message_received, # type: ignore[arg-type] + ) + else: + handle_message = self._on_message_received while self._running: try: diff --git a/pyproject.toml b/pyproject.toml index 99a54a5df..8b76bc5e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ dependencies = [ "packaging >= 23.1", "typing_extensions>=3.10.0.0", "msgpack~=1.0.0; platform_system != 'Windows'", - "pywin32>=305; platform_system == 'Windows' and platform_python_implementation == 'CPython'", ] requires-python = ">=3.8" license = { text = "LGPL v3" } @@ -35,6 +34,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Embedded Systems", @@ -61,10 +61,11 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==3.2.*", - "ruff==0.4.8", - "black==24.4.*", - "mypy==1.10.*", + "ruff==0.5.7", + "black==24.8.*", + "mypy==1.11.*", ] +pywin32 = ["pywin32>=305"] seeedstudio = ["pyserial>=3.0"] serial = ["pyserial~=3.0"] neovi = ["filelock", "python-ics>=2.12"] @@ -171,6 +172,7 @@ known-first-party = ["can"] [tool.pylint] disable = [ + "c-extension-no-member", "cyclic-import", "duplicate-code", "fixme", diff --git a/test/network_test.py b/test/network_test.py index b0fcba37f..50070ef40 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -84,7 +84,7 @@ def testProducerConsumer(self): ready = threading.Event() msg_read = threading.Event() - self.server_bus = can.interface.Bus(channel=channel) + self.server_bus = can.interface.Bus(channel=channel, interface="virtual") t = threading.Thread(target=self.producer, args=(ready, msg_read)) t.start() diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 21e88e9f0..34d29b8b6 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -154,7 +154,7 @@ def test_stopping_perodic_tasks(self): def test_restart_perodic_tasks(self): period = 0.01 - safe_timeout = period * 5 + safe_timeout = period * 5 if not IS_PYPY else 1.0 msg = can.Message( is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] @@ -241,7 +241,7 @@ def test_modifier_callback(self) -> None: msg_list: List[can.Message] = [] def increment_first_byte(msg: can.Message) -> None: - msg.data[0] += 1 + msg.data[0] = (msg.data[0] + 1) % 256 original_msg = can.Message( is_extended_id=False, arbitration_id=0x123, data=[0] * 8 diff --git a/tox.ini b/tox.ini index 477b1d4fc..1ca07a33f 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ isolated_build = true [testenv] deps = - pytest==7.3.* + pytest==8.3.* pytest-timeout==2.1.* coveralls==3.3.1 pytest-cov==4.0.0 @@ -11,7 +11,8 @@ deps = hypothesis~=6.35.0 pyserial~=3.5 parameterized~=0.8 - asammdf>=6.0;platform_python_implementation=="CPython" and python_version < "3.12" + asammdf>=6.0; platform_python_implementation=="CPython" and python_version<"3.13" + pywin32>=305; platform_system=="Windows" and platform_python_implementation=="CPython" and python_version<"3.13" commands = pytest {posargs} From 30601c70808f3feed533caf76da86b515a9a7d48 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 10 Aug 2024 18:08:40 +0200 Subject: [PATCH 1149/1235] fix slcan tests (#1834) --- test/test_slcan.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/test/test_slcan.py b/test/test_slcan.py index af7ae60c4..e1531e500 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -5,7 +5,7 @@ import serial -import can +import can.interfaces.slcan from .config import IS_PYPY @@ -58,9 +58,8 @@ def test_send_extended(self): arbitration_id=0x12ABCDEF, is_extended_id=True, data=[0xAA, 0x55] ) self.bus.send(msg) - expected = b"T12ABCDEF2AA55\r" - data = self.serial.read(len(expected)) - self.assertEqual(data, expected) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) def test_recv_standard(self): self.serial.write(b"t4563112233\r") @@ -77,9 +76,8 @@ def test_send_standard(self): arbitration_id=0x456, is_extended_id=False, data=[0x11, 0x22, 0x33] ) self.bus.send(msg) - expected = b"t4563112233\r" - data = self.serial.read(len(expected)) - self.assertEqual(data, expected) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) def test_recv_standard_remote(self): self.serial.write(b"r1238\r") @@ -95,9 +93,8 @@ def test_send_standard_remote(self): arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, dlc=8 ) self.bus.send(msg) - expected = b"r1238\r" - data = self.serial.read(len(expected)) - self.assertEqual(data, expected) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) def test_recv_extended_remote(self): self.serial.write(b"R12ABCDEF6\r") @@ -113,9 +110,8 @@ def test_send_extended_remote(self): arbitration_id=0x12ABCDEF, is_extended_id=True, is_remote_frame=True, dlc=6 ) self.bus.send(msg) - expected = b"R12ABCDEF6\r" - data = self.serial.read(len(expected)) - self.assertEqual(data, expected) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) def test_partial_recv(self): self.serial.write(b"T12ABCDEF") From 80164c134d6545e9281f7069f1e695e0499785c0 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:51:32 +0200 Subject: [PATCH 1150/1235] Undo #1772 (#1837) --- .github/workflows/ci.yml | 8 -------- doc/conf.py | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd535d4e6..e71df9aa6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,14 +27,6 @@ jobs: "pypy-3.8", "pypy-3.9", ] - # Python 3.9 is on macos-13 but not macos-latest (macos-14-arm64) - # https://github.com/actions/setup-python/issues/696#issuecomment-1637587760 - exclude: - - { python-version: "3.8", os: "macos-latest", experimental: false } - - { python-version: "3.9", os: "macos-latest", experimental: false } - include: - - { python-version: "3.8", os: "macos-13", experimental: false } - - { python-version: "3.9", os: "macos-13", experimental: false } fail-fast: false steps: - uses: actions/checkout@v4 diff --git a/doc/conf.py b/doc/conf.py index 1322a2f83..34ce385cb 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -68,7 +68,7 @@ graphviz_output_format = "png" # 'svg' # The suffix of source filenames. -source_suffix = ".rst" +source_suffix = {".rst": "restructuredtext"} # The encoding of source files. # source_encoding = 'utf-8-sig' From 3738c4f24f2ed75c0100553a8f2be89b3e75a7fd Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:41:32 +0200 Subject: [PATCH 1151/1235] Replace PyPy3.8 with PyPy3.10 (#1838) --- .github/workflows/ci.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e71df9aa6..b3e90b445 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,8 +24,8 @@ jobs: "3.11", "3.12", "3.13", - "pypy-3.8", "pypy-3.9", + "pypy-3.10", ] fail-fast: false steps: diff --git a/pyproject.toml b/pyproject.toml index 8b76bc5e6..ce2b52f2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==3.2.*", - "ruff==0.5.7", + "ruff==0.6.0", "black==24.8.*", "mypy==1.11.*", ] From e291874d9f1e17f6a206d2fa9460a34450c03299 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:59:24 +0200 Subject: [PATCH 1152/1235] add zlgcan to docs (#1839) --- doc/plugin-interface.rst | 3 +++ pyproject.toml | 1 + 2 files changed, 4 insertions(+) diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index 4a08ee9a7..d3e6115fd 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -73,8 +73,11 @@ The table below lists interface drivers that can be added by installing addition +----------------------------+-------------------------------------------------------+ | `python-can-sontheim`_ | CAN Driver for Sontheim CAN interfaces (e.g. CANfox) | +----------------------------+-------------------------------------------------------+ +| `zlgcan-driver-py`_ | Python wrapper for zlgcan-driver-rs | ++----------------------------+-------------------------------------------------------+ .. _python-can-canine: https://github.com/tinymovr/python-can-canine .. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector .. _python-can-remote: https://github.com/christiansandberg/python-can-remote .. _python-can-sontheim: https://github.com/MattWoodhead/python-can-sontheim +.. _zlgcan-driver-py: https://github.com/zhuyu4839/zlgcan-driver diff --git a/pyproject.toml b/pyproject.toml index ce2b52f2e..8beb99f5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,7 @@ pcan = ["uptime~=3.0.1"] remote = ["python-can-remote"] sontheim = ["python-can-sontheim>=0.1.2"] canine = ["python-can-canine>=0.2.2"] +zlgcan = ["zlgcan-driver-py"] viewer = [ "windows-curses; platform_system == 'Windows' and platform_python_implementation=='CPython'" ] From c7121310253c42c6410a70bc8b4f27a51b8b4411 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Thu, 29 Aug 2024 13:03:38 -0400 Subject: [PATCH 1153/1235] ThreadBasedCyclicSendTask using WIN32 API cannot handle period smaller than 1 ms (#1847) Using a win32 SetWaitableTimer with a period of `0` means that the timer will only be signaled once. This will make the `ThreadBasedCyclicSendTask` to only send one message. --- can/broadcastmanager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 6ca9c61b3..0fa94c82c 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -308,6 +308,9 @@ def __init__( self.event: Optional[_Pywin32Event] = None if PYWIN32: + if self.period_ms == 0: + # A period of 0 would mean that the timer is signaled only once + raise ValueError("The period cannot be smaller than 0.001 (1 ms)") self.event = PYWIN32.create_timer() elif ( sys.platform == "win32" From 7302127c88f157b837eae0633eb416250d07a850 Mon Sep 17 00:00:00 2001 From: Tomas Bures Date: Fri, 6 Sep 2024 13:05:45 +0200 Subject: [PATCH 1154/1235] Fix for #1849 (PCAN fails when PCAN_ERROR_ILLDATA is read via ReadFD) (#1850) * When there is an invalid frame on CAN bus (in our case CAN FD), PCAN first reports result PCAN_ERROR_ILLDATA and then it send the error frame. If the PCAN_ERROR_ILLDATA is not ignored, python-can throws an exception. This fix add the ignore on the PCAN_ERROR_ILLDATA. * Fix for ruff error `can/interfaces/pcan/pcan.py:5:1: I001 [*] Import block is un-sorted or un-formatted` Added comment explaining why to ignore the PCAN_ERROR_ILLDATA. --- can/interfaces/pcan/pcan.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 52dbb49b0..d0372a83c 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -43,6 +43,7 @@ PCAN_DICT_STATUS, PCAN_ERROR_BUSHEAVY, PCAN_ERROR_BUSLIGHT, + PCAN_ERROR_ILLDATA, PCAN_ERROR_OK, PCAN_ERROR_QRCVEMPTY, PCAN_FD_PARAMETER_LIST, @@ -555,6 +556,12 @@ def _recv_internal( elif result & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY): log.warning(self._get_formatted_error(result)) + elif result == PCAN_ERROR_ILLDATA: + # When there is an invalid frame on CAN bus (in our case CAN FD), PCAN first reports result PCAN_ERROR_ILLDATA + # and then it sends the error frame. If the PCAN_ERROR_ILLDATA is not ignored, python-can throws an exception. + # So we ignore any PCAN_ERROR_ILLDATA results here. + pass + else: raise PcanCanOperationError(self._get_formatted_error(result)) From 757370d48104ee14c51867ab9e27c525f5302bb5 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Fri, 13 Sep 2024 08:58:58 -0400 Subject: [PATCH 1155/1235] Faster Message string representation (#1858) Improved the speed of data conversion to hex string --- can/message.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/can/message.py b/can/message.py index c26733087..6dc6a83bd 100644 --- a/can/message.py +++ b/can/message.py @@ -130,12 +130,11 @@ def __str__(self) -> str: field_strings.append(flag_string) field_strings.append(f"DL: {self.dlc:2d}") - data_strings = [] + data_strings = "" if self.data is not None: - for index in range(0, min(self.dlc, len(self.data))): - data_strings.append(f"{self.data[index]:02x}") + data_strings = self.data[: min(self.dlc, len(self.data))].hex(" ") if data_strings: # if not empty - field_strings.append(" ".join(data_strings).ljust(24, " ")) + field_strings.append(data_strings.ljust(24, " ")) else: field_strings.append(" " * 24) From d4f3954b726e857528d605ea88888aab322f1750 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Fri, 13 Sep 2024 11:14:05 -0400 Subject: [PATCH 1156/1235] ASCWriter speed improvement (#1856) * ASCWriter speed improvement Changed how the message data is converted to string. This results in and 100% speed improvement. On my PC, before the change, logging 10000 messages was taking ~16 seconds and this change drop it to ~8 seconds. With this change, the ASCWriter is still one of the slowest writer we have in Python-can. * Update asc.py * Update logformats_test.py * Create single_frame.asc * Update logformats_test.py * Update test/logformats_test.py Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --------- Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/io/asc.py | 10 +++++----- test/data/single_frame.asc | 7 +++++++ test/logformats_test.py | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 test/data/single_frame.asc diff --git a/can/io/asc.py b/can/io/asc.py index 3a14f0a88..deb7d429e 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -9,7 +9,7 @@ import logging import re from datetime import datetime -from typing import Any, Dict, Final, Generator, List, Optional, TextIO, Union +from typing import Any, Dict, Final, Generator, Optional, TextIO, Union from ..message import Message from ..typechecking import StringPathLike @@ -439,10 +439,10 @@ def on_message_received(self, msg: Message) -> None: return if msg.is_remote_frame: dtype = f"r {msg.dlc:x}" # New after v8.5 - data: List[str] = [] + data: str = "" else: dtype = f"d {msg.dlc:x}" - data = [f"{byte:02X}" for byte in msg.data] + data = msg.data.hex(" ").upper() arb_id = f"{msg.arbitration_id:X}" if msg.is_extended_id: arb_id += "x" @@ -462,7 +462,7 @@ def on_message_received(self, msg: Message) -> None: esi=1 if msg.error_state_indicator else 0, dlc=len2dlc(msg.dlc), data_length=len(msg.data), - data=" ".join(data), + data=data, message_duration=0, message_length=0, flags=flags, @@ -478,6 +478,6 @@ def on_message_received(self, msg: Message) -> None: id=arb_id, dir="Rx" if msg.is_rx else "Tx", dtype=dtype, - data=" ".join(data), + data=data, ) self.log_event(serialized, msg.timestamp) diff --git a/test/data/single_frame.asc b/test/data/single_frame.asc new file mode 100644 index 000000000..cae9d1b4d --- /dev/null +++ b/test/data/single_frame.asc @@ -0,0 +1,7 @@ +date Sat Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +Begin Triggerblock Sat Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 0.000000 1 123x Rx d 40 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F +End TriggerBlock diff --git a/test/logformats_test.py b/test/logformats_test.py index 71d392aa8..0fbe065d2 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -651,6 +651,33 @@ def test_write_millisecond_handling(self): self.assertEqual(expected_file.read_text(), actual_file.read_text()) + def test_write(self): + now = datetime( + year=2017, month=9, day=30, hour=15, minute=6, second=13, microsecond=191456 + ) + + # We temporarily set the locale to C to ensure test reproducibility + with override_locale(category=locale.LC_TIME, locale_str="C"): + # We mock datetime.now during ASCWriter __init__ for reproducibility + # Unfortunately, now() is a readonly attribute, so we mock datetime + with patch("can.io.asc.datetime") as mock_datetime: + mock_datetime.now.return_value = now + writer = can.ASCWriter(self.test_file_name) + + msg = can.Message( + timestamp=now.timestamp(), + arbitration_id=0x123, + data=range(64), + ) + + with writer: + writer.on_message_received(msg) + + actual_file = Path(self.test_file_name) + expected_file = self._get_logfile_location("single_frame.asc") + + self.assertEqual(expected_file.read_text(), actual_file.read_text()) + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From c5c18dc275613c6af648fd24978b3119344375e7 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:10:12 +0200 Subject: [PATCH 1157/1235] Fix regex in _parse_additional_config() (#1868) --- can/logger.py | 2 +- test/test_logger.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/can/logger.py b/can/logger.py index 35c3db20b..81e9527f0 100644 --- a/can/logger.py +++ b/can/logger.py @@ -145,7 +145,7 @@ def __call__( def _parse_additional_config(unknown_args: Sequence[str]) -> TAdditionalCliArgs: for arg in unknown_args: - if not re.match(r"^--[a-zA-Z\-]*?=\S*?$", arg): + if not re.match(r"^--[a-zA-Z][a-zA-Z0-9\-]*=\S*?$", arg): raise ValueError(f"Parsing argument {arg} failed") def _split_arg(_arg: str) -> Tuple[str, str]: diff --git a/test/test_logger.py b/test/test_logger.py index 32fc987b4..10df2557b 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -146,6 +146,7 @@ def test_parse_additional_config(self): "--receive-own-messages=True", "--false-boolean=False", "--offset=1.5", + "--tseg1-abr=127", ] parsed_args = can.logger._parse_additional_config(unknown_args) @@ -170,6 +171,9 @@ def test_parse_additional_config(self): assert "offset" in parsed_args assert parsed_args["offset"] == 1.5 + assert "tseg1_abr" in parsed_args + assert parsed_args["tseg1_abr"] == 127 + with pytest.raises(ValueError): can.logger._parse_additional_config(["--wrong-format"]) From 950e4b40854ee97e3b771b4049a074af1935f5bc Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Tue, 27 Aug 2024 09:49:41 -0400 Subject: [PATCH 1158/1235] Use typing_extensions.TypedDict on python < 3.12 for pydantic support Reference: https://docs.pydantic.dev/2.8/errors/usage_errors/#typed-dict-version --- can/typechecking.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/can/typechecking.py b/can/typechecking.py index 284dd8aba..b0d1c22ac 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -11,17 +11,22 @@ else: from typing_extensions import TypeAlias +if sys.version_info >= (3, 12): + from typing import TypedDict +else: + from typing_extensions import TypedDict + if typing.TYPE_CHECKING: import os -class CanFilter(typing.TypedDict): +class CanFilter(TypedDict): can_id: int can_mask: int -class CanFilterExtended(typing.TypedDict): +class CanFilterExtended(TypedDict): can_id: int can_mask: int extended: bool @@ -56,7 +61,7 @@ class CanFilterExtended(typing.TypedDict): ] -class AutoDetectedConfig(typing.TypedDict): +class AutoDetectedConfig(TypedDict): interface: str channel: Channel @@ -64,7 +69,7 @@ class AutoDetectedConfig(typing.TypedDict): ReadableBytesLike = typing.Union[bytes, bytearray, memoryview] -class BitTimingDict(typing.TypedDict): +class BitTimingDict(TypedDict): f_clock: int brp: int tseg1: int @@ -73,7 +78,7 @@ class BitTimingDict(typing.TypedDict): nof_samples: int -class BitTimingFdDict(typing.TypedDict): +class BitTimingFdDict(TypedDict): f_clock: int nom_brp: int nom_tseg1: int From 7a3d23fa3ba114a6cd005c726e7815732c566f6e Mon Sep 17 00:00:00 2001 From: Sebastian Wolf Date: Tue, 8 Oct 2024 16:10:46 +0200 Subject: [PATCH 1159/1235] Add autostart option to BusABC.send_periodic() (#1853) * Add autostart option (kwarg) to BusABC.send_periodic() to fix issue #1848 * Fix for #1849 (PCAN fails when PCAN_ERROR_ILLDATA is read via ReadFD) (#1850) * When there is an invalid frame on CAN bus (in our case CAN FD), PCAN first reports result PCAN_ERROR_ILLDATA and then it send the error frame. If the PCAN_ERROR_ILLDATA is not ignored, python-can throws an exception. This fix add the ignore on the PCAN_ERROR_ILLDATA. * Fix for ruff error `can/interfaces/pcan/pcan.py:5:1: I001 [*] Import block is un-sorted or un-formatted` Added comment explaining why to ignore the PCAN_ERROR_ILLDATA. * Format with black to pass checks * Do not ignore autostart parameter for Bus.send_periodic() on IXXAT devices * Do not ignore autostart parameter for Bis.send_periodic() on socketcan devices * Fix double start socketcan periodic * Fix link methods in docstring for start() methods of the tasks can.broadcastmanager.CyclicTask.start * Change the behaviour of autostart parameter in socketcan implementation of CyclicSendTask to not call _tx_setup() method instead of adding a parameter to it. * Fix code style (max 100 chars per line) Fix wrong docstring reference. --------- Co-authored-by: Tomas Bures Co-authored-by: Sebastian Wolf --- can/broadcastmanager.py | 4 +++- can/bus.py | 15 ++++++++++++++- can/interfaces/ixxat/canlib.py | 3 ++- can/interfaces/ixxat/canlib_vcinpl.py | 22 +++++++++++++++++++--- can/interfaces/ixxat/canlib_vcinpl2.py | 22 +++++++++++++++++++--- can/interfaces/socketcan/socketcan.py | 20 ++++++++++++++++---- 6 files changed, 73 insertions(+), 13 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 0fa94c82c..bc65d3a47 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -276,6 +276,7 @@ def __init__( period: float, duration: Optional[float] = None, on_error: Optional[Callable[[Exception], bool]] = None, + autostart: bool = True, modifier_callback: Optional[Callable[[Message], None]] = None, ) -> None: """Transmits `messages` with a `period` seconds for `duration` seconds on a `bus`. @@ -324,7 +325,8 @@ def __init__( stacklevel=1, ) - self.start() + if autostart: + self.start() def stop(self) -> None: self.stopped = True diff --git a/can/bus.py b/can/bus.py index 954c78c5f..a12808ab6 100644 --- a/can/bus.py +++ b/can/bus.py @@ -215,6 +215,7 @@ def send_periodic( period: float, duration: Optional[float] = None, store_task: bool = True, + autostart: bool = True, modifier_callback: Optional[Callable[[Message], None]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -237,6 +238,10 @@ def send_periodic( :param store_task: If True (the default) the task will be attached to this Bus instance. Disable to instead manage tasks manually. + :param autostart: + If True (the default) the sending task will immediately start after creation. + Otherwise, the task has to be started by calling the + tasks :meth:`~can.RestartableCyclicTaskABC.start` method on it. :param modifier_callback: Function which should be used to modify each message's data before sending. The callback modifies the :attr:`~can.Message.data` of the @@ -272,7 +277,9 @@ def send_periodic( # Create a backend specific task; will be patched to a _SelfRemovingCyclicTask later task = cast( _SelfRemovingCyclicTask, - self._send_periodic_internal(msgs, period, duration, modifier_callback), + self._send_periodic_internal( + msgs, period, duration, autostart, modifier_callback + ), ) # we wrap the task's stop method to also remove it from the Bus's list of tasks periodic_tasks = self._periodic_tasks @@ -299,6 +306,7 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + autostart: bool = True, modifier_callback: Optional[Callable[[Message], None]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. @@ -312,6 +320,10 @@ def _send_periodic_internal( :param duration: The duration between sending each message at the given rate. If no duration is provided, the task will continue indefinitely. + :param autostart: + If True (the default) the sending task will immediately start after creation. + Otherwise, the task has to be started by calling the + tasks :meth:`~can.RestartableCyclicTaskABC.start` method on it. :return: A started task instance. Note the task can be stopped (and depending on the backend modified) by calling the @@ -328,6 +340,7 @@ def _send_periodic_internal( messages=msgs, period=period, duration=duration, + autostart=autostart, modifier_callback=modifier_callback, ) return task diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 330ccdcd9..a1693aeed 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -155,10 +155,11 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + autostart: bool = True, modifier_callback: Optional[Callable[[Message], None]] = None, ) -> CyclicSendTaskABC: return self.bus._send_periodic_internal( - msgs, period, duration, modifier_callback + msgs, period, duration, autostart, modifier_callback ) def shutdown(self) -> None: diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 709780ceb..0579d0942 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -793,6 +793,7 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + autostart: bool = True, modifier_callback: Optional[Callable[[Message], None]] = None, ) -> CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" @@ -807,7 +808,12 @@ def _send_periodic_internal( self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) return CyclicSendTask( - self._scheduler, msgs, period, duration, self._scheduler_resolution + self._scheduler, + msgs, + period, + duration, + self._scheduler_resolution, + autostart=autostart, ) # fallback to thread based cyclic task @@ -821,6 +827,7 @@ def _send_periodic_internal( msgs=msgs, period=period, duration=duration, + autostart=autostart, modifier_callback=modifier_callback, ) @@ -863,7 +870,15 @@ def state(self) -> BusState: class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" - def __init__(self, scheduler, msgs, period, duration, resolution): + def __init__( + self, + scheduler, + msgs, + period, + duration, + resolution, + autostart: bool = True, + ): super().__init__(msgs, period, duration) if len(self.messages) != 1: raise ValueError( @@ -883,7 +898,8 @@ def __init__(self, scheduler, msgs, period, duration, resolution): self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc for i, b in enumerate(self.messages[0].data): self._msg.abData[i] = b - self.start() + if autostart: + self.start() def start(self): """Start transmitting message (add to list if needed).""" diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 18b3a1e57..5872f76b9 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -937,6 +937,7 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + autostart: bool = True, modifier_callback: Optional[Callable[[Message], None]] = None, ) -> CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" @@ -953,7 +954,12 @@ def _send_periodic_internal( ) # TODO: confirm _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) return CyclicSendTask( - self._scheduler, msgs, period, duration, self._scheduler_resolution + self._scheduler, + msgs, + period, + duration, + self._scheduler_resolution, + autostart=autostart, ) # fallback to thread based cyclic task @@ -967,6 +973,7 @@ def _send_periodic_internal( msgs=msgs, period=period, duration=duration, + autostart=autostart, modifier_callback=modifier_callback, ) @@ -983,7 +990,15 @@ def shutdown(self): class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): """A message in the cyclic transmit list.""" - def __init__(self, scheduler, msgs, period, duration, resolution): + def __init__( + self, + scheduler, + msgs, + period, + duration, + resolution, + autostart: bool = True, + ): super().__init__(msgs, period, duration) if len(self.messages) != 1: raise ValueError( @@ -1003,7 +1018,8 @@ def __init__(self, scheduler, msgs, period, duration, resolution): self._msg.uMsgInfo.Bits.dlc = self.messages[0].dlc for i, b in enumerate(self.messages[0].data): self._msg.abData[i] = b - self.start() + if autostart: + self.start() def start(self): """Start transmitting message (add to list if needed).""" diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 03bd8c3f8..40da0d094 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -327,6 +327,7 @@ def __init__( messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + autostart: bool = True, ) -> None: """Construct and :meth:`~start` a task. @@ -349,10 +350,13 @@ def __init__( self.bcm_socket = bcm_socket self.task_id = task_id - self._tx_setup(self.messages) + if autostart: + self._tx_setup(self.messages) def _tx_setup( - self, messages: Sequence[Message], raise_if_task_exists: bool = True + self, + messages: Sequence[Message], + raise_if_task_exists: bool = True, ) -> None: # Create a low level packed frame to pass to the kernel body = bytearray() @@ -813,6 +817,7 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + autostart: bool = True, modifier_callback: Optional[Callable[[Message], None]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -823,13 +828,17 @@ def _send_periodic_internal( :class:`CyclicSendTask` within BCM provides flexibility to schedule CAN messages sending with the same CAN ID, but different CAN data. - :param messages: + :param msgs: The message(s) to be sent periodically. :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. + :param autostart: + If True (the default) the sending task will immediately start after creation. + Otherwise, the task has to be started by calling the + tasks :meth:`~can.RestartableCyclicTaskABC.start` method on it. :raises ValueError: If task identifier passed to :class:`CyclicSendTask` can't be used @@ -854,7 +863,9 @@ def _send_periodic_internal( msgs_channel = str(msgs[0].channel) if msgs[0].channel else None bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) task_id = self._get_next_task_id() - task = CyclicSendTask(bcm_socket, task_id, msgs, period, duration) + task = CyclicSendTask( + bcm_socket, task_id, msgs, period, duration, autostart=autostart + ) return task # fallback to thread based cyclic task @@ -868,6 +879,7 @@ def _send_periodic_internal( msgs=msgs, period=period, duration=duration, + autostart=autostart, modifier_callback=modifier_callback, ) From a39e63eac51ae7fbb872fa997aa4730113ba766c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:09:39 +0200 Subject: [PATCH 1160/1235] Add tox environment for doctest (#1870) * test * use py312 to build docs --- .github/workflows/ci.yml | 18 +++--------------- .readthedocs.yml | 2 +- doc/development.rst | 3 +-- doc/scripts.rst | 4 ++++ tox.ini | 15 ++++++++++++++- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3e90b445..6291994a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,10 +35,6 @@ jobs: with: python-version: ${{ matrix.python-version }} allow-prereleases: true - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox - name: Setup SocketCAN if: ${{ matrix.os == 'ubuntu-latest' }} run: | @@ -46,7 +42,7 @@ jobs: sudo ./test/open_vcan.sh - name: Test with pytest via tox run: | - tox -e gh + pipx run tox -e gh env: # SocketCAN tests currently fail with PyPy because it does not support raw CAN sockets # See: https://foss.heptapod.net/pypy/pypy/-/issues/3809 @@ -131,18 +127,10 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e .[canalystii,gs_usb,mf4] - pip install -r doc/doc-requirements.txt + python-version: "3.12" - name: Build documentation run: | - python -m sphinx -Wan --keep-going doc build - - name: Run doctest - run: | - python -m sphinx -b doctest -W --keep-going doc build + pipx run tox -e docs build: name: Packaging diff --git a/.readthedocs.yml b/.readthedocs.yml index 32be9c7b5..dad8c28db 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,7 +9,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.10" + python: "3.12" # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/doc/development.rst b/doc/development.rst index c83f0f213..a8332eeb6 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -48,8 +48,7 @@ The unit tests can be run with:: The documentation can be built with:: - pip install -r doc/doc-requirements.txt - python -m sphinx -an doc build + pipx run tox -e docs The linters can be run with:: diff --git a/doc/scripts.rst b/doc/scripts.rst index 520b19177..2d59b7528 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -12,12 +12,14 @@ Command line help, called with ``--help``: .. command-output:: python -m can.logger -h + :shell: can.player ---------- .. command-output:: python -m can.player -h + :shell: can.viewer @@ -52,9 +54,11 @@ By default the ``can.viewer`` uses the :doc:`/interfaces/socketcan` interface. A The full usage page can be seen below: .. command-output:: python -m can.viewer -h + :shell: can.logconvert -------------- .. command-output:: python -m can.logconvert -h + :shell: diff --git a/tox.ini b/tox.ini index 1ca07a33f..1d4e88166 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,4 @@ [tox] -isolated_build = true [testenv] deps = @@ -30,6 +29,20 @@ passenv = PY_COLORS TEST_SOCKETCAN +[testenv:docs] +description = Build and test the documentation +basepython = py312 +deps = + -r doc/doc-requirements.txt + gs-usb + +extras = + canalystii + +commands = + python -m sphinx -b html -Wan --keep-going doc build + python -m sphinx -b doctest -W --keep-going doc build + [pytest] testpaths = test From abe0db58a2db6052f8d6f9cd4ebf2b2c9f2c1ba4 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:21:44 +0200 Subject: [PATCH 1161/1235] Set end_time in ThreadBasedCyclicSendTask.start() (#1871) --- can/broadcastmanager.py | 9 ++++++--- test/simplecyclic_test.py | 32 ++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index bc65d3a47..319fb0f53 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -182,6 +182,7 @@ def __init__( """ super().__init__(messages, period) self.duration = duration + self.end_time: Optional[float] = None class RestartableCyclicTaskABC(CyclicSendTaskABC, abc.ABC): @@ -299,9 +300,6 @@ def __init__( self.send_lock = lock self.stopped = True self.thread: Optional[threading.Thread] = None - self.end_time: Optional[float] = ( - time.perf_counter() + duration if duration else None - ) self.on_error = on_error self.modifier_callback = modifier_callback @@ -341,6 +339,10 @@ def start(self) -> None: self.thread = threading.Thread(target=self._run, name=name) self.thread.daemon = True + self.end_time: Optional[float] = ( + time.perf_counter() + self.duration if self.duration else None + ) + if self.event and PYWIN32: PYWIN32.set_timer(self.event, self.period_ms) @@ -356,6 +358,7 @@ def _run(self) -> None: while not self.stopped: if self.end_time is not None and time.perf_counter() >= self.end_time: + self.stop() break try: diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 34d29b8b6..7116efc9d 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -155,14 +155,21 @@ def test_stopping_perodic_tasks(self): def test_restart_perodic_tasks(self): period = 0.01 safe_timeout = period * 5 if not IS_PYPY else 1.0 + duration = 0.3 msg = can.Message( is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] ) + def _read_all_messages(_bus: can.interfaces.virtual.VirtualBus) -> None: + sleep(safe_timeout) + while not _bus.queue.empty(): + _bus.recv(timeout=period) + sleep(safe_timeout) + with can.ThreadSafeBus(interface="virtual", receive_own_messages=True) as bus: task = bus.send_periodic(msg, period) - self.assertIsInstance(task, can.broadcastmanager.RestartableCyclicTaskABC) + self.assertIsInstance(task, can.broadcastmanager.ThreadBasedCyclicSendTask) # Test that the task is sending messages sleep(safe_timeout) @@ -170,10 +177,27 @@ def test_restart_perodic_tasks(self): # Stop the task and check that messages are no longer being sent bus.stop_all_periodic_tasks(remove_tasks=False) + _read_all_messages(bus) + assert bus.queue.empty(), "messages should not have been transmitted" + + # Restart the task and check that messages are being sent again + task.start() sleep(safe_timeout) - while not bus.queue.empty(): - bus.recv(timeout=period) - sleep(safe_timeout) + assert not bus.queue.empty(), "messages should have been transmitted" + + # Stop the task and check that messages are no longer being sent + bus.stop_all_periodic_tasks(remove_tasks=False) + _read_all_messages(bus) + assert bus.queue.empty(), "messages should not have been transmitted" + + # Restart the task with limited duration and wait until it stops + task.duration = duration + task.start() + sleep(duration + safe_timeout) + assert task.stopped + assert time.time() > task.end_time + assert not bus.queue.empty(), "messages should have been transmitted" + _read_all_messages(bus) assert bus.queue.empty(), "messages should not have been transmitted" # Restart the task and check that messages are being sent again From ff01a8b073f77c184f157e7060d03ce74891e2ba Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 18 Oct 2024 11:14:26 +0200 Subject: [PATCH 1162/1235] Update msgpack dependency (#1875) --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8beb99f5e..f2b6ac04f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "wrapt~=1.10", "packaging >= 23.1", "typing_extensions>=3.10.0.0", - "msgpack~=1.0.0; platform_system != 'Windows'", + "msgpack~=1.1.0; platform_system != 'Windows'", ] requires-python = ">=3.8" license = { text = "LGPL v3" } @@ -61,9 +61,9 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==3.2.*", - "ruff==0.6.0", - "black==24.8.*", - "mypy==1.11.*", + "ruff==0.7.0", + "black==24.10.*", + "mypy==1.12.*", ] pywin32 = ["pywin32>=305"] seeedstudio = ["pyserial>=3.0"] From 5be89ecf2212c5ebc373ab230d86a686cf9e3911 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:34:49 +0200 Subject: [PATCH 1163/1235] Fix Kvaser timestamp (#1878) * get timestamp offset after canBusOn * update comment --- can/interfaces/kvaser/canlib.py | 36 ++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 5501c311d..51a77a567 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -573,6 +573,23 @@ def __init__( ) canSetBusOutputControl(self._write_handle, can_driver_mode) + self._is_filtered = False + super().__init__( + channel=channel, + can_filters=can_filters, + **kwargs, + ) + + # activate channel after CAN filters were applied + log.debug("Go on bus") + if not self.single_handle: + canBusOn(self._read_handle) + canBusOn(self._write_handle) + + # timestamp must be set after bus is online, otherwise kvReadTimer may return erroneous values + self._timestamp_offset = self._update_timestamp_offset() + + def _update_timestamp_offset(self) -> float: timer = ctypes.c_uint(0) try: if time.get_clock_info("time").resolution > 1e-5: @@ -580,28 +597,15 @@ def __init__( kvReadTimer(self._read_handle, ctypes.byref(timer)) current_perfcounter = time.perf_counter() now = ts + (current_perfcounter - perfcounter) - self._timestamp_offset = now - (timer.value * TIMESTAMP_FACTOR) + return now - (timer.value * TIMESTAMP_FACTOR) else: kvReadTimer(self._read_handle, ctypes.byref(timer)) - self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) + return time.time() - (timer.value * TIMESTAMP_FACTOR) except Exception as exc: # timer is usually close to 0 log.info(str(exc)) - self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) - - self._is_filtered = False - super().__init__( - channel=channel, - can_filters=can_filters, - **kwargs, - ) - - # activate channel after CAN filters were applied - log.debug("Go on bus") - if not self.single_handle: - canBusOn(self._read_handle) - canBusOn(self._write_handle) + return time.time() - (timer.value * TIMESTAMP_FACTOR) def _apply_filters(self, filters): if filters and len(filters) == 1: From 5ae913e8459cb87d7f45326707b16a850b0a3c99 Mon Sep 17 00:00:00 2001 From: Riccardo Belli <61554895+belliriccardo@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:18:04 +0100 Subject: [PATCH 1164/1235] Added Netronic's CANdo and CANdoISO to plugin list (#1887) --- doc/plugin-interface.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index d3e6115fd..bfdedf3c6 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -75,9 +75,13 @@ The table below lists interface drivers that can be added by installing addition +----------------------------+-------------------------------------------------------+ | `zlgcan-driver-py`_ | Python wrapper for zlgcan-driver-rs | +----------------------------+-------------------------------------------------------+ +| `python-can-cando`_ | Python wrapper for Netronics' CANdo and CANdoISO | ++----------------------------+-------------------------------------------------------+ .. _python-can-canine: https://github.com/tinymovr/python-can-canine .. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector .. _python-can-remote: https://github.com/christiansandberg/python-can-remote .. _python-can-sontheim: https://github.com/MattWoodhead/python-can-sontheim .. _zlgcan-driver-py: https://github.com/zhuyu4839/zlgcan-driver +.. _python-can-cando: https://github.com/belliriccardo/python-can-cando + From 9a766ce01575955341767b925ce937f7aa95d301 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:06:45 +0100 Subject: [PATCH 1165/1235] Fix CI (#1889) --- .github/workflows/ci.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6291994a0..0ddb3028c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,10 @@ jobs: with: python-version: ${{ matrix.python-version }} allow-prereleases: true + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox - name: Setup SocketCAN if: ${{ matrix.os == 'ubuntu-latest' }} run: | @@ -42,7 +46,7 @@ jobs: sudo ./test/open_vcan.sh - name: Test with pytest via tox run: | - pipx run tox -e gh + tox -e gh env: # SocketCAN tests currently fail with PyPy because it does not support raw CAN sockets # See: https://foss.heptapod.net/pypy/pypy/-/issues/3809 @@ -128,9 +132,13 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.12" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox - name: Build documentation run: | - pipx run tox -e docs + tox -e docs build: name: Packaging From 805f3fb04e75e0590881d15d62bee5fbe86feb82 Mon Sep 17 00:00:00 2001 From: cssedev <187732701+cssedev@users.noreply.github.com> Date: Fri, 15 Nov 2024 03:46:58 +0100 Subject: [PATCH 1166/1235] MF4 reader updates (#1892) --- can/io/mf4.py | 416 +++++++++++++++++++++++++++----------------------- 1 file changed, 229 insertions(+), 187 deletions(-) diff --git a/can/io/mf4.py b/can/io/mf4.py index 042bf8765..4f5336b42 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -5,16 +5,18 @@ the ASAM MDF standard (see https://www.asam.net/standards/detail/mdf/) """ +import abc +import heapq import logging from datetime import datetime from hashlib import md5 from io import BufferedIOBase, BytesIO from pathlib import Path -from typing import Any, BinaryIO, Generator, Optional, Union, cast +from typing import Any, BinaryIO, Dict, Generator, Iterator, List, Optional, Union, cast from ..message import Message from ..typechecking import StringPathLike -from ..util import channel2int, dlc2len, len2dlc +from ..util import channel2int, len2dlc from .generic import BinaryIOMessageReader, BinaryIOMessageWriter logger = logging.getLogger("can.io.mf4") @@ -22,10 +24,10 @@ try: import asammdf import numpy as np - from asammdf import Signal + from asammdf import Signal, Source from asammdf.blocks.mdf_v4 import MDF4 - from asammdf.blocks.v4_blocks import SourceInformation - from asammdf.blocks.v4_constants import BUS_TYPE_CAN, SOURCE_BUS + from asammdf.blocks.v4_blocks import ChannelGroup, SourceInformation + from asammdf.blocks.v4_constants import BUS_TYPE_CAN, FLAG_CG_BUS_EVENT, SOURCE_BUS from asammdf.mdf import MDF STD_DTYPE = np.dtype( @@ -70,6 +72,8 @@ ) except ImportError: asammdf = None + MDF4 = None + Signal = None CAN_MSG_EXT = 0x80000000 @@ -266,13 +270,179 @@ def on_message_received(self, msg: Message) -> None: self._rtr_buffer = np.zeros(1, dtype=RTR_DTYPE) +class FrameIterator(metaclass=abc.ABCMeta): + """ + Iterator helper class for common handling among CAN DataFrames, ErrorFrames and RemoteFrames. + """ + + # Number of records to request for each asammdf call + _chunk_size = 1000 + + def __init__(self, mdf: MDF4, group_index: int, start_timestamp: float, name: str): + self._mdf = mdf + self._group_index = group_index + self._start_timestamp = start_timestamp + self._name = name + + # Extract names + channel_group: ChannelGroup = self._mdf.groups[self._group_index] + + self._channel_names = [] + + for channel in channel_group.channels: + if str(channel.name).startswith(f"{self._name}."): + self._channel_names.append(channel.name) + + def _get_data(self, current_offset: int) -> Signal: + # NOTE: asammdf suggests using select instead of get. Select seem to miss converting some + # channels which get does convert as expected. + data_raw = self._mdf.get( + self._name, + self._group_index, + record_offset=current_offset, + record_count=self._chunk_size, + raw=False, + ) + + return data_raw + + @abc.abstractmethod + def __iter__(self) -> Generator[Message, None, None]: + pass + + class MF4Reader(BinaryIOMessageReader): """ Iterator of CAN messages from a MF4 logging file. - The MF4Reader only supports MF4 files that were recorded with python-can. + The MF4Reader only supports MF4 files with CAN bus logging. """ + # NOTE: Readout based on the bus logging code from asammdf GUI + + class _CANDataFrameIterator(FrameIterator): + + def __init__(self, mdf: MDF4, group_index: int, start_timestamp: float): + super().__init__(mdf, group_index, start_timestamp, "CAN_DataFrame") + + def __iter__(self) -> Generator[Message, None, None]: + for current_offset in range( + 0, + self._mdf.groups[self._group_index].channel_group.cycles_nr, + self._chunk_size, + ): + data = self._get_data(current_offset) + names = data.samples[0].dtype.names + + for i in range(len(data)): + data_length = int(data["CAN_DataFrame.DataLength"][i]) + + kv: Dict[str, Any] = { + "timestamp": float(data.timestamps[i]) + self._start_timestamp, + "arbitration_id": int(data["CAN_DataFrame.ID"][i]) & 0x1FFFFFFF, + "data": data["CAN_DataFrame.DataBytes"][i][ + :data_length + ].tobytes(), + } + + if "CAN_DataFrame.BusChannel" in names: + kv["channel"] = int(data["CAN_DataFrame.BusChannel"][i]) + if "CAN_DataFrame.Dir" in names: + kv["is_rx"] = int(data["CAN_DataFrame.Dir"][i]) == 0 + if "CAN_DataFrame.IDE" in names: + kv["is_extended_id"] = bool(data["CAN_DataFrame.IDE"][i]) + if "CAN_DataFrame.EDL" in names: + kv["is_fd"] = bool(data["CAN_DataFrame.EDL"][i]) + if "CAN_DataFrame.BRS" in names: + kv["bitrate_switch"] = bool(data["CAN_DataFrame.BRS"][i]) + if "CAN_DataFrame.ESI" in names: + kv["error_state_indicator"] = bool(data["CAN_DataFrame.ESI"][i]) + + yield Message(**kv) + + class _CANErrorFrameIterator(FrameIterator): + + def __init__(self, mdf: MDF4, group_index: int, start_timestamp: float): + super().__init__(mdf, group_index, start_timestamp, "CAN_ErrorFrame") + + def __iter__(self) -> Generator[Message, None, None]: + for current_offset in range( + 0, + self._mdf.groups[self._group_index].channel_group.cycles_nr, + self._chunk_size, + ): + data = self._get_data(current_offset) + names = data.samples[0].dtype.names + + for i in range(len(data)): + kv: Dict[str, Any] = { + "timestamp": float(data.timestamps[i]) + self._start_timestamp, + "is_error_frame": True, + } + + if "CAN_ErrorFrame.BusChannel" in names: + kv["channel"] = int(data["CAN_ErrorFrame.BusChannel"][i]) + if "CAN_ErrorFrame.Dir" in names: + kv["is_rx"] = int(data["CAN_ErrorFrame.Dir"][i]) == 0 + if "CAN_ErrorFrame.ID" in names: + kv["arbitration_id"] = ( + int(data["CAN_ErrorFrame.ID"][i]) & 0x1FFFFFFF + ) + if "CAN_ErrorFrame.IDE" in names: + kv["is_extended_id"] = bool(data["CAN_ErrorFrame.IDE"][i]) + if "CAN_ErrorFrame.EDL" in names: + kv["is_fd"] = bool(data["CAN_ErrorFrame.EDL"][i]) + if "CAN_ErrorFrame.BRS" in names: + kv["bitrate_switch"] = bool(data["CAN_ErrorFrame.BRS"][i]) + if "CAN_ErrorFrame.ESI" in names: + kv["error_state_indicator"] = bool( + data["CAN_ErrorFrame.ESI"][i] + ) + if "CAN_ErrorFrame.RTR" in names: + kv["is_remote_frame"] = bool(data["CAN_ErrorFrame.RTR"][i]) + if ( + "CAN_ErrorFrame.DataLength" in names + and "CAN_ErrorFrame.DataBytes" in names + ): + data_length = int(data["CAN_ErrorFrame.DataLength"][i]) + kv["data"] = data["CAN_ErrorFrame.DataBytes"][i][ + :data_length + ].tobytes() + + yield Message(**kv) + + class _CANRemoteFrameIterator(FrameIterator): + + def __init__(self, mdf: MDF4, group_index: int, start_timestamp: float): + super().__init__(mdf, group_index, start_timestamp, "CAN_RemoteFrame") + + def __iter__(self) -> Generator[Message, None, None]: + for current_offset in range( + 0, + self._mdf.groups[self._group_index].channel_group.cycles_nr, + self._chunk_size, + ): + data = self._get_data(current_offset) + names = data.samples[0].dtype.names + + for i in range(len(data)): + kv: Dict[str, Any] = { + "timestamp": float(data.timestamps[i]) + self._start_timestamp, + "arbitration_id": int(data["CAN_RemoteFrame.ID"][i]) + & 0x1FFFFFFF, + "dlc": int(data["CAN_RemoteFrame.DLC"][i]), + "is_remote_frame": True, + } + + if "CAN_RemoteFrame.BusChannel" in names: + kv["channel"] = int(data["CAN_RemoteFrame.BusChannel"][i]) + if "CAN_RemoteFrame.Dir" in names: + kv["is_rx"] = int(data["CAN_RemoteFrame.Dir"][i]) == 0 + if "CAN_RemoteFrame.IDE" in names: + kv["is_extended_id"] = bool(data["CAN_RemoteFrame.IDE"][i]) + + yield Message(**kv) + def __init__( self, file: Union[StringPathLike, BinaryIO], @@ -293,193 +463,65 @@ def __init__( self._mdf: MDF4 if isinstance(file, BufferedIOBase): - self._mdf = MDF(BytesIO(file.read())) + self._mdf = cast(MDF4, MDF(BytesIO(file.read()))) else: - self._mdf = MDF(file) - - self.start_timestamp = self._mdf.header.start_time.timestamp() - - masters = [self._mdf.get_master(i) for i in range(3)] - - masters = [ - np.core.records.fromarrays((master, np.ones(len(master)) * i)) - for i, master in enumerate(masters) - ] - - self.masters = np.sort(np.concatenate(masters)) - - def __iter__(self) -> Generator[Message, None, None]: - standard_counter = 0 - error_counter = 0 - rtr_counter = 0 - - for timestamp, group_index in self.masters: - # standard frames - if group_index == 0: - sample = self._mdf.get( - "CAN_DataFrame", - group=group_index, - raw=True, - record_offset=standard_counter, - record_count=1, - ) - - try: - channel = int(sample["CAN_DataFrame.BusChannel"][0]) - except ValueError: - channel = None - - if sample["CAN_DataFrame.EDL"] == 0: - is_extended_id = bool(sample["CAN_DataFrame.IDE"][0]) - arbitration_id = int(sample["CAN_DataFrame.ID"][0]) - is_rx = int(sample["CAN_DataFrame.Dir"][0]) == 0 - size = int(sample["CAN_DataFrame.DataLength"][0]) - dlc = int(sample["CAN_DataFrame.DLC"][0]) - data = sample["CAN_DataFrame.DataBytes"][0, :size].tobytes() - - msg = Message( - timestamp=timestamp + self.start_timestamp, - is_error_frame=False, - is_remote_frame=False, - is_fd=False, - is_extended_id=is_extended_id, - channel=channel, - is_rx=is_rx, - arbitration_id=arbitration_id, - data=data, - dlc=dlc, - ) - - else: - is_extended_id = bool(sample["CAN_DataFrame.IDE"][0]) - arbitration_id = int(sample["CAN_DataFrame.ID"][0]) - is_rx = int(sample["CAN_DataFrame.Dir"][0]) == 0 - size = int(sample["CAN_DataFrame.DataLength"][0]) - dlc = dlc2len(sample["CAN_DataFrame.DLC"][0]) - data = sample["CAN_DataFrame.DataBytes"][0, :size].tobytes() - error_state_indicator = bool(sample["CAN_DataFrame.ESI"][0]) - bitrate_switch = bool(sample["CAN_DataFrame.BRS"][0]) - - msg = Message( - timestamp=timestamp + self.start_timestamp, - is_error_frame=False, - is_remote_frame=False, - is_fd=True, - is_extended_id=is_extended_id, - channel=channel, - arbitration_id=arbitration_id, - is_rx=is_rx, - data=data, - dlc=dlc, - bitrate_switch=bitrate_switch, - error_state_indicator=error_state_indicator, + self._mdf = cast(MDF4, MDF(file)) + + self._start_timestamp = self._mdf.header.start_time.timestamp() + + def __iter__(self) -> Iterator[Message]: + # To handle messages split over multiple channel groups, create a single iterator per + # channel group and merge these iterators into a single iterator using heapq. + iterators: List[FrameIterator] = [] + for group_index, group in enumerate(self._mdf.groups): + channel_group: ChannelGroup = group.channel_group + + if not channel_group.flags & FLAG_CG_BUS_EVENT: + # Not a bus event, skip + continue + + if channel_group.cycles_nr == 0: + # No data, skip + continue + + acquisition_source: Optional[Source] = channel_group.acq_source + + if acquisition_source is None: + # No source information, skip + continue + if not acquisition_source.source_type & Source.SOURCE_BUS: + # Not a bus type (likely already covered by the channel group flag), skip + continue + + channel_names = [channel.name for channel in group.channels] + + if acquisition_source.bus_type == Source.BUS_TYPE_CAN: + if "CAN_DataFrame" in channel_names: + iterators.append( + self._CANDataFrameIterator( + self._mdf, group_index, self._start_timestamp + ) ) - - yield msg - standard_counter += 1 - - # error frames - elif group_index == 1: - sample = self._mdf.get( - "CAN_ErrorFrame", - group=group_index, - raw=True, - record_offset=error_counter, - record_count=1, - ) - - try: - channel = int(sample["CAN_ErrorFrame.BusChannel"][0]) - except ValueError: - channel = None - - if sample["CAN_ErrorFrame.EDL"] == 0: - is_extended_id = bool(sample["CAN_ErrorFrame.IDE"][0]) - arbitration_id = int(sample["CAN_ErrorFrame.ID"][0]) - is_rx = int(sample["CAN_ErrorFrame.Dir"][0]) == 0 - size = int(sample["CAN_ErrorFrame.DataLength"][0]) - dlc = int(sample["CAN_ErrorFrame.DLC"][0]) - data = sample["CAN_ErrorFrame.DataBytes"][0, :size].tobytes() - - msg = Message( - timestamp=timestamp + self.start_timestamp, - is_error_frame=True, - is_remote_frame=False, - is_fd=False, - is_extended_id=is_extended_id, - channel=channel, - arbitration_id=arbitration_id, - is_rx=is_rx, - data=data, - dlc=dlc, + elif "CAN_ErrorFrame" in channel_names: + iterators.append( + self._CANErrorFrameIterator( + self._mdf, group_index, self._start_timestamp + ) ) - - else: - is_extended_id = bool(sample["CAN_ErrorFrame.IDE"][0]) - arbitration_id = int(sample["CAN_ErrorFrame.ID"][0]) - is_rx = int(sample["CAN_ErrorFrame.Dir"][0]) == 0 - size = int(sample["CAN_ErrorFrame.DataLength"][0]) - dlc = dlc2len(sample["CAN_ErrorFrame.DLC"][0]) - data = sample["CAN_ErrorFrame.DataBytes"][0, :size].tobytes() - error_state_indicator = bool(sample["CAN_ErrorFrame.ESI"][0]) - bitrate_switch = bool(sample["CAN_ErrorFrame.BRS"][0]) - - msg = Message( - timestamp=timestamp + self.start_timestamp, - is_error_frame=True, - is_remote_frame=False, - is_fd=True, - is_extended_id=is_extended_id, - channel=channel, - arbitration_id=arbitration_id, - is_rx=is_rx, - data=data, - dlc=dlc, - bitrate_switch=bitrate_switch, - error_state_indicator=error_state_indicator, + elif "CAN_RemoteFrame" in channel_names: + iterators.append( + self._CANRemoteFrameIterator( + self._mdf, group_index, self._start_timestamp + ) ) - - yield msg - error_counter += 1 - - # remote frames else: - sample = self._mdf.get( - "CAN_RemoteFrame", - group=group_index, - raw=True, - record_offset=rtr_counter, - record_count=1, - ) - - try: - channel = int(sample["CAN_RemoteFrame.BusChannel"][0]) - except ValueError: - channel = None - - is_extended_id = bool(sample["CAN_RemoteFrame.IDE"][0]) - arbitration_id = int(sample["CAN_RemoteFrame.ID"][0]) - is_rx = int(sample["CAN_RemoteFrame.Dir"][0]) == 0 - dlc = int(sample["CAN_RemoteFrame.DLC"][0]) - - msg = Message( - timestamp=timestamp + self.start_timestamp, - is_error_frame=False, - is_remote_frame=True, - is_fd=False, - is_extended_id=is_extended_id, - channel=channel, - arbitration_id=arbitration_id, - is_rx=is_rx, - dlc=dlc, - ) - - yield msg - - rtr_counter += 1 - - self.stop() + # Unknown bus type, skip + continue + + # Create merged iterator over all the groups, using the timestamps as comparison key + return iter(heapq.merge(*iterators, key=lambda x: x.timestamp)) def stop(self) -> None: self._mdf.close() + self._mdf = None super().stop() From 33a1ec740137345075390fe5252db69e27668294 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:39:43 +0100 Subject: [PATCH 1167/1235] Fix CI failures (#1898) * implement join_threads helper method * simplify network_test.py * update tox.ini --- test/network_test.py | 103 +++++++++++++++----------------------- test/simplecyclic_test.py | 40 +++++++++++---- tox.ini | 4 +- 3 files changed, 71 insertions(+), 76 deletions(-) diff --git a/test/network_test.py b/test/network_test.py index 50070ef40..3a231dff7 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -6,16 +6,14 @@ import unittest import can +from test.config import IS_PYPY logging.getLogger(__file__).setLevel(logging.WARNING) # make a random bool: def rbool(): - return bool(round(random.random())) - - -channel = "vcan0" + return random.choice([False, True]) class ControllerAreaNetworkTestCase(unittest.TestCase): @@ -51,74 +49,51 @@ def tearDown(self): # Restore the defaults can.rc = self._can_rc - def producer(self, ready_event, msg_read): - self.client_bus = can.interface.Bus(channel=channel) - ready_event.wait() - for i in range(self.num_messages): - m = can.Message( - arbitration_id=self.ids[i], - is_remote_frame=self.remote_flags[i], - is_error_frame=self.error_flags[i], - is_extended_id=self.extended_flags[i], - data=self.data[i], - ) - # logging.debug("writing message: {}".format(m)) - if msg_read is not None: - # Don't send until the other thread is ready - msg_read.wait() - msg_read.clear() - - self.client_bus.send(m) + def producer(self, channel: str): + with can.interface.Bus(channel=channel) as client_bus: + for i in range(self.num_messages): + m = can.Message( + arbitration_id=self.ids[i], + is_remote_frame=self.remote_flags[i], + is_error_frame=self.error_flags[i], + is_extended_id=self.extended_flags[i], + data=self.data[i], + ) + client_bus.send(m) def testProducer(self): """Verify that we can send arbitrary messages on the bus""" logging.debug("testing producer alone") - ready = threading.Event() - ready.set() - self.producer(ready, None) - + self.producer(channel="testProducer") logging.debug("producer test complete") def testProducerConsumer(self): logging.debug("testing producer/consumer") - ready = threading.Event() - msg_read = threading.Event() - - self.server_bus = can.interface.Bus(channel=channel, interface="virtual") - - t = threading.Thread(target=self.producer, args=(ready, msg_read)) - t.start() - - # Ensure there are no messages on the bus - while True: - m = self.server_bus.recv(timeout=0.5) - if m is None: - print("No messages... lets go") - break - else: - self.fail("received messages before the test has started ...") - ready.set() - i = 0 - while i < self.num_messages: - msg_read.set() - msg = self.server_bus.recv(timeout=0.5) - self.assertIsNotNone(msg, "Didn't receive a message") - # logging.debug("Received message {} with data: {}".format(i, msg.data)) - - self.assertEqual(msg.is_extended_id, self.extended_flags[i]) - if not msg.is_remote_frame: - self.assertEqual(msg.data, self.data[i]) - self.assertEqual(msg.arbitration_id, self.ids[i]) - - self.assertEqual(msg.is_error_frame, self.error_flags[i]) - self.assertEqual(msg.is_remote_frame, self.remote_flags[i]) - - i += 1 - t.join() - - with contextlib.suppress(NotImplementedError): - self.server_bus.flush_tx_buffer() - self.server_bus.shutdown() + read_timeout = 2.0 if IS_PYPY else 0.5 + channel = "testProducerConsumer" + + with can.interface.Bus(channel=channel, interface="virtual") as server_bus: + t = threading.Thread(target=self.producer, args=(channel,)) + t.start() + + i = 0 + while i < self.num_messages: + msg = server_bus.recv(timeout=read_timeout) + self.assertIsNotNone(msg, "Didn't receive a message") + + self.assertEqual(msg.is_extended_id, self.extended_flags[i]) + if not msg.is_remote_frame: + self.assertEqual(msg.data, self.data[i]) + self.assertEqual(msg.arbitration_id, self.ids[i]) + + self.assertEqual(msg.is_error_frame, self.error_flags[i]) + self.assertEqual(msg.is_remote_frame, self.remote_flags[i]) + + i += 1 + t.join() + + with contextlib.suppress(NotImplementedError): + server_bus.flush_tx_buffer() if __name__ == "__main__": diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 7116efc9d..c4c1a2340 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -5,8 +5,11 @@ """ import gc +import sys import time +import traceback import unittest +from threading import Thread from time import sleep from typing import List from unittest.mock import MagicMock @@ -87,6 +90,8 @@ def test_removing_bus_tasks(self): # Note calling task.stop will remove the task from the Bus's internal task management list task.stop() + self.join_threads([task.thread for task in tasks], 5.0) + assert len(bus._periodic_tasks) == 0 bus.shutdown() @@ -115,8 +120,7 @@ def test_managed_tasks(self): for task in tasks: task.stop() - for task in tasks: - assert task.thread.join(5.0) is None, "Task didn't stop before timeout" + self.join_threads([task.thread for task in tasks], 5.0) bus.shutdown() @@ -142,9 +146,7 @@ def test_stopping_perodic_tasks(self): # stop the other half using the bus api bus.stop_all_periodic_tasks(remove_tasks=False) - - for task in tasks: - assert task.thread.join(5.0) is None, "Task didn't stop before timeout" + self.join_threads([task.thread for task in tasks], 5.0) # Tasks stopped via `stop_all_periodic_tasks` with remove_tasks=False should # still be associated with the bus (e.g. for restarting) @@ -161,7 +163,7 @@ def test_restart_perodic_tasks(self): is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] ) - def _read_all_messages(_bus: can.interfaces.virtual.VirtualBus) -> None: + def _read_all_messages(_bus: "can.interfaces.virtual.VirtualBus") -> None: sleep(safe_timeout) while not _bus.queue.empty(): _bus.recv(timeout=period) @@ -207,9 +209,8 @@ def _read_all_messages(_bus: can.interfaces.virtual.VirtualBus) -> None: # Stop all tasks and wait for the thread to exit bus.stop_all_periodic_tasks() - if isinstance(task, can.broadcastmanager.ThreadBasedCyclicSendTask): - # Avoids issues where the thread is still running when the bus is shutdown - task.thread.join(safe_timeout) + # Avoids issues where the thread is still running when the bus is shutdown + self.join_threads([task.thread], 5.0) @unittest.skipIf(IS_CI, "fails randomly when run on CI server") def test_thread_based_cyclic_send_task(self): @@ -288,6 +289,27 @@ def increment_first_byte(msg: can.Message) -> None: self.assertEqual(b"\x06\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[5].data)) self.assertEqual(b"\x07\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[6].data)) + @staticmethod + def join_threads(threads: List[Thread], timeout: float) -> None: + stuck_threads: List[Thread] = [] + t0 = time.perf_counter() + for thread in threads: + time_left = timeout - (time.perf_counter() - t0) + if time_left > 0.0: + thread.join(time_left) + if thread.is_alive(): + if platform.python_implementation() == "CPython": + # print thread frame to help with debugging + frame = sys._current_frames()[thread.ident] + traceback.print_stack(frame, file=sys.stderr) + stuck_threads.append(thread) + if stuck_threads: + err_message = ( + f"Threads did not stop within {timeout:.1f} seconds: " + f"[{', '.join([str(t) for t in stuck_threads])}]" + ) + raise RuntimeError(err_message) + if __name__ == "__main__": unittest.main() diff --git a/tox.ini b/tox.ini index 1d4e88166..e112c22b4 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ deps = pyserial~=3.5 parameterized~=0.8 asammdf>=6.0; platform_python_implementation=="CPython" and python_version<"3.13" - pywin32>=305; platform_system=="Windows" and platform_python_implementation=="CPython" and python_version<"3.13" + pywin32>=305; platform_system=="Windows" and platform_python_implementation=="CPython" and python_version<"3.14" commands = pytest {posargs} @@ -19,8 +19,6 @@ commands = extras = canalystii -recreate = True - [testenv:gh] passenv = CI From 856fd115da6672cc8821b88d5d3e601ded32c662 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:48:55 +0100 Subject: [PATCH 1168/1235] Add BitTiming/BitTimingFd support to slcanBus (#1512) * add BitTiming parameter to slcanBus * remove btr argument, rename ttyBaudrate --- can/interfaces/slcan.py | 73 +++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index f023e084c..4cf8b5e25 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -5,16 +5,17 @@ import io import logging import time -from typing import Any, Optional, Tuple +import warnings +from typing import Any, Optional, Tuple, Union -from can import BusABC, CanProtocol, Message, typechecking - -from ..exceptions import ( +from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message, typechecking +from can.exceptions import ( CanInitializationError, CanInterfaceNotImplementedError, CanOperationError, error_check, ) +from can.util import check_or_adjust_timing_clock, deprecated_args_alias logger = logging.getLogger(__name__) @@ -54,12 +55,17 @@ class slcanBus(BusABC): LINE_TERMINATOR = b"\r" + @deprecated_args_alias( + deprecation_start="4.5.0", + deprecation_end="5.0.0", + ttyBaudrate="tty_baudrate", + ) def __init__( self, channel: typechecking.ChannelStr, - ttyBaudrate: int = 115200, + tty_baudrate: int = 115200, bitrate: Optional[int] = None, - btr: Optional[str] = None, + timing: Optional[Union[BitTiming, BitTimingFd]] = None, sleep_after_open: float = _SLEEP_AFTER_SERIAL_OPEN, rtscts: bool = False, listen_only: bool = False, @@ -70,12 +76,16 @@ def __init__( :param str channel: port of underlying serial or usb device (e.g. ``/dev/ttyUSB0``, ``COM8``, ...) Must not be empty. Can also end with ``@115200`` (or similarly) to specify the baudrate. - :param int ttyBaudrate: + :param int tty_baudrate: baudrate of underlying serial or usb device (Ignored if set via the ``channel`` parameter) :param bitrate: Bitrate in bit/s - :param btr: - BTR register value to set custom can speed + :param timing: + Optional :class:`~can.BitTiming` instance to use for custom bit timing setting. + If this argument is set then it overrides the bitrate and btr arguments. The + `f_clock` value of the timing instance must be set to 8_000_000 (8MHz) + for standard CAN. + CAN FD and the :class:`~can.BitTimingFd` class are not supported. :param poll_interval: Poll interval in seconds when reading messages :param sleep_after_open: @@ -97,16 +107,26 @@ def __init__( if serial is None: raise CanInterfaceNotImplementedError("The serial module is not installed") + btr: Optional[str] = kwargs.get("btr", None) + if btr is not None: + warnings.warn( + "The 'btr' argument is deprecated since python-can v4.5.0 " + "and scheduled for removal in v5.0.0. " + "Use the 'timing' argument instead.", + DeprecationWarning, + stacklevel=1, + ) + if not channel: # if None or empty raise ValueError("Must specify a serial port.") if "@" in channel: (channel, baudrate) = channel.split("@") - ttyBaudrate = int(baudrate) + tty_baudrate = int(baudrate) with error_check(exception_type=CanInitializationError): self.serialPortOrig = serial.serial_for_url( channel, - baudrate=ttyBaudrate, + baudrate=tty_baudrate, rtscts=rtscts, timeout=timeout, ) @@ -117,21 +137,23 @@ def __init__( time.sleep(sleep_after_open) with error_check(exception_type=CanInitializationError): - 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(bitrate) - if btr is not None: - self.set_bitrate_reg(btr) + if isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000]) + self.set_bitrate_reg(f"{timing.btr0:02X}{timing.btr1:02X}") + elif isinstance(timing, BitTimingFd): + raise NotImplementedError( + f"CAN FD is not supported by {self.__class__.__name__}." + ) + else: + 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(bitrate) + if btr is not None: + self.set_bitrate_reg(btr) self.open() - super().__init__( - channel, - ttyBaudrate=115200, - bitrate=None, - rtscts=False, - **kwargs, - ) + super().__init__(channel, **kwargs) def set_bitrate(self, bitrate: int) -> None: """ @@ -153,7 +175,8 @@ def set_bitrate(self, bitrate: int) -> None: def set_bitrate_reg(self, btr: str) -> None: """ :param btr: - BTR register value to set custom can speed + BTR register value to set custom can speed as a string `xxyy` where + xx is the BTR0 value in hex and yy is the BTR1 value in hex. """ self.close() self._write("s" + btr) From 2eb8f53d97faf14f3924023d26da2c540b5a5052 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:49:29 +0100 Subject: [PATCH 1169/1235] Add BitTiming parameter to Usb2canBus (#1511) --- can/interfaces/usb2can/usb2canInterface.py | 49 ++++++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index c89e394df..adc16e8b3 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -4,9 +4,18 @@ import logging from ctypes import byref -from typing import Optional - -from can import BusABC, CanInitializationError, CanOperationError, CanProtocol, Message +from typing import Optional, Union + +from can import ( + BitTiming, + BitTimingFd, + BusABC, + CanInitializationError, + CanOperationError, + CanProtocol, + Message, +) +from can.util import check_or_adjust_timing_clock from .serial_selector import find_serial_devices from .usb2canabstractionlayer import ( @@ -78,6 +87,13 @@ class Usb2canBus(BusABC): Bitrate of channel in bit/s. Values will be limited to a maximum of 1000 Kb/s. Default is 500 Kbs + :param timing: + Optional :class:`~can.BitTiming` instance to use for custom bit timing setting. + If this argument is set then it overrides the bitrate argument. The + `f_clock` value of the timing instance must be set to 32_000_000 (32MHz) + for standard CAN. + CAN FD and the :class:`~can.BitTimingFd` class are not supported. + :param flags: Flags to directly pass to open function of the usb2can abstraction layer. @@ -97,8 +113,8 @@ def __init__( channel: Optional[str] = None, dll: str = "usb2can.dll", flags: int = 0x00000008, - *_, bitrate: int = 500000, + timing: Optional[Union[BitTiming, BitTimingFd]] = None, serial: Optional[str] = None, **kwargs, ): @@ -114,13 +130,28 @@ def __init__( raise CanInitializationError("could not automatically find any device") device_id = devices[0] - # convert to kb/s and cap: max rate is 1000 kb/s - baudrate = min(int(bitrate // 1000), 1000) - self.channel_info = f"USB2CAN device {device_id}" - self._can_protocol = CanProtocol.CAN_20 - connector = f"{device_id}; {baudrate}" + if isinstance(timing, BitTiming): + timing = check_or_adjust_timing_clock(timing, valid_clocks=[32_000_000]) + connector = ( + f"{device_id};" + "0;" + f"{timing.tseg1};" + f"{timing.tseg2};" + f"{timing.sjw};" + f"{timing.brp}" + ) + elif isinstance(timing, BitTimingFd): + raise NotImplementedError( + f"CAN FD is not supported by {self.__class__.__name__}." + ) + else: + # convert to kb/s and cap: max rate is 1000 kb/s + baudrate = min(int(bitrate // 1000), 1000) + connector = f"{device_id};{baudrate}" + + self._can_protocol = CanProtocol.CAN_20 self.handle = self.can.open(connector, flags) super().__init__(channel=channel, **kwargs) From 7ba6ddd1af98145493035b1d74a6764c02ba1154 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:15:11 +0100 Subject: [PATCH 1170/1235] Add timing parameter to CLI tools (#1869) --- can/logger.py | 47 ++++++++++++++++++++++++++++++++++++- doc/bit_timing.rst | 4 ---- test/test_logger.py | 57 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/can/logger.py b/can/logger.py index 81e9527f0..4167558d8 100644 --- a/can/logger.py +++ b/can/logger.py @@ -17,7 +17,7 @@ import can from can import Bus, BusState, Logger, SizedRotatingLogger from can.typechecking import TAdditionalCliArgs -from can.util import cast_from_string +from can.util import _dict2timing, cast_from_string if TYPE_CHECKING: from can.io import BaseRotatingLogger @@ -58,6 +58,19 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: help="Bitrate to use for the data phase in case of CAN-FD.", ) + parser.add_argument( + "--timing", + action=_BitTimingAction, + nargs=argparse.ONE_OR_MORE, + help="Configure bit rate and bit timing. For example, use " + "`--timing f_clock=8_000_000 tseg1=5 tseg2=2 sjw=2 brp=2 nof_samples=1` for classical CAN " + "or `--timing f_clock=80_000_000 nom_tseg1=119 nom_tseg2=40 nom_sjw=40 nom_brp=1 " + "data_tseg1=29 data_tseg2=10 data_sjw=10 data_brp=1` for CAN FD. " + "Check the python-can documentation to verify whether your " + "CAN interface supports the `timing` argument.", + metavar="TIMING_ARG", + ) + parser.add_argument( "extra_args", nargs=argparse.REMAINDER, @@ -109,6 +122,8 @@ def _create_bus(parsed_args: argparse.Namespace, **kwargs: Any) -> can.BusABC: config["data_bitrate"] = parsed_args.data_bitrate if getattr(parsed_args, "can_filters", None): config["can_filters"] = parsed_args.can_filters + if parsed_args.timing: + config["timing"] = parsed_args.timing return Bus(parsed_args.channel, **config) @@ -143,6 +158,36 @@ def __call__( setattr(namespace, self.dest, can_filters) +class _BitTimingAction(argparse.Action): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, + ) -> None: + if not isinstance(values, list): + raise argparse.ArgumentError(None, "Invalid --timing argument") + + timing_dict: Dict[str, int] = {} + for arg in values: + try: + key, value_string = arg.split("=") + value = int(value_string) + timing_dict[key] = value + except ValueError: + raise argparse.ArgumentError( + None, f"Invalid timing argument: {arg}" + ) from None + + if not (timing := _dict2timing(timing_dict)): + err_msg = "Invalid --timing argument. Incomplete parameters." + raise argparse.ArgumentError(None, err_msg) + + setattr(namespace, self.dest, timing) + print(timing) + + def _parse_additional_config(unknown_args: Sequence[str]) -> TAdditionalCliArgs: for arg in unknown_args: if not re.match(r"^--[a-zA-Z][a-zA-Z0-9\-]*=\S*?$", arg): diff --git a/doc/bit_timing.rst b/doc/bit_timing.rst index 73005a3c6..bf7aad486 100644 --- a/doc/bit_timing.rst +++ b/doc/bit_timing.rst @@ -1,10 +1,6 @@ Bit Timing Configuration ======================== -.. attention:: - This feature is experimental. The implementation might change in future - versions. - The CAN protocol, specified in ISO 11898, allows the bitrate, sample point and number of samples to be optimized for a given application. These parameters, known as bit timings, can be adjusted to meet the requirements diff --git a/test/test_logger.py b/test/test_logger.py index 10df2557b..d9f200e00 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -139,6 +139,63 @@ def test_parse_can_filters_list(self): ) assert results.can_filters == expected_can_filters + def test_parse_timing(self) -> None: + can20_args = self.baseargs + [ + "--timing", + "f_clock=8_000_000", + "tseg1=5", + "tseg2=2", + "sjw=2", + "brp=2", + "nof_samples=1", + "--app-name=CANalyzer", + ] + results, additional_config = can.logger._parse_logger_args(can20_args[1:]) + assert results.timing == can.BitTiming( + f_clock=8_000_000, brp=2, tseg1=5, tseg2=2, sjw=2, nof_samples=1 + ) + assert additional_config["app_name"] == "CANalyzer" + + canfd_args = self.baseargs + [ + "--timing", + "f_clock=80_000_000", + "nom_tseg1=119", + "nom_tseg2=40", + "nom_sjw=40", + "nom_brp=1", + "data_tseg1=29", + "data_tseg2=10", + "data_sjw=10", + "data_brp=1", + "--app-name=CANalyzer", + ] + results, additional_config = can.logger._parse_logger_args(canfd_args[1:]) + assert results.timing == can.BitTimingFd( + f_clock=80_000_000, + nom_brp=1, + nom_tseg1=119, + nom_tseg2=40, + nom_sjw=40, + data_brp=1, + data_tseg1=29, + data_tseg2=10, + data_sjw=10, + ) + assert additional_config["app_name"] == "CANalyzer" + + # remove f_clock parameter, parsing should fail + incomplete_args = self.baseargs + [ + "--timing", + "tseg1=5", + "tseg2=2", + "sjw=2", + "brp=2", + "nof_samples=1", + "--app-name=CANalyzer", + ] + with self.assertRaises(SystemExit): + can.logger._parse_logger_args(incomplete_args[1:]) + def test_parse_additional_config(self): unknown_args = [ "--app-name=CANalyzer", From 226e040ffa722593add5c763651d1e4504d8387b Mon Sep 17 00:00:00 2001 From: Adrian Immer <62163284+lebuni@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:43:43 +0100 Subject: [PATCH 1171/1235] Improve speed of TRCReader (#1893) Rewrote some lines in the TRCReader to optimize for speed, mainly for TRC files v2.x. According to cProfile it's double as fast now. --- can/io/trc.py | 83 +++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index f0595c23e..2dbe3763c 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -11,15 +11,12 @@ import os from datetime import datetime, timedelta, timezone from enum import Enum -from typing import Any, Callable, Dict, Generator, List, Optional, TextIO, Union +from typing import Any, Callable, Dict, Generator, Optional, TextIO, Tuple, Union from ..message import Message from ..typechecking import StringPathLike -from ..util import channel2int, dlc2len, len2dlc -from .generic import ( - TextIOMessageReader, - TextIOMessageWriter, -) +from ..util import channel2int, len2dlc +from .generic import TextIOMessageReader, TextIOMessageWriter logger = logging.getLogger("can.io.trc") @@ -58,13 +55,22 @@ def __init__( """ super().__init__(file, mode="r") self.file_version = TRCFileVersion.UNKNOWN - self.start_time: Optional[datetime] = None + self._start_time: float = 0 self.columns: Dict[str, int] = {} + self._num_columns = -1 if not self.file: raise ValueError("The given file cannot be None") - self._parse_cols: Callable[[List[str]], Optional[Message]] = lambda x: None + self._parse_cols: Callable[[Tuple[str, ...]], Optional[Message]] = ( + lambda x: None + ) + + @property + def start_time(self) -> Optional[datetime]: + if self._start_time: + return datetime.fromtimestamp(self._start_time, timezone.utc) + return None def _extract_header(self): line = "" @@ -89,9 +95,10 @@ def _extract_header(self): elif line.startswith(";$STARTTIME"): logger.debug("TRCReader: Found start time '%s'", line) try: - self.start_time = datetime( - 1899, 12, 30, tzinfo=timezone.utc - ) + timedelta(days=float(line.split("=")[1])) + self._start_time = ( + datetime(1899, 12, 30, tzinfo=timezone.utc) + + timedelta(days=float(line.split("=")[1])) + ).timestamp() except IndexError: logger.debug("TRCReader: Failed to parse start time") elif line.startswith(";$COLUMNS"): @@ -99,6 +106,7 @@ def _extract_header(self): try: columns = line.split("=")[1].split(",") self.columns = {column: columns.index(column) for column in columns} + self._num_columns = len(columns) - 1 except IndexError: logger.debug("TRCReader: Failed to parse columns") elif line.startswith(";"): @@ -107,7 +115,7 @@ def _extract_header(self): break if self.file_version >= TRCFileVersion.V1_1: - if self.start_time is None: + if self._start_time is None: raise ValueError("File has no start time information") if self.file_version >= TRCFileVersion.V2_0: @@ -132,7 +140,7 @@ def _extract_header(self): return line - def _parse_msg_v1_0(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v1_0(self, cols: Tuple[str, ...]) -> Optional[Message]: arbit_id = cols[2] if arbit_id == "FFFFFFFF": logger.info("TRCReader: Dropping bus info line") @@ -147,16 +155,11 @@ def _parse_msg_v1_0(self, cols: List[str]) -> Optional[Message]: msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) return msg - def _parse_msg_v1_1(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v1_1(self, cols: Tuple[str, ...]) -> Optional[Message]: arbit_id = cols[3] msg = Message() - if isinstance(self.start_time, datetime): - msg.timestamp = ( - self.start_time + timedelta(milliseconds=float(cols[1])) - ).timestamp() - else: - msg.timestamp = float(cols[1]) / 1000 + msg.timestamp = float(cols[1]) / 1000 + self._start_time msg.arbitration_id = int(arbit_id, 16) msg.is_extended_id = len(arbit_id) > 4 msg.channel = 1 @@ -165,16 +168,11 @@ def _parse_msg_v1_1(self, cols: List[str]) -> Optional[Message]: msg.is_rx = cols[2] == "Rx" return msg - def _parse_msg_v1_3(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v1_3(self, cols: Tuple[str, ...]) -> Optional[Message]: arbit_id = cols[4] msg = Message() - if isinstance(self.start_time, datetime): - msg.timestamp = ( - self.start_time + timedelta(milliseconds=float(cols[1])) - ).timestamp() - else: - msg.timestamp = float(cols[1]) / 1000 + msg.timestamp = float(cols[1]) / 1000 + self._start_time msg.arbitration_id = int(arbit_id, 16) msg.is_extended_id = len(arbit_id) > 4 msg.channel = int(cols[2]) @@ -183,7 +181,7 @@ def _parse_msg_v1_3(self, cols: List[str]) -> Optional[Message]: msg.is_rx = cols[3] == "Rx" return msg - def _parse_msg_v2_x(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v2_x(self, cols: Tuple[str, ...]) -> Optional[Message]: type_ = cols[self.columns["T"]] bus = self.columns.get("B", None) @@ -192,32 +190,25 @@ def _parse_msg_v2_x(self, cols: List[str]) -> Optional[Message]: dlc = len2dlc(length) elif "L" in self.columns: dlc = int(cols[self.columns["L"]]) - length = dlc2len(dlc) else: raise ValueError("No length/dlc columns present.") msg = Message() - if isinstance(self.start_time, datetime): - msg.timestamp = ( - self.start_time + timedelta(milliseconds=float(cols[self.columns["O"]])) - ).timestamp() - else: - msg.timestamp = float(cols[1]) / 1000 + msg.timestamp = float(cols[self.columns["O"]]) / 1000 + self._start_time msg.arbitration_id = int(cols[self.columns["I"]], 16) msg.is_extended_id = len(cols[self.columns["I"]]) > 4 msg.channel = int(cols[bus]) if bus is not None else 1 msg.dlc = dlc - msg.data = bytearray( - [int(cols[i + self.columns["D"]], 16) for i in range(length)] - ) + if dlc: + msg.data = bytearray.fromhex(cols[self.columns["D"]]) msg.is_rx = cols[self.columns["d"]] == "Rx" - msg.is_fd = type_ in ["FD", "FB", "FE", "BI"] - msg.bitrate_switch = type_ in ["FB", " FE"] - msg.error_state_indicator = type_ in ["FE", "BI"] + msg.is_fd = type_ in {"FD", "FB", "FE", "BI"} + msg.bitrate_switch = type_ in {"FB", "FE"} + msg.error_state_indicator = type_ in {"FE", "BI"} return msg - def _parse_cols_v1_1(self, cols: List[str]) -> Optional[Message]: + def _parse_cols_v1_1(self, cols: Tuple[str, ...]) -> Optional[Message]: dtype = cols[2] if dtype in ("Tx", "Rx"): return self._parse_msg_v1_1(cols) @@ -225,7 +216,7 @@ def _parse_cols_v1_1(self, cols: List[str]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_v1_3(self, cols: List[str]) -> Optional[Message]: + def _parse_cols_v1_3(self, cols: Tuple[str, ...]) -> Optional[Message]: dtype = cols[3] if dtype in ("Tx", "Rx"): return self._parse_msg_v1_3(cols) @@ -233,9 +224,9 @@ def _parse_cols_v1_3(self, cols: List[str]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_v2_x(self, cols: List[str]) -> Optional[Message]: + def _parse_cols_v2_x(self, cols: Tuple[str, ...]) -> Optional[Message]: dtype = cols[self.columns["T"]] - if dtype in ["DT", "FD", "FB"]: + if dtype in {"DT", "FD", "FB", "FE", "BI"}: return self._parse_msg_v2_x(cols) else: logger.info("TRCReader: Unsupported type '%s'", dtype) @@ -244,7 +235,7 @@ def _parse_cols_v2_x(self, cols: List[str]) -> Optional[Message]: def _parse_line(self, line: str) -> Optional[Message]: logger.debug("TRCReader: Parse '%s'", line) try: - cols = line.split() + cols = tuple(line.split(maxsplit=self._num_columns)) return self._parse_cols(cols) except IndexError: logger.warning("TRCReader: Failed to parse message '%s'", line) From 654a02ae24bfc50bf1bb1fad7aab4aa88763d302 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 28 Nov 2024 18:59:18 +1300 Subject: [PATCH 1172/1235] Changelog for v4.5.0 (#1897) --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTORS.txt | 9 +++++++++ doc/development.rst | 1 - 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d2581d9d..39cbaa716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +Version 4.5.0 +============= + +Features +-------- + +* gs_usb command-line support (and documentation updates and stability fixes) by @BenGardiner in https://github.com/hardbyte/python-can/pull/1790 +* Faster and more general MF4 support by @cssedev in https://github.com/hardbyte/python-can/pull/1892 +* ASCWriter speed improvement by @pierreluctg in https://github.com/hardbyte/python-can/pull/1856 +* Faster Message string representation by @pierreluctg in https://github.com/hardbyte/python-can/pull/1858 +* Added Netronic's CANdo and CANdoISO adapters interface by @belliriccardo in https://github.com/hardbyte/python-can/pull/1887 +* Add autostart option to BusABC.send_periodic() to fix issue #1848 by @SWolfSchunk in https://github.com/hardbyte/python-can/pull/1853 +* Improve TestBusConfig by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1804 +* Improve speed of TRCReader by @lebuni in https://github.com/hardbyte/python-can/pull/1893 + +Bug Fixes +--------- + +* Fix Kvaser timestamp by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1878 +* Set end_time in ThreadBasedCyclicSendTask.start() by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1871 +* Fix regex in _parse_additional_config() by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1868 +* Fix for #1849 (PCAN fails when PCAN_ERROR_ILLDATA is read via ReadFD) by @bures in https://github.com/hardbyte/python-can/pull/1850 +* Period must be >= 1ms for BCM using Win32 API by @pierreluctg in https://github.com/hardbyte/python-can/pull/1847 +* Fix ASCReader Crash on "Start of Measurement" Line by @RitheeshBaradwaj in https://github.com/hardbyte/python-can/pull/1811 +* Resolve AttributeError within NicanError by @vijaysubbiah20 in https://github.com/hardbyte/python-can/pull/1806 + + +Miscellaneous +------------- + +* Fix CI by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1889 +* Update msgpack dependency by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1875 +* Add tox environment for doctest by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1870 +* Use typing_extensions.TypedDict on python < 3.12 for pydantic support by @NickCao in https://github.com/hardbyte/python-can/pull/1845 +* Replace PyPy3.8 with PyPy3.10 by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1838 +* Fix slcan tests by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1834 +* Test on Python 3.13 by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1833 +* Stop notifier in examples by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1814 +* Use setuptools_scm by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1810 +* Added extra info for Kvaser dongles by @FedericoSpada in https://github.com/hardbyte/python-can/pull/1797 +* Socketcand: show actual response as well as expected in error by @liamkinne in https://github.com/hardbyte/python-can/pull/1807 +* Refactor CLI filter parsing, add tests by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1805 +* Add zlgcan to docs by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1839 + + Version 4.4.2 ============= diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 389d26412..524908dfc 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -83,3 +83,12 @@ Felix Nieuwenhuizen @felixn @Tbruno25 @RitheeshBaradwaj +@vijaysubbiah20 +@liamkinne +@RitheeshBaradwaj +@BenGardiner +@bures +@NickCao +@SWolfSchunk +@belliriccardo +@cssedev diff --git a/doc/development.rst b/doc/development.rst index a8332eeb6..31a7ae077 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -110,7 +110,6 @@ Creating a new Release ---------------------- - Release from the ``main`` branch (except for pre-releases). -- Update the library version in ``__init__.py`` using `semantic versioning `__. - Check if any deprecations are pending. - Run all tests and examples against available hardware. - Update ``CONTRIBUTORS.txt`` with any new contributors. From 5526e32d718ae414ea8337128bd16f9b59c06f36 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 2 Feb 2025 14:14:37 +0100 Subject: [PATCH 1173/1235] Fix slcanBus.get_version() and slcanBus.get_serial_number() (#1904) Co-authored-by: zariiii9003 --- can/interfaces/slcan.py | 53 ++++++++++++--------- pyproject.toml | 1 - test/test_slcan.py | 100 +++++++++++++++++++++++++++++++--------- 3 files changed, 110 insertions(+), 44 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 4cf8b5e25..f0a04e305 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -6,7 +6,8 @@ import logging import time import warnings -from typing import Any, Optional, Tuple, Union +from queue import SimpleQueue +from typing import Any, Optional, Tuple, Union, cast from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message, typechecking from can.exceptions import ( @@ -131,6 +132,7 @@ def __init__( timeout=timeout, ) + self._queue: SimpleQueue[str] = SimpleQueue() self._buffer = bytearray() self._can_protocol = CanProtocol.CAN_20 @@ -196,7 +198,7 @@ def _read(self, timeout: Optional[float]) -> Optional[str]: # We read the `serialPortOrig.in_waiting` only once here. in_waiting = self.serialPortOrig.in_waiting for _ in range(max(1, in_waiting)): - new_byte = self.serialPortOrig.read(size=1) + new_byte = self.serialPortOrig.read(1) if new_byte: self._buffer.extend(new_byte) else: @@ -234,7 +236,10 @@ def _recv_internal( extended = False data = None - string = self._read(timeout) + if self._queue.qsize(): + string: Optional[str] = self._queue.get_nowait() + else: + string = self._read(timeout) if not string: pass @@ -300,7 +305,7 @@ def shutdown(self) -> None: def fileno(self) -> int: try: - return self.serialPortOrig.fileno() + return cast(int, self.serialPortOrig.fileno()) except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" @@ -321,19 +326,21 @@ def get_version( int hw_version is the hardware version or None on timeout int sw_version is the software version or None on timeout """ + _timeout = serial.Timeout(timeout) cmd = "V" self._write(cmd) - string = self._read(timeout) - - if not string: - pass - elif string[0] == cmd and len(string) == 6: - # convert ASCII coded version - hw_version = int(string[1:3]) - sw_version = int(string[3:5]) - return hw_version, sw_version - + while True: + if string := self._read(_timeout.time_left()): + if string[0] == cmd: + # convert ASCII coded version + hw_version = int(string[1:3]) + sw_version = int(string[3:5]) + return hw_version, sw_version + else: + self._queue.put_nowait(string) + if _timeout.expired(): + break return None, None def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: @@ -345,15 +352,17 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: :return: :obj:`None` on timeout or a :class:`str` object. """ + _timeout = serial.Timeout(timeout) cmd = "N" self._write(cmd) - string = self._read(timeout) - - if not string: - pass - elif string[0] == cmd and len(string) == 6: - serial_number = string[1:-1] - return serial_number - + while True: + if string := self._read(_timeout.time_left()): + if string[0] == cmd: + serial_number = string[1:-1] + return serial_number + else: + self._queue.put_nowait(string) + if _timeout.expired(): + break return None diff --git a/pyproject.toml b/pyproject.toml index f2b6ac04f..4ee463c5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,6 @@ exclude = [ "^can/interfaces/neousys", "^can/interfaces/pcan", "^can/interfaces/serial", - "^can/interfaces/slcan", "^can/interfaces/socketcan", "^can/interfaces/systec", "^can/interfaces/udp_multicast", diff --git a/test/test_slcan.py b/test/test_slcan.py index e1531e500..220a6d7e0 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -import unittest -from typing import cast +import unittest.mock +from typing import cast, Optional -import serial +from serial.serialutil import SerialBase import can.interfaces.slcan @@ -21,20 +21,69 @@ TIMEOUT = 0.5 if IS_PYPY else 0.01 # 0.001 is the default set in slcanBus +class SerialMock(SerialBase): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self._input_buffer = b"" + self._output_buffer = b"" + + def open(self) -> None: + self.is_open = True + + def close(self) -> None: + self.is_open = False + self._input_buffer = b"" + self._output_buffer = b"" + + def read(self, size: int = -1, /) -> bytes: + if size > 0: + data = self._input_buffer[:size] + self._input_buffer = self._input_buffer[size:] + return data + return b"" + + def write(self, b: bytes, /) -> Optional[int]: + self._output_buffer = b + if b == b"N\r": + self.set_input_buffer(b"NA123\r") + elif b == b"V\r": + self.set_input_buffer(b"V1013\r") + return len(b) + + def set_input_buffer(self, expected: bytes) -> None: + self._input_buffer = expected + + def get_output_buffer(self) -> bytes: + return self._output_buffer + + def reset_input_buffer(self) -> None: + self._input_buffer = b"" + + @property + def in_waiting(self) -> int: + return len(self._input_buffer) + + @classmethod + def serial_for_url(cls, *args, **kwargs) -> SerialBase: + return cls(*args, **kwargs) + + class slcanTestCase(unittest.TestCase): + @unittest.mock.patch("serial.serial_for_url", SerialMock.serial_for_url) def setUp(self): self.bus = cast( can.interfaces.slcan.slcanBus, can.Bus("loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT), ) - self.serial = cast(serial.Serial, self.bus.serialPortOrig) + self.serial = cast(SerialMock, self.bus.serialPortOrig) self.serial.reset_input_buffer() def tearDown(self): self.bus.shutdown() def test_recv_extended(self): - self.serial.write(b"T12ABCDEF2AA55\r") + self.serial.set_input_buffer(b"T12ABCDEF2AA55\r") msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -44,7 +93,7 @@ def test_recv_extended(self): self.assertSequenceEqual(msg.data, [0xAA, 0x55]) # Ewert Energy Systems CANDapter specific - self.serial.write(b"x12ABCDEF2AA55\r") + self.serial.set_input_buffer(b"x12ABCDEF2AA55\r") msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -54,15 +103,19 @@ def test_recv_extended(self): self.assertSequenceEqual(msg.data, [0xAA, 0x55]) def test_send_extended(self): + payload = b"T12ABCDEF2AA55\r" msg = can.Message( arbitration_id=0x12ABCDEF, is_extended_id=True, data=[0xAA, 0x55] ) self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) rx_msg = self.bus.recv(TIMEOUT) self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) def test_recv_standard(self): - self.serial.write(b"t4563112233\r") + self.serial.set_input_buffer(b"t4563112233\r") msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x456) @@ -72,15 +125,19 @@ def test_recv_standard(self): self.assertSequenceEqual(msg.data, [0x11, 0x22, 0x33]) def test_send_standard(self): + payload = b"t4563112233\r" msg = can.Message( arbitration_id=0x456, is_extended_id=False, data=[0x11, 0x22, 0x33] ) self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) rx_msg = self.bus.recv(TIMEOUT) self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) def test_recv_standard_remote(self): - self.serial.write(b"r1238\r") + self.serial.set_input_buffer(b"r1238\r") msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x123) @@ -89,15 +146,19 @@ def test_recv_standard_remote(self): self.assertEqual(msg.dlc, 8) def test_send_standard_remote(self): + payload = b"r1238\r" msg = can.Message( arbitration_id=0x123, is_extended_id=False, is_remote_frame=True, dlc=8 ) self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) rx_msg = self.bus.recv(TIMEOUT) self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) def test_recv_extended_remote(self): - self.serial.write(b"R12ABCDEF6\r") + self.serial.set_input_buffer(b"R12ABCDEF6\r") msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -106,19 +167,23 @@ def test_recv_extended_remote(self): self.assertEqual(msg.dlc, 6) def test_send_extended_remote(self): + payload = b"R12ABCDEF6\r" msg = can.Message( arbitration_id=0x12ABCDEF, is_extended_id=True, is_remote_frame=True, dlc=6 ) self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) rx_msg = self.bus.recv(TIMEOUT) self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) def test_partial_recv(self): - self.serial.write(b"T12ABCDEF") + self.serial.set_input_buffer(b"T12ABCDEF") msg = self.bus.recv(TIMEOUT) self.assertIsNone(msg) - self.serial.write(b"2AA55\rT12") + self.serial.set_input_buffer(b"2AA55\rT12") msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) self.assertEqual(msg.arbitration_id, 0x12ABCDEF) @@ -130,28 +195,21 @@ def test_partial_recv(self): msg = self.bus.recv(TIMEOUT) self.assertIsNone(msg) - self.serial.write(b"ABCDEF2AA55\r") + self.serial.set_input_buffer(b"ABCDEF2AA55\r") msg = self.bus.recv(TIMEOUT) self.assertIsNotNone(msg) def test_version(self): - self.serial.write(b"V1013\r") hw_ver, sw_ver = self.bus.get_version(0) + self.assertEqual(b"V\r", self.serial.get_output_buffer()) self.assertEqual(hw_ver, 10) self.assertEqual(sw_ver, 13) - hw_ver, sw_ver = self.bus.get_version(0) - self.assertIsNone(hw_ver) - self.assertIsNone(sw_ver) - def test_serial_number(self): - self.serial.write(b"NA123\r") sn = self.bus.get_serial_number(0) + self.assertEqual(b"N\r", self.serial.get_output_buffer()) self.assertEqual(sn, "A123") - sn = self.bus.get_serial_number(0) - self.assertIsNone(sn) - if __name__ == "__main__": unittest.main() From b323e7753ab95ce97ea08a7673177775e623f61d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 2 Feb 2025 14:15:14 +0100 Subject: [PATCH 1174/1235] BlfReader: Fix CAN_FD_MESSAGE_64 data padding (#1906) * add padding to CAN_FD_MESSAGE_64 data * set timezone to utc --------- Co-authored-by: zariiii9003 --- can/io/blf.py | 21 ++++++++++++++++----- test/data/issue_1905.blf | Bin 0 -> 524 bytes test/logformats_test.py | 24 ++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 test/data/issue_1905.blf diff --git a/can/io/blf.py b/can/io/blf.py index 81146233d..7d944a846 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -103,7 +103,7 @@ def timestamp_to_systemtime(timestamp: float) -> TSystemTime: if timestamp is None or timestamp < 631152000: # Probably not a Unix timestamp return 0, 0, 0, 0, 0, 0, 0, 0 - t = datetime.datetime.fromtimestamp(round(timestamp, 3)) + t = datetime.datetime.fromtimestamp(round(timestamp, 3), tz=datetime.timezone.utc) return ( t.year, t.month, @@ -126,6 +126,7 @@ def systemtime_to_timestamp(systemtime: TSystemTime) -> float: systemtime[5], systemtime[6], systemtime[7] * 1000, + tzinfo=datetime.timezone.utc, ) return t.timestamp() except ValueError: @@ -239,7 +240,7 @@ def _parse_data(self, data): raise BLFParseError("Could not find next object") from None header = unpack_obj_header_base(data, pos) # print(header) - signature, _, header_version, obj_size, obj_type = header + signature, header_size, header_version, obj_size, obj_type = header if signature != b"LOBJ": raise BLFParseError() @@ -334,10 +335,20 @@ def _parse_data(self, data): _, _, direction, - _, + ext_data_offset, _, ) = unpack_can_fd_64_msg(data, pos) - pos += can_fd_64_msg_size + + # :issue:`1905`: `valid_bytes` can be higher than the actually available data. + # Add zero-byte padding to mimic behavior of CANoe and binlog.dll. + data_field_length = min( + valid_bytes, + (ext_data_offset or obj_size) - header_size - can_fd_64_msg_size, + ) + msg_data_offset = pos + can_fd_64_msg_size + msg_data = data[msg_data_offset : msg_data_offset + data_field_length] + msg_data = msg_data.ljust(valid_bytes, b"\x00") + yield Message( timestamp=timestamp, arbitration_id=can_id & 0x1FFFFFFF, @@ -348,7 +359,7 @@ def _parse_data(self, data): bitrate_switch=bool(fd_flags & 0x2000), error_state_indicator=bool(fd_flags & 0x4000), dlc=dlc2len(dlc), - data=data[pos : pos + valid_bytes], + data=msg_data, channel=channel - 1, ) diff --git a/test/data/issue_1905.blf b/test/data/issue_1905.blf new file mode 100644 index 0000000000000000000000000000000000000000..a896a6d7ca36d8842a0958d26bcf4f66c0593916 GIT binary patch literal 524 zcmebAcXyw_z`*b)!Iy!JlaYak3CID0E!+@V3`j8o@e6hy1||l120jK(1`URLNPKPv z8HP8E9Uw(O5Cg;-U>13VkH3?b0MN#7KAtAwo zHA#()BVyyzYl;GA&%HmeVN!47LB=D72?sd;P3EX6xDh>}@ICKih8x-+{&Y%wX1MWs z;m=czsSIy?U;LhN)QaJoSxf!wLuL%$wB97&IB&=B&5t9Y_M{=hH!qJ)u43MXz4l*H za(A*Hh+}4JEt$u-!ThjWuF`vk8^4_nWtrb+xKaIJ?cLyij2p@s=WFkO;BX`QU9zT$ zz_)*&y~X4@zMPS|s%<3jwXX0<*~P{a?hgz3kFp)O$KQJMju5lpe#4*3CYfvzXK?#8 zrIdjI=I Date: Sun, 2 Feb 2025 13:15:59 +0000 Subject: [PATCH 1175/1235] Fix `is_rx` for `receive_own_messages` for Kvaser (#1908) * Enable LOCAL_TXACK to fix is_rx for Kvaser * Add missing constant definition * Update comment --- can/interfaces/kvaser/canlib.py | 11 +++++++++++ can/interfaces/kvaser/constants.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 51a77a567..9731a4415 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -554,6 +554,15 @@ def __init__( 1, ) + # enable canMSG_LOCAL_TXACK flag in received messages + + canIoCtlInit( + self._read_handle, + canstat.canIOCTL_SET_LOCAL_TXACK, + ctypes.byref(ctypes.c_byte(local_echo)), + 1, + ) + if self.single_handle: log.debug("We don't require separate handles to the bus") self._write_handle = self._read_handle @@ -671,6 +680,7 @@ def _recv_internal(self, timeout=None): is_remote_frame = bool(flags & canstat.canMSG_RTR) is_error_frame = bool(flags & canstat.canMSG_ERROR_FRAME) is_fd = bool(flags & canstat.canFDMSG_FDF) + is_rx = not bool(flags & canstat.canMSG_LOCAL_TXACK) bitrate_switch = bool(flags & canstat.canFDMSG_BRS) error_state_indicator = bool(flags & canstat.canFDMSG_ESI) msg_timestamp = timestamp.value * TIMESTAMP_FACTOR @@ -682,6 +692,7 @@ def _recv_internal(self, timeout=None): is_error_frame=is_error_frame, is_remote_frame=is_remote_frame, is_fd=is_fd, + is_rx=is_rx, bitrate_switch=bitrate_switch, error_state_indicator=error_state_indicator, channel=self.channel, diff --git a/can/interfaces/kvaser/constants.py b/can/interfaces/kvaser/constants.py index 3d01faa84..dc710648c 100644 --- a/can/interfaces/kvaser/constants.py +++ b/can/interfaces/kvaser/constants.py @@ -63,6 +63,7 @@ def CANSTATUS_SUCCESS(status): canMSG_ERROR_FRAME = 0x0020 canMSG_TXACK = 0x0040 canMSG_TXRQ = 0x0080 +canMSG_LOCAL_TXACK = 0x1000_0000 canFDMSG_FDF = 0x010000 canFDMSG_BRS = 0x020000 @@ -195,6 +196,7 @@ def CANSTATUS_SUCCESS(status): canIOCTL_GET_USB_THROTTLE = 29 canIOCTL_SET_BUSON_TIME_AUTO_RESET = 30 canIOCTL_SET_LOCAL_TXECHO = 32 +canIOCTL_SET_LOCAL_TXACK = 46 canIOCTL_PREFER_EXT = 1 canIOCTL_PREFER_STD = 2 canIOCTL_CLEAR_ERROR_COUNTERS = 5 From 8e685acc2599c9e1a93322a636301da53e0f8835 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Mon, 3 Feb 2025 09:04:51 -0500 Subject: [PATCH 1176/1235] Adding env marker to pywin32 requirements (extra) (#1916) As part of #1833, the `pywin32` requirements environment markers got dropped when moving the requirement to an extra. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ee463c5d..67bf9ddd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ lint = [ "black==24.10.*", "mypy==1.12.*", ] -pywin32 = ["pywin32>=305"] +pywin32 = ["pywin32>=305; platform_system == 'Windows' and platform_python_implementation == 'CPython'"] seeedstudio = ["pyserial>=3.0"] serial = ["pyserial~=3.0"] neovi = ["filelock", "python-ics>=2.12"] From 2bd4758038e1f727a23aa2379378db87e7e83aa9 Mon Sep 17 00:00:00 2001 From: Thomas Seiler Date: Sat, 15 Feb 2025 13:39:07 +0100 Subject: [PATCH 1177/1235] udp_multicast interface: support windows (#1914) * support windows Neither the SO_TIMESTAMPNS / via recvmsg() method, nor the SIOCGSTAMP / ioctl() method for obtaining the packet timestamp is supported on Windows. This code adds a fallback to datetime, and switches to recvfrom() so that the udp_multicast bus becomes usable on windows. * clean-up example on windows, the example would otherwise cause an _enter_buffered_busy fatal error, notifier shutdown and bus shutdown are racing eachother... * fix double inversion * remove unused import * datetime to time.time() * add msgpack dependency for windows * enable udp_multicast back2back tests on windows * handle WSAEINVAL like EINVAL * avoid potential AttributeError on Linux * simplify unittest skip condition * fix formatting issue --- can/interfaces/udp_multicast/bus.py | 61 ++++++++++++++++++++--------- doc/interfaces/udp_multicast.rst | 3 ++ pyproject.toml | 2 +- test/back2back_test.py | 13 +++--- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 8ca2d516b..dd114278c 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -3,6 +3,7 @@ import select import socket import struct +import time import warnings from typing import List, Optional, Tuple, Union @@ -12,9 +13,12 @@ from .utils import check_msgpack_installed, pack_message, unpack_message +ioctl_supported = True + try: from fcntl import ioctl except ModuleNotFoundError: # Missing on Windows + ioctl_supported = False pass @@ -30,6 +34,9 @@ SO_TIMESTAMPNS = 35 SIOCGSTAMP = 0x8906 +# Additional constants for the interaction with the Winsock API +WSAEINVAL = 10022 + class UdpMulticastBus(BusABC): """A virtual interface for CAN communications between multiple processes using UDP over Multicast IP. @@ -272,7 +279,11 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: try: sock.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) except OSError as error: - if error.errno == errno.ENOPROTOOPT: # It is unavailable on macOS + if ( + error.errno == errno.ENOPROTOOPT + or error.errno == errno.EINVAL + or error.errno == WSAEINVAL + ): # It is unavailable on macOS (ENOPROTOOPT) or windows(EINVAL/WSAEINVAL) self.timestamp_nanosecond = False else: raise error @@ -353,18 +364,18 @@ def recv( ) from exc if ready_receive_sockets: # not empty - # fetch data & source address - ( - raw_message_data, - ancillary_data, - _, # flags - sender_address, - ) = self._socket.recvmsg( - self.max_buffer, self.received_ancillary_buffer_size - ) - # fetch timestamp; this is configured in _create_socket() if self.timestamp_nanosecond: + # fetch data, timestamp & source address + ( + raw_message_data, + ancillary_data, + _, # flags + sender_address, + ) = self._socket.recvmsg( + self.max_buffer, self.received_ancillary_buffer_size + ) + # Very similar to timestamp handling in can/interfaces/socketcan/socketcan.py -> capture_message() if len(ancillary_data) != 1: raise can.CanOperationError( @@ -385,14 +396,28 @@ def recv( ) timestamp = seconds + nanoseconds * 1.0e-9 else: - result_buffer = ioctl( - self._socket.fileno(), - SIOCGSTAMP, - bytes(self.received_timestamp_struct_size), - ) - seconds, microseconds = struct.unpack( - self.received_timestamp_struct, result_buffer + # fetch data & source address + (raw_message_data, sender_address) = self._socket.recvfrom( + self.max_buffer ) + + if ioctl_supported: + result_buffer = ioctl( + self._socket.fileno(), + SIOCGSTAMP, + bytes(self.received_timestamp_struct_size), + ) + seconds, microseconds = struct.unpack( + self.received_timestamp_struct, result_buffer + ) + else: + # fallback to time.time_ns + now = time.time() + + # Extract seconds and microseconds + seconds = int(now) + microseconds = int((now - seconds) * 1000000) + if microseconds >= 1e6: raise can.CanOperationError( f"Timestamp microseconds field was out of range: {microseconds} not less than 1e6" diff --git a/doc/interfaces/udp_multicast.rst b/doc/interfaces/udp_multicast.rst index 4f9745615..e41925cb3 100644 --- a/doc/interfaces/udp_multicast.rst +++ b/doc/interfaces/udp_multicast.rst @@ -53,6 +53,9 @@ from ``bus_1`` to ``bus_2``: # give the notifier enough time to get triggered by the second bus time.sleep(2.0) + # clean-up + notifier.stop() + Bus Class Documentation ----------------------- diff --git a/pyproject.toml b/pyproject.toml index 67bf9ddd7..d41bf6e22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "wrapt~=1.10", "packaging >= 23.1", "typing_extensions>=3.10.0.0", - "msgpack~=1.1.0; platform_system != 'Windows'", + "msgpack~=1.1.0", ] requires-python = ">=3.8" license = { text = "LGPL v3" } diff --git a/test/back2back_test.py b/test/back2back_test.py index 90cf8a9bf..fc630fb65 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -21,6 +21,7 @@ IS_PYPY, IS_TRAVIS, IS_UNIX, + IS_WINDOWS, TEST_CAN_FD, TEST_INTERFACE_SOCKETCAN, ) @@ -302,9 +303,9 @@ class BasicTestSocketCan(Back2BackTestCase): # this doesn't even work on Travis CI for macOS; for example, see # https://travis-ci.org/github/hardbyte/python-can/jobs/745389871 -@unittest.skipUnless( - IS_UNIX and not (IS_CI and IS_OSX), - "only supported on Unix systems (but not on macOS at Travis CI and GitHub Actions)", +@unittest.skipIf( + IS_CI and IS_OSX, + "not supported for macOS CI", ) class BasicTestUdpMulticastBusIPv4(Back2BackTestCase): INTERFACE_1 = "udp_multicast" @@ -319,9 +320,9 @@ def test_unique_message_instances(self): # this doesn't even work for loopback multicast addresses on Travis CI; for example, see # https://travis-ci.org/github/hardbyte/python-can/builds/745065503 -@unittest.skipUnless( - IS_UNIX and not (IS_TRAVIS or (IS_CI and IS_OSX)), - "only supported on Unix systems (but not on Travis CI; and not on macOS at GitHub Actions)", +@unittest.skipIf( + IS_CI and IS_OSX, + "not supported for macOS CI", ) class BasicTestUdpMulticastBusIPv6(Back2BackTestCase): HOST_LOCAL_MCAST_GROUP_IPv6 = "ff11:7079:7468:6f6e:6465:6d6f:6d63:6173" From 6c9d59c2dedc571fa400b5774131cd8af6a7016f Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 24 Feb 2025 21:28:16 +0100 Subject: [PATCH 1178/1235] Fix typo busses to bus (#1925) --- can/exceptions.py | 2 +- doc/other-tools.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/can/exceptions.py b/can/exceptions.py index e1c970e27..ca2d6c5a3 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -1,6 +1,6 @@ """ There are several specific :class:`Exception` classes to allow user -code to react to specific scenarios related to CAN busses:: +code to react to specific scenarios related to CAN buses:: Exception (Python standard library) +-- ... diff --git a/doc/other-tools.rst b/doc/other-tools.rst index db06812ca..607db6c8a 100644 --- a/doc/other-tools.rst +++ b/doc/other-tools.rst @@ -47,7 +47,7 @@ CAN Frame Parsing tools etc. (implemented in Python) #. CAN Message / Database scripting * The `cantools`_ package provides multiple methods for interacting with can message database - files, and using these files to monitor live busses with a command line monitor tool. + files, and using these files to monitor live buses with a command line monitor tool. #. CAN Message / Log Decoding * The `canmatrix`_ module provides methods for converting between multiple popular message frame definition file formats (e.g. .DBC files, .KCD files, .ARXML files etc.). From 319a9f27ac57973b28580c65f4be2c23ac073ff7 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Tue, 25 Feb 2025 10:34:52 -0500 Subject: [PATCH 1179/1235] Fix timestamp offset bug in BLFWriter (#1921) Fix timestamp offset bug in BLFWriter In the BLF files, the frame timestamp are stored as a delta from the "start time". The "start time" stored in the file is the first frame timestamp rounded to 3 decimals. When we calculate the timestamp delta for each frame, we were previously calculating it using the non-rounded "start time". This new code, use the "start time" as the first frame timestamp truncated to 3 decimals. --- can/io/blf.py | 5 ++++- test/data/example_data.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index 7d944a846..deefd4736 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -530,7 +530,10 @@ def _add_object(self, obj_type, data, timestamp=None): if timestamp is None: timestamp = self.stop_timestamp or time.time() if self.start_timestamp is None: - self.start_timestamp = timestamp + # Save start timestamp using the same precision as the BLF format + # Truncating to milliseconds to avoid rounding errors when calculating + # the timestamp difference + self.start_timestamp = int(timestamp * 1000) / 1000 self.stop_timestamp = timestamp timestamp = int((timestamp - self.start_timestamp) * 1e9) header_size = OBJ_HEADER_BASE_STRUCT.size + OBJ_HEADER_V1_STRUCT.size diff --git a/test/data/example_data.py b/test/data/example_data.py index 592556926..b78420e4e 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -160,7 +160,7 @@ def sort_messages(messages): TEST_MESSAGES_ERROR_FRAMES = sort_messages( [ - Message(is_error_frame=True), + Message(is_error_frame=True, timestamp=TEST_TIME), Message(is_error_frame=True, timestamp=TEST_TIME + 0.170), Message(is_error_frame=True, timestamp=TEST_TIME + 17.157), ] From 613c653f0f1ad5e911901734ab42d843284fd1bb Mon Sep 17 00:00:00 2001 From: eldadcool Date: Mon, 3 Mar 2025 20:43:26 +0200 Subject: [PATCH 1180/1235] =?UTF-8?q?CC-2174:=20modify=20CAN=20frame=20hea?= =?UTF-8?q?der=20structure=20to=20match=20updated=20struct=20ca=E2=80=A6?= =?UTF-8?q?=20(#1851)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CC-2174: modify CAN frame header structure to match updated struct can_frame from kernel * allow only valid fd message lengths * add remote message special case for dlc handling * fix pylint issues * verify non FD frame before assigning the len8 DLC * Fix formatting --------- Co-authored-by: Gal Rahamim Co-authored-by: Eldad Sitbon Co-authored-by: rahagal <114643899+rahagal@users.noreply.github.com> --- can/interfaces/socketcan/constants.py | 14 +++- can/interfaces/socketcan/socketcan.py | 106 ++++++++++++++++++++++---- 2 files changed, 105 insertions(+), 15 deletions(-) diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index 3144a2cfa..941d52573 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -53,8 +53,18 @@ SIOCGSTAMP = 0x8906 EXTFLG = 0x0004 -CANFD_BRS = 0x01 -CANFD_ESI = 0x02 +CANFD_BRS = 0x01 # bit rate switch (second bitrate for payload data) +CANFD_ESI = 0x02 # error state indicator of the transmitting node +CANFD_FDF = 0x04 # mark CAN FD for dual use of struct canfd_frame + +# CAN payload length and DLC definitions according to ISO 11898-1 +CAN_MAX_DLC = 8 +CAN_MAX_RAW_DLC = 15 +CAN_MAX_DLEN = 8 + +# CAN FD payload length and DLC definitions according to ISO 11898-7 +CANFD_MAX_DLC = 15 +CANFD_MAX_DLEN = 64 CANFD_MTU = 72 diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 40da0d094..a5819dcb5 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -139,23 +139,68 @@ def bcm_header_factory( # The 32bit can id is directly followed by the 8bit data link count # The data field is aligned on an 8 byte boundary, hence we add padding # which aligns the data field to an 8 byte boundary. -CAN_FRAME_HEADER_STRUCT = struct.Struct("=IBB2x") +CAN_FRAME_HEADER_STRUCT = struct.Struct("=IBB1xB") def build_can_frame(msg: Message) -> bytes: """CAN frame packing/unpacking (see 'struct can_frame' in ) /** - * struct can_frame - basic CAN frame structure - * @can_id: the CAN ID of the frame and CAN_*_FLAG flags, see above. - * @can_dlc: the data length field of the CAN frame - * @data: the CAN frame payload. - */ + * struct can_frame - Classical CAN frame structure (aka CAN 2.0B) + * @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition + * @len: CAN frame payload length in byte (0 .. 8) + * @can_dlc: deprecated name for CAN frame payload length in byte (0 .. 8) + * @__pad: padding + * @__res0: reserved / padding + * @len8_dlc: optional DLC value (9 .. 15) at 8 byte payload length + * len8_dlc contains values from 9 .. 15 when the payload length is + * 8 bytes but the DLC value (see ISO 11898-1) is greater then 8. + * CAN_CTRLMODE_CC_LEN8_DLC flag has to be enabled in CAN driver. + * @data: CAN frame payload (up to 8 byte) + */ struct can_frame { canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ - __u8 can_dlc; /* data length code: 0 .. 8 */ - __u8 data[8] __attribute__((aligned(8))); + union { + /* CAN frame payload length in byte (0 .. CAN_MAX_DLEN) + * was previously named can_dlc so we need to carry that + * name for legacy support + */ + __u8 len; + __u8 can_dlc; /* deprecated */ + } __attribute__((packed)); /* disable padding added in some ABIs */ + __u8 __pad; /* padding */ + __u8 __res0; /* reserved / padding */ + __u8 len8_dlc; /* optional DLC for 8 byte payload length (9 .. 15) */ + __u8 data[CAN_MAX_DLEN] __attribute__((aligned(8))); }; + /* + * defined bits for canfd_frame.flags + * + * The use of struct canfd_frame implies the FD Frame (FDF) bit to + * be set in the CAN frame bitstream on the wire. The FDF bit switch turns + * the CAN controllers bitstream processor into the CAN FD mode which creates + * two new options within the CAN FD frame specification: + * + * Bit Rate Switch - to indicate a second bitrate is/was used for the payload + * Error State Indicator - represents the error state of the transmitting node + * + * As the CANFD_ESI bit is internally generated by the transmitting CAN + * controller only the CANFD_BRS bit is relevant for real CAN controllers when + * building a CAN FD frame for transmission. Setting the CANFD_ESI bit can make + * sense for virtual CAN interfaces to test applications with echoed frames. + * + * The struct can_frame and struct canfd_frame intentionally share the same + * layout to be able to write CAN frame content into a CAN FD frame structure. + * When this is done the former differentiation via CAN_MTU / CANFD_MTU gets + * lost. CANFD_FDF allows programmers to mark CAN FD frames in the case of + * using struct canfd_frame for mixed CAN / CAN FD content (dual use). + * Since the introduction of CAN XL the CANFD_FDF flag is set in all CAN FD + * frame structures provided by the CAN subsystem of the Linux kernel. + */ + #define CANFD_BRS 0x01 /* bit rate switch (second bitrate for payload data) */ + #define CANFD_ESI 0x02 /* error state indicator of the transmitting node */ + #define CANFD_FDF 0x04 /* mark CAN FD for dual use of struct canfd_frame */ + /** * struct canfd_frame - CAN flexible data rate frame structure * @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition @@ -175,14 +220,30 @@ def build_can_frame(msg: Message) -> bytes: }; """ can_id = _compose_arbitration_id(msg) + flags = 0 + + # The socketcan code identify the received FD frame by the packet length. + # So, padding to the data length is performed according to the message type (Classic / FD) + if msg.is_fd: + flags |= constants.CANFD_FDF + max_len = constants.CANFD_MAX_DLEN + else: + max_len = constants.CAN_MAX_DLEN + if msg.bitrate_switch: flags |= constants.CANFD_BRS if msg.error_state_indicator: flags |= constants.CANFD_ESI - max_len = 64 if msg.is_fd else 8 + data = bytes(msg.data).ljust(max_len, b"\x00") - return CAN_FRAME_HEADER_STRUCT.pack(can_id, msg.dlc, flags) + data + + if msg.is_remote_frame: + data_len = msg.dlc + else: + data_len = min(i for i in can.util.CAN_FD_DLC if i >= len(msg.data)) + header = CAN_FRAME_HEADER_STRUCT.pack(can_id, data_len, flags, msg.dlc) + return header + data def build_bcm_header( @@ -259,12 +320,31 @@ def build_bcm_update_header(can_id: int, msg_flags: int, nframes: int = 1) -> by ) +def is_frame_fd(frame: bytes): + # According to the SocketCAN implementation the frame length + # should indicate if the message is FD or not (not the flag value) + return len(frame) == constants.CANFD_MTU + + def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]: - can_id, can_dlc, flags = CAN_FRAME_HEADER_STRUCT.unpack_from(frame) - if len(frame) != constants.CANFD_MTU: + can_id, data_len, flags, len8_dlc = CAN_FRAME_HEADER_STRUCT.unpack_from(frame) + + if data_len not in can.util.CAN_FD_DLC: + data_len = min(i for i in can.util.CAN_FD_DLC if i >= data_len) + + can_dlc = data_len + + if not is_frame_fd(frame): # Flags not valid in non-FD frames flags = 0 - return can_id, can_dlc, flags, frame[8 : 8 + can_dlc] + + if ( + data_len == constants.CAN_MAX_DLEN + and constants.CAN_MAX_DLEN < len8_dlc <= constants.CAN_MAX_RAW_DLC + ): + can_dlc = len8_dlc + + return can_id, can_dlc, flags, frame[8 : 8 + data_len] def create_bcm_socket(channel: str) -> socket.socket: From 1a200c9acf44334eed070c7a6fd609052d53af43 Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Wed, 5 Mar 2025 21:05:54 -0500 Subject: [PATCH 1181/1235] Fix float arithmetic in BLF reader (#1927) * Fix float arithmetic in BLF reader Using the Python decimal module for fast correctly rounded decimal floating-point arithmetic when applying the timestamp factor. * Remove the allowed_timestamp_delta from the BLF UT Now that the BLF float arithmetic issue is fixed we no longer need to tweek the `allowed_timestamp_delta` in the BLF unit tests --- can/io/blf.py | 5 +++-- test/logformats_test.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index deefd4736..f6e1b6d4d 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -17,6 +17,7 @@ import struct import time import zlib +from decimal import Decimal from typing import Any, BinaryIO, Generator, List, Optional, Tuple, Union, cast from ..message import Message @@ -264,8 +265,8 @@ def _parse_data(self, data): continue # Calculate absolute timestamp in seconds - factor = 1e-5 if flags == 1 else 1e-9 - timestamp = timestamp * factor + start_timestamp + factor = Decimal("1e-5") if flags == 1 else Decimal("1e-9") + timestamp = float(Decimal(timestamp) * factor) + start_timestamp if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): channel, flags, dlc, can_id, can_data = unpack_can_msg(data, pos) diff --git a/test/logformats_test.py b/test/logformats_test.py index a2fd0fe09..f3fe485b2 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -694,7 +694,6 @@ def _setup_instance(self): check_fd=True, check_comments=False, test_append=True, - allowed_timestamp_delta=1.0e-6, preserves_channel=False, adds_default_channel=0, ) From 6deca6e233cd01b18a7ea9bd2a008307ad622e36 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 14 Mar 2025 00:25:49 +0100 Subject: [PATCH 1182/1235] Update black, ruff & mypy (#1929) * update black and ruff * update mypy * fix pylint complaints --------- Co-authored-by: zariiii9003 --- can/__init__.py | 22 +-- can/bit_timing.py | 40 +++--- can/broadcastmanager.py | 6 +- can/bus.py | 4 +- can/ctypesutil.py | 2 +- can/interface.py | 4 +- can/interfaces/ics_neovi/__init__.py | 3 +- can/interfaces/ixxat/canlib_vcinpl.py | 8 +- can/interfaces/ixxat/canlib_vcinpl2.py | 8 +- can/interfaces/ixxat/exceptions.py | 6 +- can/interfaces/ixxat/structures.py | 14 +- can/interfaces/kvaser/__init__.py | 3 +- can/interfaces/neousys/__init__.py | 2 +- can/interfaces/neousys/neousys.py | 2 +- can/interfaces/pcan/__init__.py | 3 +- can/interfaces/seeedstudio/__init__.py | 3 - can/interfaces/serial/__init__.py | 3 +- can/interfaces/slcan.py | 2 +- can/interfaces/socketcan/utils.py | 2 +- can/interfaces/usb2can/__init__.py | 5 +- can/interfaces/usb2can/serial_selector.py | 3 +- can/interfaces/vector/__init__.py | 3 +- can/interfaces/vector/canlib.py | 4 +- can/io/__init__.py | 14 +- can/io/blf.py | 12 +- can/io/canutils.py | 2 +- can/io/generic.py | 8 +- can/io/logger.py | 2 +- can/io/mf4.py | 10 +- can/io/printer.py | 2 +- can/message.py | 16 +-- can/player.py | 11 +- can/typechecking.py | 3 +- can/util.py | 6 +- can/viewer.py | 3 +- pyproject.toml | 8 +- test/listener_test.py | 3 +- test/test_interface_canalystii.py | 3 +- test/test_kvaser.py | 3 +- test/test_neovi.py | 3 +- test/test_vector.py | 166 +++++++++++----------- test/zero_dlc_test.py | 3 +- 42 files changed, 212 insertions(+), 218 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 324803b9e..1d4b7f0cf 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -11,17 +11,20 @@ from typing import Any, Dict __all__ = [ + "VALID_INTERFACES", "ASCReader", "ASCWriter", "AsyncBufferedReader", - "BitTiming", - "BitTimingFd", "BLFReader", "BLFWriter", + "BitTiming", + "BitTimingFd", "BufferedReader", "Bus", "BusABC", "BusState", + "CSVReader", + "CSVWriter", "CanError", "CanInitializationError", "CanInterfaceNotImplementedError", @@ -30,18 +33,16 @@ "CanTimeoutError", "CanutilsLogReader", "CanutilsLogWriter", - "CSVReader", - "CSVWriter", "CyclicSendTaskABC", "LimitedDurationCyclicSendTaskABC", "Listener", - "Logger", "LogReader", - "ModifiableCyclicTaskABC", - "Message", - "MessageSync", + "Logger", "MF4Reader", "MF4Writer", + "Message", + "MessageSync", + "ModifiableCyclicTaskABC", "Notifier", "Printer", "RedirectReader", @@ -49,11 +50,10 @@ "SizedRotatingLogger", "SqliteReader", "SqliteWriter", - "ThreadSafeBus", "TRCFileVersion", "TRCReader", "TRCWriter", - "VALID_INTERFACES", + "ThreadSafeBus", "bit_timing", "broadcastmanager", "bus", @@ -64,8 +64,8 @@ "interfaces", "io", "listener", - "logconvert", "log", + "logconvert", "logger", "message", "notifier", diff --git a/can/bit_timing.py b/can/bit_timing.py index f5d50eac1..feba8b6d2 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -155,7 +155,7 @@ def from_bitrate_and_segments( if the arguments are invalid. """ try: - brp = int(round(f_clock / (bitrate * (1 + tseg1 + tseg2)))) + brp = round(f_clock / (bitrate * (1 + tseg1 + tseg2))) except ZeroDivisionError: raise ValueError("Invalid inputs") from None @@ -232,7 +232,7 @@ def iterate_from_sample_point( raise ValueError(f"sample_point (={sample_point}) must not be below 50%.") for brp in range(1, 65): - nbt = round(int(f_clock / (bitrate * brp))) + nbt = int(f_clock / (bitrate * brp)) if nbt < 8: break @@ -240,7 +240,7 @@ def iterate_from_sample_point( if abs(effective_bitrate - bitrate) > bitrate / 256: continue - tseg1 = int(round(sample_point / 100 * nbt)) - 1 + tseg1 = round(sample_point / 100 * nbt) - 1 # limit tseg1, so tseg2 is at least 1 TQ tseg1 = min(tseg1, nbt - 2) @@ -312,7 +312,7 @@ def f_clock(self) -> int: @property def bitrate(self) -> int: """Bitrate in bits/s.""" - return int(round(self.f_clock / (self.nbt * self.brp))) + return round(self.f_clock / (self.nbt * self.brp)) @property def brp(self) -> int: @@ -322,7 +322,7 @@ def brp(self) -> int: @property def tq(self) -> int: """Time quantum in nanoseconds""" - return int(round(self.brp / self.f_clock * 1e9)) + return round(self.brp / self.f_clock * 1e9) @property def nbt(self) -> int: @@ -433,7 +433,7 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming": "f_clock change failed because of sample point discrepancy." ) # adapt synchronization jump width, so it has the same size relative to bit time as self - sjw = int(round(self.sjw / self.nbt * bt.nbt)) + sjw = round(self.sjw / self.nbt * bt.nbt) sjw = max(1, min(4, bt.tseg2, sjw)) bt._data["sjw"] = sjw # pylint: disable=protected-access bt._data["nof_samples"] = self.nof_samples # pylint: disable=protected-access @@ -458,7 +458,7 @@ def __repr__(self) -> str: return f"can.{self.__class__.__name__}({args})" def __getitem__(self, key: str) -> int: - return cast(int, self._data.__getitem__(key)) + return cast("int", self._data.__getitem__(key)) def __len__(self) -> int: return self._data.__len__() @@ -716,10 +716,8 @@ def from_bitrate_and_segments( # pylint: disable=too-many-arguments if the arguments are invalid. """ try: - nom_brp = int(round(f_clock / (nom_bitrate * (1 + nom_tseg1 + nom_tseg2)))) - data_brp = int( - round(f_clock / (data_bitrate * (1 + data_tseg1 + data_tseg2))) - ) + nom_brp = round(f_clock / (nom_bitrate * (1 + nom_tseg1 + nom_tseg2))) + data_brp = round(f_clock / (data_bitrate * (1 + data_tseg1 + data_tseg2))) except ZeroDivisionError: raise ValueError("Invalid inputs.") from None @@ -787,7 +785,7 @@ def iterate_from_sample_point( sync_seg = 1 for nom_brp in range(1, 257): - nbt = round(int(f_clock / (nom_bitrate * nom_brp))) + nbt = int(f_clock / (nom_bitrate * nom_brp)) if nbt < 1: break @@ -795,7 +793,7 @@ def iterate_from_sample_point( if abs(effective_nom_bitrate - nom_bitrate) > nom_bitrate / 256: continue - nom_tseg1 = int(round(nom_sample_point / 100 * nbt)) - 1 + nom_tseg1 = round(nom_sample_point / 100 * nbt) - 1 # limit tseg1, so tseg2 is at least 2 TQ nom_tseg1 = min(nom_tseg1, nbt - sync_seg - 2) nom_tseg2 = nbt - nom_tseg1 - 1 @@ -811,7 +809,7 @@ def iterate_from_sample_point( if abs(effective_data_bitrate - data_bitrate) > data_bitrate / 256: continue - data_tseg1 = int(round(data_sample_point / 100 * dbt)) - 1 + data_tseg1 = round(data_sample_point / 100 * dbt) - 1 # limit tseg1, so tseg2 is at least 2 TQ data_tseg1 = min(data_tseg1, dbt - sync_seg - 2) data_tseg2 = dbt - data_tseg1 - 1 @@ -923,7 +921,7 @@ def f_clock(self) -> int: @property def nom_bitrate(self) -> int: """Nominal (arbitration phase) bitrate.""" - return int(round(self.f_clock / (self.nbt * self.nom_brp))) + return round(self.f_clock / (self.nbt * self.nom_brp)) @property def nom_brp(self) -> int: @@ -933,7 +931,7 @@ def nom_brp(self) -> int: @property def nom_tq(self) -> int: """Nominal time quantum in nanoseconds""" - return int(round(self.nom_brp / self.f_clock * 1e9)) + return round(self.nom_brp / self.f_clock * 1e9) @property def nbt(self) -> int: @@ -969,7 +967,7 @@ def nom_sample_point(self) -> float: @property def data_bitrate(self) -> int: """Bitrate of the data phase in bit/s.""" - return int(round(self.f_clock / (self.dbt * self.data_brp))) + return round(self.f_clock / (self.dbt * self.data_brp)) @property def data_brp(self) -> int: @@ -979,7 +977,7 @@ def data_brp(self) -> int: @property def data_tq(self) -> int: """Data time quantum in nanoseconds""" - return int(round(self.data_brp / self.f_clock * 1e9)) + return round(self.data_brp / self.f_clock * 1e9) @property def dbt(self) -> int: @@ -1106,10 +1104,10 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTimingFd": "f_clock change failed because of sample point discrepancy." ) # adapt synchronization jump width, so it has the same size relative to bit time as self - nom_sjw = int(round(self.nom_sjw / self.nbt * bt.nbt)) + nom_sjw = round(self.nom_sjw / self.nbt * bt.nbt) nom_sjw = max(1, min(bt.nom_tseg2, nom_sjw)) bt._data["nom_sjw"] = nom_sjw # pylint: disable=protected-access - data_sjw = int(round(self.data_sjw / self.dbt * bt.dbt)) + data_sjw = round(self.data_sjw / self.dbt * bt.dbt) data_sjw = max(1, min(bt.data_tseg2, data_sjw)) bt._data["data_sjw"] = data_sjw # pylint: disable=protected-access bt._validate() # pylint: disable=protected-access @@ -1138,7 +1136,7 @@ def __repr__(self) -> str: return f"can.{self.__class__.__name__}({args})" def __getitem__(self, key: str) -> int: - return cast(int, self._data.__getitem__(key)) + return cast("int", self._data.__getitem__(key)) def __len__(self) -> int: return self._data.__len__() diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 319fb0f53..19dca8fbc 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -61,7 +61,7 @@ def create_timer(self) -> _Pywin32Event: ): event = self.win32event.CreateWaitableTimer(None, False, None) - return cast(_Pywin32Event, event) + return cast("_Pywin32Event", event) def set_timer(self, event: _Pywin32Event, period_ms: int) -> None: self.win32event.SetWaitableTimer(event.handle, 0, period_ms, None, None, False) @@ -121,12 +121,12 @@ def __init__( # Take the Arbitration ID of the first element self.arbitration_id = messages[0].arbitration_id self.period = period - self.period_ns = int(round(period * 1e9)) + self.period_ns = round(period * 1e9) self.messages = messages @staticmethod def _check_and_convert_messages( - messages: Union[Sequence[Message], Message] + 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. diff --git a/can/bus.py b/can/bus.py index a12808ab6..d186e2d05 100644 --- a/can/bus.py +++ b/can/bus.py @@ -276,7 +276,7 @@ def send_periodic( # Create a backend specific task; will be patched to a _SelfRemovingCyclicTask later task = cast( - _SelfRemovingCyclicTask, + "_SelfRemovingCyclicTask", self._send_periodic_internal( msgs, period, duration, autostart, modifier_callback ), @@ -452,7 +452,7 @@ def _matches_filters(self, msg: Message) -> bool: for _filter in self._filters: # check if this filter even applies to the message if "extended" in _filter: - _filter = cast(can.typechecking.CanFilterExtended, _filter) + _filter = cast("can.typechecking.CanFilterExtended", _filter) if _filter["extended"] != msg.is_extended_id: continue diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 0336b03d3..a80dc3194 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -9,7 +9,7 @@ log = logging.getLogger("can.ctypesutil") -__all__ = ["CLibrary", "HANDLE", "PHANDLE", "HRESULT"] +__all__ = ["HANDLE", "HRESULT", "PHANDLE", "CLibrary"] if sys.platform == "win32": _LibBase = ctypes.WinDLL diff --git a/can/interface.py b/can/interface.py index 2b7e27f8d..f7a8ecc02 100644 --- a/can/interface.py +++ b/can/interface.py @@ -52,7 +52,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]: f"'{interface}': {e}" ) from None - return cast(Type[BusABC], bus_class) + return cast("Type[BusABC]", bus_class) @util.deprecated_args_alias( @@ -138,7 +138,7 @@ def Bus( # noqa: N802 def detect_available_configs( - interfaces: Union[None, str, Iterable[str]] = None + interfaces: Union[None, str, Iterable[str]] = None, ) -> List[AutoDetectedConfig]: """Detect all configurations/channels that the interfaces could currently connect with. diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py index 0252c7345..74cb43af4 100644 --- a/can/interfaces/ics_neovi/__init__.py +++ b/can/interfaces/ics_neovi/__init__.py @@ -1,5 +1,4 @@ -""" -""" +""" """ __all__ = [ "ICSApiError", diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 0579d0942..567dd0cd9 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -35,11 +35,11 @@ from .exceptions import * __all__ = [ - "VCITimeout", - "VCIError", + "IXXATBus", "VCIBusOffError", "VCIDeviceNotFoundError", - "IXXATBus", + "VCIError", + "VCITimeout", "vciFormatError", ] @@ -890,7 +890,7 @@ def __init__( self._count = int(duration / period) if duration else 0 self._msg = structures.CANCYCLICTXMSG() - self._msg.wCycleTime = int(round(period * resolution)) + self._msg.wCycleTime = round(period * resolution) self._msg.dwMsgId = self.messages[0].arbitration_id self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0 diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 5872f76b9..79ad34c4f 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -34,11 +34,11 @@ from .exceptions import * __all__ = [ - "VCITimeout", - "VCIError", + "IXXATBus", "VCIBusOffError", "VCIDeviceNotFoundError", - "IXXATBus", + "VCIError", + "VCITimeout", "vciFormatError", ] @@ -1010,7 +1010,7 @@ def __init__( self._count = int(duration / period) if duration else 0 self._msg = structures.CANCYCLICTXMSG2() - self._msg.wCycleTime = int(round(period * resolution)) + self._msg.wCycleTime = round(period * resolution) self._msg.dwMsgId = self.messages[0].arbitration_id self._msg.uMsgInfo.Bits.type = constants.CAN_MSGTYPE_DATA self._msg.uMsgInfo.Bits.ext = 1 if self.messages[0].is_extended_id else 0 diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index 50b84dfa4..771eec307 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -12,11 +12,11 @@ ) __all__ = [ - "VCITimeout", - "VCIError", - "VCIRxQueueEmptyError", "VCIBusOffError", "VCIDeviceNotFoundError", + "VCIError", + "VCIRxQueueEmptyError", + "VCITimeout", ] diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index 611955db7..680ed1ac6 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -201,13 +201,13 @@ class CANBTP(ctypes.Structure): ] def __str__(self): - return "dwMode=%d, dwBPS=%d, wTS1=%d, wTS2=%d, wSJW=%d, wTDO=%d" % ( - self.dwMode, - self.dwBPS, - self.wTS1, - self.wTS2, - self.wSJW, - self.wTDO, + return ( + f"dwMode={self.dwMode:d}, " + f"dwBPS={self.dwBPS:d}, " + f"wTS1={self.wTS1:d}, " + f"wTS2={self.wTS2:d}, " + f"wSJW={self.wSJW:d}, " + f"wTDO={self.wTDO:d}" ) diff --git a/can/interfaces/kvaser/__init__.py b/can/interfaces/kvaser/__init__.py index 8e7c3feb9..7cfb13d8d 100644 --- a/can/interfaces/kvaser/__init__.py +++ b/can/interfaces/kvaser/__init__.py @@ -1,5 +1,4 @@ -""" -""" +""" """ __all__ = [ "CANLIBInitializationError", diff --git a/can/interfaces/neousys/__init__.py b/can/interfaces/neousys/__init__.py index 44bd3127f..f3e0cb039 100644 --- a/can/interfaces/neousys/__init__.py +++ b/can/interfaces/neousys/__init__.py @@ -1,4 +1,4 @@ -""" Neousys CAN bus driver """ +"""Neousys CAN bus driver""" __all__ = [ "NeousysBus", diff --git a/can/interfaces/neousys/neousys.py b/can/interfaces/neousys/neousys.py index cb9d0174d..7e8c877b4 100644 --- a/can/interfaces/neousys/neousys.py +++ b/can/interfaces/neousys/neousys.py @@ -1,4 +1,4 @@ -""" Neousys CAN bus driver """ +"""Neousys CAN bus driver""" # # This kind of interface can be found for example on Neousys POC-551VTC diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py index 73e351665..a3eafcb4d 100644 --- a/can/interfaces/pcan/__init__.py +++ b/can/interfaces/pcan/__init__.py @@ -1,5 +1,4 @@ -""" -""" +""" """ __all__ = [ "PcanBus", diff --git a/can/interfaces/seeedstudio/__init__.py b/can/interfaces/seeedstudio/__init__.py index 0b3c7b1c9..9466bde43 100644 --- a/can/interfaces/seeedstudio/__init__.py +++ b/can/interfaces/seeedstudio/__init__.py @@ -1,6 +1,3 @@ -""" -""" - __all__ = [ "SeeedBus", "seeedstudio", diff --git a/can/interfaces/serial/__init__.py b/can/interfaces/serial/__init__.py index beb6457bb..6327530d7 100644 --- a/can/interfaces/serial/__init__.py +++ b/can/interfaces/serial/__init__.py @@ -1,5 +1,4 @@ -""" -""" +""" """ __all__ = [ "SerialBus", diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index f0a04e305..ddd2bec23 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -305,7 +305,7 @@ def shutdown(self) -> None: def fileno(self) -> int: try: - return cast(int, self.serialPortOrig.fileno()) + return cast("int", self.serialPortOrig.fileno()) except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 679c9cefc..1505c6cf8 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -28,7 +28,7 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes can_id = can_filter["can_id"] can_mask = can_filter["can_mask"] if "extended" in can_filter: - can_filter = cast(typechecking.CanFilterExtended, can_filter) + can_filter = cast("typechecking.CanFilterExtended", can_filter) # Match on either 11-bit OR 29-bit messages instead of both can_mask |= CAN_EFF_FLAG if can_filter["extended"]: diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index a2b587842..f818130ee 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -1,12 +1,11 @@ -""" -""" +""" """ __all__ = [ "Usb2CanAbstractionLayer", "Usb2canBus", "serial_selector", - "usb2canabstractionlayer", "usb2canInterface", + "usb2canabstractionlayer", ] from .usb2canabstractionlayer import Usb2CanAbstractionLayer diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index 92a3a07a2..de29f03fe 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -1,5 +1,4 @@ -""" -""" +""" """ import logging from typing import List diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index 3a5b13a1f..e78783f1f 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,5 +1,4 @@ -""" -""" +""" """ __all__ = [ "VectorBus", diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index d307d076f..d51d74529 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -220,7 +220,7 @@ def __init__( # at the same time. If the VectorBus is instantiated with a config, that was returned from # VectorBus._detect_available_configs(), then use the contained global channel_index # to avoid any ambiguities. - channel_index = cast(int, _channel_index) + channel_index = cast("int", _channel_index) else: channel_index = self._find_global_channel_idx( channel=channel, @@ -410,7 +410,7 @@ def _find_global_channel_idx( app_name, channel ) idx = cast( - int, self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) + "int", self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) ) if idx < 0: # Undocumented behavior! See issue #353. diff --git a/can/io/__init__.py b/can/io/__init__.py index 5601f2591..69894c3d0 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -4,22 +4,22 @@ """ __all__ = [ + "MESSAGE_READERS", + "MESSAGE_WRITERS", "ASCReader", "ASCWriter", - "BaseRotatingLogger", "BLFReader", "BLFWriter", - "CanutilsLogReader", - "CanutilsLogWriter", + "BaseRotatingLogger", "CSVReader", "CSVWriter", - "Logger", + "CanutilsLogReader", + "CanutilsLogWriter", "LogReader", - "MESSAGE_READERS", - "MESSAGE_WRITERS", - "MessageSync", + "Logger", "MF4Reader", "MF4Writer", + "MessageSync", "Printer", "SizedRotatingLogger", "SqliteReader", diff --git a/can/io/blf.py b/can/io/blf.py index f6e1b6d4d..139b44285 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -162,8 +162,12 @@ def __init__( self.file_size = header[10] self.uncompressed_size = header[11] self.object_count = header[12] - self.start_timestamp = systemtime_to_timestamp(cast(TSystemTime, header[14:22])) - self.stop_timestamp = systemtime_to_timestamp(cast(TSystemTime, header[22:30])) + self.start_timestamp = systemtime_to_timestamp( + cast("TSystemTime", header[14:22]) + ) + self.stop_timestamp = systemtime_to_timestamp( + cast("TSystemTime", header[22:30]) + ) # Read rest of header self.file.read(header[1] - FILE_HEADER_STRUCT.size) self._tail = b"" @@ -429,10 +433,10 @@ def __init__( self.uncompressed_size = header[11] self.object_count = header[12] self.start_timestamp: Optional[float] = systemtime_to_timestamp( - cast(TSystemTime, header[14:22]) + cast("TSystemTime", header[14:22]) ) self.stop_timestamp: Optional[float] = systemtime_to_timestamp( - cast(TSystemTime, header[22:30]) + cast("TSystemTime", header[22:30]) ) # Jump to the end of the file self.file.seek(0, 2) diff --git a/can/io/canutils.py b/can/io/canutils.py index d7ae99daf..cc978d4f2 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -165,7 +165,7 @@ def on_message_received(self, msg): timestamp = msg.timestamp channel = msg.channel if msg.channel is not None else self.channel - if isinstance(channel, int) or isinstance(channel, str) and channel.isdigit(): + if isinstance(channel, int) or (isinstance(channel, str) and channel.isdigit()): channel = f"can{channel}" framestr = f"({timestamp:f}) {channel}" diff --git a/can/io/generic.py b/can/io/generic.py index 55468ff16..93840f807 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -50,7 +50,7 @@ def __init__( """ if file is None or (hasattr(file, "read") and hasattr(file, "write")): # file is None or some file-like object - self.file = cast(Optional[typechecking.FileLike], file) + self.file = cast("Optional[typechecking.FileLike]", file) else: encoding: Optional[str] = ( None @@ -60,8 +60,10 @@ def __init__( # pylint: disable=consider-using-with # file is some path-like object self.file = cast( - typechecking.FileLike, - open(cast(typechecking.StringPathLike, file), mode, encoding=encoding), + "typechecking.FileLike", + open( + cast("typechecking.StringPathLike", file), mode, encoding=encoding + ), ) # for multiple inheritance diff --git a/can/io/logger.py b/can/io/logger.py index f54223741..359aae4ac 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -268,7 +268,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: if isinstance(logger, FileIOMessageWriter): return logger elif isinstance(logger, Printer) and logger.file is not None: - return cast(FileIOMessageWriter, logger) + return cast("FileIOMessageWriter", logger) raise ValueError( f'The log format of "{pathlib.Path(filename).name}" ' diff --git a/can/io/mf4.py b/can/io/mf4.py index 4f5336b42..9efa1d83d 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -124,7 +124,7 @@ def __init__( super().__init__(file, mode="w+b") now = datetime.now() - self._mdf = cast(MDF4, MDF(version="4.10")) + self._mdf = cast("MDF4", MDF(version="4.10")) self._mdf.header.start_time = now self.last_timestamp = self._start_time = now.timestamp() @@ -184,7 +184,9 @@ def __init__( def file_size(self) -> int: """Return an estimate of the current file size in bytes.""" # TODO: find solution without accessing private attributes of asammdf - return cast(int, self._mdf._tempfile.tell()) # pylint: disable=protected-access + return cast( + "int", self._mdf._tempfile.tell() # pylint: disable=protected-access + ) def stop(self) -> None: self._mdf.save(self.file, compression=self._compression_level) @@ -463,9 +465,9 @@ def __init__( self._mdf: MDF4 if isinstance(file, BufferedIOBase): - self._mdf = cast(MDF4, MDF(BytesIO(file.read()))) + self._mdf = cast("MDF4", MDF(BytesIO(file.read()))) else: - self._mdf = cast(MDF4, MDF(file)) + self._mdf = cast("MDF4", MDF(file)) self._start_timestamp = self._mdf.header.start_time.timestamp() diff --git a/can/io/printer.py b/can/io/printer.py index 67c353cc6..30bc227ab 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -44,7 +44,7 @@ def __init__( def on_message_received(self, msg: Message) -> None: if self.write_to_file: - cast(TextIO, self.file).write(str(msg) + "\n") + cast("TextIO", self.file).write(str(msg) + "\n") else: print(msg) # noqa: T201 diff --git a/can/message.py b/can/message.py index 6dc6a83bd..d8d94ea84 100644 --- a/can/message.py +++ b/can/message.py @@ -32,19 +32,19 @@ class Message: # pylint: disable=too-many-instance-attributes; OK for a datacla """ __slots__ = ( - "timestamp", + "__weakref__", # support weak references to messages "arbitration_id", - "is_extended_id", - "is_remote_frame", - "is_error_frame", + "bitrate_switch", "channel", - "dlc", "data", + "dlc", + "error_state_indicator", + "is_error_frame", + "is_extended_id", "is_fd", + "is_remote_frame", "is_rx", - "bitrate_switch", - "error_state_indicator", - "__weakref__", # support weak references to messages + "timestamp", ) def __init__( # pylint: disable=too-many-locals, too-many-arguments diff --git a/can/player.py b/can/player.py index 40e4cc43a..9deb0c51f 100644 --- a/can/player.py +++ b/can/player.py @@ -9,12 +9,17 @@ import errno import sys from datetime import datetime -from typing import Iterable, cast +from typing import TYPE_CHECKING, cast -from can import LogReader, Message, MessageSync +from can import LogReader, MessageSync from .logger import _create_base_argument_parser, _create_bus, _parse_additional_config +if TYPE_CHECKING: + from typing import Iterable + + from can import Message + def main() -> None: parser = argparse.ArgumentParser(description="Replay CAN traffic.") @@ -88,7 +93,7 @@ def main() -> None: with _create_bus(results, **additional_config) as bus: with LogReader(results.infile, **additional_config) as reader: in_sync = MessageSync( - cast(Iterable[Message], reader), + cast("Iterable[Message]", reader), timestamps=results.timestamps, gap=results.gap, skip=results.skip, diff --git a/can/typechecking.py b/can/typechecking.py index b0d1c22ac..d993d6bd3 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -1,5 +1,4 @@ -"""Types for mypy type-checking -""" +"""Types for mypy type-checking""" import gzip import struct diff --git a/can/util.py b/can/util.py index 7f2f824db..bb835b846 100644 --- a/can/util.py +++ b/can/util.py @@ -178,7 +178,7 @@ def load_config( # Use the given dict for default values config_sources = cast( - Iterable[Union[Dict[str, Any], Callable[[Any], Dict[str, Any]]]], + "Iterable[Union[Dict[str, Any], Callable[[Any], Dict[str, Any]]]]", [ given_config, can.rc, @@ -251,7 +251,7 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: if "fd" in config: config["fd"] = config["fd"] not in (0, False) - return cast(typechecking.BusConfig, config) + return cast("typechecking.BusConfig", config) def _dict2timing(data: Dict[str, Any]) -> Union[BitTiming, BitTimingFd, None]: @@ -396,7 +396,7 @@ def _rename_kwargs( func_name: str, start: str, end: Optional[str], - kwargs: P1.kwargs, + kwargs: Dict[str, Any], aliases: Dict[str, Optional[str]], ) -> None: """Helper function for `deprecated_args_alias`""" diff --git a/can/viewer.py b/can/viewer.py index 45c313b07..57b3d6f7d 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -166,9 +166,8 @@ def unpack_data(cmd: int, cmd_to_struct: Dict, data: bytes) -> List[float]: # These messages do not contain a data package return [] - for key in cmd_to_struct: + for key, value in cmd_to_struct.items(): if cmd == key if isinstance(key, int) else cmd in key: - value = cmd_to_struct[key] if isinstance(value, tuple): # The struct is given as the fist argument struct_t: struct.Struct = value[0] diff --git a/pyproject.toml b/pyproject.toml index d41bf6e22..c65275559 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,9 +61,9 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==3.2.*", - "ruff==0.7.0", - "black==24.10.*", - "mypy==1.12.*", + "ruff==0.10.0", + "black==25.1.*", + "mypy==1.15.*", ] pywin32 = ["pywin32>=305; platform_system == 'Windows' and platform_python_implementation == 'CPython'"] seeedstudio = ["pyserial>=3.0"] @@ -133,7 +133,7 @@ exclude = [ line-length = 100 [tool.ruff.lint] -select = [ +extend-select = [ "A", # flake8-builtins "B", # flake8-bugbear "C4", # flake8-comprehensions diff --git a/test/listener_test.py b/test/listener_test.py index b530afa60..54496176b 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -""" -""" +""" """ import asyncio import logging import os diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py index 65d9ee74b..3bdd281d2 100755 --- a/test/test_interface_canalystii.py +++ b/test/test_interface_canalystii.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -""" -""" +""" """ import unittest from ctypes import c_ubyte diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 8d18976aa..c18b3bc15 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -""" -""" +""" """ import ctypes import time diff --git a/test/test_neovi.py b/test/test_neovi.py index d8f54960a..8c816bef2 100644 --- a/test/test_neovi.py +++ b/test/test_neovi.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -""" -""" +""" """ import pickle import unittest diff --git a/test/test_vector.py b/test/test_vector.py index ca46526fb..5d074f614 100644 --- a/test/test_vector.py +++ b/test/test_vector.py @@ -1042,142 +1042,142 @@ def _find_virtual_can_serial() -> int: XL_DRIVER_CONFIG_EXAMPLE = ( - b"\x0E\x00\x1E\x14\x0C\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x0e\x00\x1e\x14\x0c\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E" - b"\x65\x6C\x20\x53\x74\x72\x65\x61\x6D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x2D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04" - b"\x0A\x40\x00\x02\x00\x02\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4e\x38\x39\x31\x34\x20\x43\x68\x61\x6e\x6e" + b"\x65\x6c\x20\x53\x74\x72\x65\x61\x6d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x2d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04" + b"\x0a\x40\x00\x02\x00\x02\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E" - b"\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08" - b"\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x8e" + b"\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00\x00\x08" + b"\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31" - b"\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x01\x03\x02\x00\x00\x00\x00\x01\x02\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4e\x38\x39\x31" + b"\x34\x20\x43\x68\x61\x6e\x6e\x65\x6c\x20\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x2d\x00\x01\x03\x02\x00\x00\x00\x00\x01\x02\x00\x00" b"\x00\x00\x00\x00\x00\x02\x10\x00\x08\x07\x01\x04\x00\x00\x00\x00\x00\x00\x04\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00" - b"\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x0a\x04\x00" + b"\x00\x00\x00\x00\x00\x00\x8e\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x46\x52\x70\x69\x67\x67\x79\x20\x31\x30" - b"\x38\x30\x41\x6D\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\xe9\x03\x00\x00\x08\x1c\x00\x00\x46\x52\x70\x69\x67\x67\x79\x20\x31\x30" + b"\x38\x30\x41\x6d\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x05\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x32\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x02\x3C\x01\x00" - b"\x00\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\xA2\x03\x05\x01\x00" - b"\x00\x00\x04\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01\x00\x00" + b"\x00\x00\x56\x4e\x38\x39\x31\x34\x20\x43\x68\x61\x6e\x6e\x65\x6c\x20\x32\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2d\x00\x02\x3c\x01\x00" + b"\x00\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\xa2\x03\x05\x01\x00" + b"\x00\x00\x04\x00\x00\x01\x00\x00\x00\x20\xa1\x07\x00\x01\x04\x03\x01\x01\x00\x00" b"\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00" + b"\x00\x0c\x00\x02\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x8e\x00\x02\x0a\x00\x00\x00" b"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4F\x6E\x20" - b"\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35\x31\x63\x61\x70\x28\x48\x69" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00\x00\x08\x1c\x00\x00\x4f\x6e\x20" + b"\x62\x6f\x61\x72\x64\x20\x43\x41\x4e\x20\x31\x30\x35\x31\x63\x61\x70\x28\x48\x69" b"\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03" b"\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E" - b"\x6E\x65\x6C\x20\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x2D\x00\x03\x3C\x01\x00\x00\x00\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x12" - b"\x00\x00\xA2\x03\x09\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00" - b"\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x9B\x00\x00\x00\x68\x89\x09" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00" - b"\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00" - b"\x08\x1C\x00\x00\x4F\x6E\x20\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4e\x38\x39\x31\x34\x20\x43\x68\x61\x6e" + b"\x6e\x65\x6c\x20\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x2d\x00\x03\x3c\x01\x00\x00\x00\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x12" + b"\x00\x00\xa2\x03\x09\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xa1\x07\x00" + b"\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x9b\x00\x00\x00\x68\x89\x09" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x0a\x04\x00\x00\x00\x00\x00\x00\x00" + b"\x8e\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00\x00" + b"\x08\x1c\x00\x00\x4f\x6e\x20\x62\x6f\x61\x72\x64\x20\x43\x41\x4e\x20\x31\x30\x35" b"\x31\x63\x61\x70\x28\x48\x69\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39" - b"\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x34\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x04\x33\x01\x00\x00\x00\x00\x04\x10\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4e\x38\x39" + b"\x31\x34\x20\x43\x68\x61\x6e\x6e\x65\x6c\x20\x34\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x2d\x00\x04\x33\x01\x00\x00\x00\x00\x04\x10\x00" b"\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x09\x02\x08\x00\x00\x00\x00\x00\x02" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x03" - b"\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x0a\x03" + b"\x00\x00\x00\x00\x00\x00\x00\x8e\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00\x01\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4C\x49\x4E\x70\x69\x67\x67\x79\x20" - b"\x37\x32\x36\x39\x6D\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x07\x00\x00\x00\x70\x17\x00\x00\x0C\x09\x03\x04\x58\x02\x10\x0E\x30" + b"\x00\x00\x00\xe9\x03\x00\x00\x08\x1c\x00\x00\x4c\x49\x4e\x70\x69\x67\x67\x79\x20" + b"\x37\x32\x36\x39\x6d\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x07\x00\x00\x00\x70\x17\x00\x00\x0c\x09\x03\x04\x58\x02\x10\x0e\x30" b"\x57\x05\x00\x00\x00\x00\x00\x88\x13\x88\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x35\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x05\x00\x00" + b"\x00\x00\x00\x56\x4e\x38\x39\x31\x34\x20\x43\x68\x61\x6e\x6e\x65\x6c\x20\x35\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2d\x00\x05\x00\x00" b"\x00\x00\x02\x00\x05\x20\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x0C\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00" + b"\x00\x00\x0c\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x8e\x00\x02\x0a\x00\x00" b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00\x00\x08\x1c\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61" - b"\x6E\x6E\x65\x6C\x20\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x2D\x00\x06\x00\x00\x00\x00\x02\x00\x06\x40\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4e\x38\x39\x31\x34\x20\x43\x68\x61" + b"\x6e\x6e\x65\x6c\x20\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x2d\x00\x06\x00\x00\x00\x00\x02\x00\x06\x40\x00\x00\x00\x00\x00\x00\x00" b"\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00" - b"\x00\x08\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x8e\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00" + b"\x00\x08\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38" - b"\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x37\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x07\x00\x00\x00\x00\x02\x00\x07\x80" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4e\x38" + b"\x39\x31\x34\x20\x43\x68\x61\x6e\x6e\x65\x6c\x20\x37\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2d\x00\x07\x00\x00\x00\x00\x02\x00\x07\x80" b"\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x0a" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x8e\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00\x01" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\xe9\x03\x00\x00\x08\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x38" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x08\x3C" - b"\x01\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x12\x00\x00\xA2\x01\x00" - b"\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01" + b"\x00\x00\x00\x00\x56\x4e\x38\x39\x31\x34\x20\x43\x68\x61\x6e\x6e\x65\x6c\x20\x38" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2d\x00\x08\x3c" + b"\x01\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x12\x00\x00\xa2\x01\x00" + b"\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xa1\x07\x00\x01\x04\x03\x01\x01" b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00" + b"\x00\x00\x00\x0c\x00\x02\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x8e\x00\x02\x0a\x00" b"\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4F" - b"\x6E\x20\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35\x31\x63\x61\x70\x28" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00\x00\x08\x1c\x00\x00\x4f" + b"\x6e\x20\x62\x6f\x61\x72\x64\x20\x43\x41\x4e\x20\x31\x30\x35\x31\x63\x61\x70\x28" b"\x48\x69\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68" - b"\x61\x6E\x6E\x65\x6C\x20\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2D\x00\x09\x80\x02\x00\x00\x00\x00\x09\x00\x02\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4e\x38\x39\x31\x34\x20\x43\x68" + b"\x61\x6e\x6e\x65\x6c\x20\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x2d\x00\x09\x80\x02\x00\x00\x00\x00\x09\x00\x02\x00\x00\x00\x00\x00" b"\x00\x02\x00\x00\x00\x40\x00\x40\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x03\x00\x00\x00\x00\x00" - b"\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03" - b"\x00\x00\x08\x1C\x00\x00\x44\x2F\x41\x20\x49\x4F\x70\x69\x67\x67\x79\x20\x38\x36" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x02\x0a\x03\x00\x00\x00\x00\x00" + b"\x00\x00\x8e\x00\x02\x0a\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9\x03" + b"\x00\x00\x08\x1c\x00\x00\x44\x2f\x41\x20\x49\x4f\x70\x69\x67\x67\x79\x20\x38\x36" b"\x34\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69" - b"\x72\x74\x75\x61\x6C\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x31\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x16\x00\x00\x00\x00\x00\x0A" - b"\x00\x04\x00\x00\x00\x00\x00\x00\x07\x00\x00\xA0\x01\x00\x01\x00\x00\x00\x00\x00" - b"\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00" - b"\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x1E" + b"\x72\x74\x75\x61\x6c\x20\x43\x68\x61\x6e\x6e\x65\x6c\x20\x31\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x16\x00\x00\x00\x00\x00\x0a" + b"\x00\x04\x00\x00\x00\x00\x00\x00\x07\x00\x00\xa0\x01\x00\x01\x00\x00\x00\x00\x00" + b"\x00\x01\x00\x00\x00\x20\xa1\x07\x00\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00" + b"\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x1e" b"\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6C" - b"\x20\x43\x41\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6c" + b"\x20\x43\x41\x4e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6C\x20\x43\x68\x61\x6E\x6E\x65\x6C" + b"\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6c\x20\x43\x68\x61\x6e\x6e\x65\x6c" b"\x20\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01" - b"\x16\x00\x00\x00\x00\x00\x0B\x00\x08\x00\x00\x00\x00\x00\x00\x07\x00\x00\xA0\x01" - b"\x00\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01" + b"\x16\x00\x00\x00\x00\x00\x0b\x00\x08\x00\x00\x00\x00\x00\x00\x07\x00\x00\xa0\x01" + b"\x00\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xa1\x07\x00\x01\x04\x03\x01" b"\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x10\x00\x1E\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x10\x00\x1e\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x56\x69\x72\x74\x75\x61\x6C\x20\x43\x41\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x56\x69\x72\x74\x75\x61\x6c\x20\x43\x41\x4e\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x02" + 11832 * b"\x00" ) diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py index 4e7596caf..d6693e294 100644 --- a/test/zero_dlc_test.py +++ b/test/zero_dlc_test.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -""" -""" +""" """ import logging import unittest From 5d62394006f42d1bf98e159fe9bb08d10e47e6eb Mon Sep 17 00:00:00 2001 From: Pierre-Luc Date: Tue, 1 Apr 2025 11:32:10 -0400 Subject: [PATCH 1183/1235] Refactor timestamp factor calculations to use constants (#1933) --- can/io/blf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/can/io/blf.py b/can/io/blf.py index 139b44285..e64a2247d 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -99,6 +99,9 @@ class BLFParseError(Exception): TIME_TEN_MICS = 0x00000001 TIME_ONE_NANS = 0x00000002 +TIME_TEN_MICS_FACTOR = Decimal("1e-5") +TIME_ONE_NANS_FACTOR = Decimal("1e-9") + def timestamp_to_systemtime(timestamp: float) -> TSystemTime: if timestamp is None or timestamp < 631152000: @@ -269,7 +272,7 @@ def _parse_data(self, data): continue # Calculate absolute timestamp in seconds - factor = Decimal("1e-5") if flags == 1 else Decimal("1e-9") + factor = TIME_TEN_MICS_FACTOR if flags == 1 else TIME_ONE_NANS_FACTOR timestamp = float(Decimal(timestamp) * factor) + start_timestamp if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): From c46d3ea7bd631df633f2588877cfcd94ac0802e3 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 30 May 2025 11:14:53 +0200 Subject: [PATCH 1184/1235] Raise minimum python version to 3.9 (#1931) * require python>=3.9 * fix formatting * pin ruff==0.11.12, mypy==1.16.* --------- Co-authored-by: zariiii9003 --- .github/workflows/ci.yml | 4 ---- README.rst | 7 ++---- can/__init__.py | 4 ++-- can/_entry_points.py | 6 +++--- can/bit_timing.py | 7 +++--- can/broadcastmanager.py | 7 +++--- can/bus.py | 14 +++++------- can/ctypesutil.py | 4 ++-- can/exceptions.py | 11 +++------- can/interface.py | 9 ++++---- can/interfaces/__init__.py | 4 +--- can/interfaces/canalystii.py | 11 +++++----- can/interfaces/etas/__init__.py | 10 ++++----- can/interfaces/gs_usb.py | 4 ++-- can/interfaces/iscan.py | 4 ++-- can/interfaces/ixxat/canlib.py | 5 +++-- can/interfaces/ixxat/canlib_vcinpl.py | 7 +++--- can/interfaces/ixxat/canlib_vcinpl2.py | 5 +++-- can/interfaces/nican.py | 6 +++--- can/interfaces/nixnet.py | 6 +++--- can/interfaces/pcan/pcan.py | 6 +++--- can/interfaces/serial/serial_can.py | 8 +++---- can/interfaces/slcan.py | 6 +++--- can/interfaces/socketcan/socketcan.py | 21 +++++++++--------- can/interfaces/socketcan/utils.py | 4 ++-- can/interfaces/socketcand/socketcand.py | 5 ++--- can/interfaces/systec/exceptions.py | 9 ++++---- can/interfaces/udp_multicast/bus.py | 10 ++++----- can/interfaces/udp_multicast/utils.py | 4 ++-- can/interfaces/usb2can/serial_selector.py | 3 +-- can/interfaces/vector/canlib.py | 24 +++++++++------------ can/interfaces/virtual.py | 8 +++---- can/io/asc.py | 13 ++++++------ can/io/blf.py | 7 +++--- can/io/canutils.py | 3 ++- can/io/csv.py | 3 ++- can/io/generic.py | 14 +++++------- can/io/logger.py | 16 ++++++-------- can/io/mf4.py | 11 +++++----- can/io/player.py | 12 ++++------- can/io/sqlite.py | 3 ++- can/io/trc.py | 21 +++++++++--------- can/listener.py | 3 ++- can/logger.py | 19 +++++++---------- can/notifier.py | 9 ++++---- can/player.py | 2 +- can/typechecking.py | 10 ++++----- can/util.py | 26 +++++++++++------------ can/viewer.py | 9 ++++---- pyproject.toml | 7 +++--- test/contextmanager_test.py | 14 ++++++------ 51 files changed, 209 insertions(+), 236 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ddb3028c..ab14b6b6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] experimental: [false] python-version: [ - "3.8", "3.9", "3.10", "3.11", @@ -81,9 +80,6 @@ jobs: run: | python -m pip install --upgrade pip pip install -e .[lint] - - name: mypy 3.8 - run: | - mypy --python-version 3.8 . - name: mypy 3.9 run: | mypy --python-version 3.9 . diff --git a/README.rst b/README.rst index d2f05b2c1..3c185f6cb 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ python-can |pypi| |conda| |python_implementation| |downloads| |downloads_monthly| -|docs| |github-actions| |coverage| |mergify| |formatter| +|docs| |github-actions| |coverage| |formatter| .. |pypi| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ @@ -41,10 +41,6 @@ python-can :target: https://coveralls.io/github/hardbyte/python-can?branch=develop :alt: Test coverage reports on Coveralls.io -.. |mergify| image:: https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/hardbyte/python-can&style=flat - :target: https://mergify.io - :alt: Mergify Status - 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 @@ -64,6 +60,7 @@ Library Version Python 3.x 2.7+, 3.5+ 4.0+ 3.7+ 4.3+ 3.8+ + 4.6+ 3.9+ ============================== =========== diff --git a/can/__init__.py b/can/__init__.py index 1d4b7f0cf..b1bd636c1 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import contextlib import logging from importlib.metadata import PackageNotFoundError, version -from typing import Any, Dict +from typing import Any __all__ = [ "VALID_INTERFACES", @@ -130,4 +130,4 @@ log = logging.getLogger("can") -rc: Dict[str, Any] = {} +rc: dict[str, Any] = {} diff --git a/can/_entry_points.py b/can/_entry_points.py index 6842e3c1a..e8ce92d7c 100644 --- a/can/_entry_points.py +++ b/can/_entry_points.py @@ -2,7 +2,7 @@ import sys from dataclasses import dataclass from importlib.metadata import entry_points -from typing import Any, List +from typing import Any @dataclass @@ -20,14 +20,14 @@ def load(self) -> Any: # "Compatibility Note". if sys.version_info >= (3, 10): - def read_entry_points(group: str) -> List[_EntryPoint]: + def read_entry_points(group: str) -> list[_EntryPoint]: return [ _EntryPoint(ep.name, ep.module, ep.attr) for ep in entry_points(group=group) ] else: - def read_entry_points(group: str) -> List[_EntryPoint]: + def read_entry_points(group: str) -> list[_EntryPoint]: return [ _EntryPoint(ep.name, *ep.value.split(":", maxsplit=1)) for ep in entry_points().get(group, []) diff --git a/can/bit_timing.py b/can/bit_timing.py index feba8b6d2..4b0074472 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -1,6 +1,7 @@ # pylint: disable=too-many-lines import math -from typing import TYPE_CHECKING, Iterator, List, Mapping, cast +from collections.abc import Iterator, Mapping +from typing import TYPE_CHECKING, cast if TYPE_CHECKING: from can.typechecking import BitTimingDict, BitTimingFdDict @@ -286,7 +287,7 @@ def from_sample_point( if sample_point < 50.0: raise ValueError(f"sample_point (={sample_point}) must not be below 50%.") - possible_solutions: List[BitTiming] = list( + possible_solutions: list[BitTiming] = list( cls.iterate_from_sample_point(f_clock, bitrate, sample_point) ) @@ -874,7 +875,7 @@ def from_sample_point( f"data_sample_point (={data_sample_point}) must not be below 50%." ) - possible_solutions: List[BitTimingFd] = list( + possible_solutions: list[BitTimingFd] = list( cls.iterate_from_sample_point( f_clock, nom_bitrate, diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 19dca8fbc..b2bc28e76 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -12,13 +12,12 @@ import threading import time import warnings +from collections.abc import Sequence from typing import ( TYPE_CHECKING, Callable, Final, Optional, - Sequence, - Tuple, Union, cast, ) @@ -127,7 +126,7 @@ def __init__( @staticmethod def _check_and_convert_messages( messages: Union[Sequence[Message], Message], - ) -> Tuple[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. @@ -194,7 +193,7 @@ def start(self) -> None: class ModifiableCyclicTaskABC(CyclicSendTaskABC, abc.ABC): - def _check_modified_messages(self, messages: Tuple[Message, ...]) -> None: + def _check_modified_messages(self, messages: tuple[Message, ...]) -> None: """Helper function to perform error checking when modifying the data in the cyclic task. diff --git a/can/bus.py b/can/bus.py index d186e2d05..0d031a18b 100644 --- a/can/bus.py +++ b/can/bus.py @@ -6,18 +6,14 @@ import logging import threading from abc import ABC, ABCMeta, abstractmethod +from collections.abc import Iterator, Sequence from enum import Enum, auto from time import time from types import TracebackType from typing import ( Any, Callable, - Iterator, - List, Optional, - Sequence, - Tuple, - Type, Union, cast, ) @@ -97,7 +93,7 @@ def __init__( :raises ~can.exceptions.CanInitializationError: If the bus cannot be initialized """ - self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] + self._periodic_tasks: list[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) # Flip the class default value when the constructor finishes. That # usually means the derived class constructor was also successful, @@ -147,7 +143,7 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> 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` @@ -491,7 +487,7 @@ def __enter__(self) -> Self: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> None: @@ -529,7 +525,7 @@ def protocol(self) -> CanProtocol: return self._can_protocol @staticmethod - def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: + def _detect_available_configs() -> list[can.typechecking.AutoDetectedConfig]: """Detect all configurations/channels that this interface could currently connect with. diff --git a/can/ctypesutil.py b/can/ctypesutil.py index a80dc3194..8336941be 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -5,7 +5,7 @@ import ctypes import logging import sys -from typing import Any, Callable, Optional, Tuple, Union +from typing import Any, Callable, Optional, Union log = logging.getLogger("can.ctypesutil") @@ -32,7 +32,7 @@ def map_symbol( self, func_name: str, restype: Any = None, - argtypes: Tuple[Any, ...] = (), + argtypes: tuple[Any, ...] = (), errcheck: Optional[Callable[..., Any]] = None, ) -> Any: """ diff --git a/can/exceptions.py b/can/exceptions.py index ca2d6c5a3..8abc75147 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -15,14 +15,9 @@ :class:`ValueError`. This should always be documented for the function at hand. """ -import sys +from collections.abc import Generator from contextlib import contextmanager -from typing import Optional, Type - -if sys.version_info >= (3, 9): - from collections.abc import Generator -else: - from typing import Generator +from typing import Optional class CanError(Exception): @@ -114,7 +109,7 @@ class CanTimeoutError(CanError, TimeoutError): @contextmanager def error_check( error_message: Optional[str] = None, - exception_type: Type[CanError] = CanOperationError, + exception_type: type[CanError] = CanOperationError, ) -> Generator[None, None, None]: """Catches any exceptions and turns them into the new type while preserving the stack trace.""" try: diff --git a/can/interface.py b/can/interface.py index f7a8ecc02..e017b9514 100644 --- a/can/interface.py +++ b/can/interface.py @@ -6,7 +6,8 @@ import importlib import logging -from typing import Any, Iterable, List, Optional, Type, Union, cast +from collections.abc import Iterable +from typing import Any, Optional, Union, cast from . import util from .bus import BusABC @@ -18,7 +19,7 @@ log_autodetect = log.getChild("detect_available_configs") -def _get_class_for_interface(interface: str) -> Type[BusABC]: +def _get_class_for_interface(interface: str) -> type[BusABC]: """ Returns the main bus class for the given interface. @@ -52,7 +53,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]: f"'{interface}': {e}" ) from None - return cast("Type[BusABC]", bus_class) + return cast("type[BusABC]", bus_class) @util.deprecated_args_alias( @@ -139,7 +140,7 @@ def Bus( # noqa: N802 def detect_available_configs( interfaces: Union[None, str, Iterable[str]] = None, -) -> List[AutoDetectedConfig]: +) -> list[AutoDetectedConfig]: """Detect all configurations/channels that the interfaces could currently connect with. diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index f220d28e5..1b401639a 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -2,8 +2,6 @@ Interfaces contain low level implementations that interact with CAN hardware. """ -from typing import Dict, Tuple - from can._entry_points import read_entry_points __all__ = [ @@ -35,7 +33,7 @@ ] # interface_name => (module, classname) -BACKENDS: Dict[str, Tuple[str, str]] = { +BACKENDS: dict[str, tuple[str, str]] = { "kvaser": ("can.interfaces.kvaser", "KvaserBus"), "socketcan": ("can.interfaces.socketcan", "SocketcanBus"), "serial": ("can.interfaces.serial.serial_can", "SerialBus"), diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 2fef19497..d85211130 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,8 +1,9 @@ import logging import time from collections import deque +from collections.abc import Sequence from ctypes import c_ubyte -from typing import Any, Deque, Dict, Optional, Sequence, Tuple, Union +from typing import Any, Optional, Union import canalystii as driver @@ -26,7 +27,7 @@ def __init__( timing: Optional[Union[BitTiming, BitTimingFd]] = None, can_filters: Optional[CanFilters] = None, rx_queue_size: Optional[int] = None, - **kwargs: Dict[str, Any], + **kwargs: dict[str, Any], ): """ @@ -68,7 +69,7 @@ def __init__( self.channels = list(channel) self.channel_info = f"CANalyst-II: device {device}, channels {self.channels}" - self.rx_queue: Deque[Tuple[int, driver.Message]] = deque(maxlen=rx_queue_size) + self.rx_queue: deque[tuple[int, driver.Message]] = deque(maxlen=rx_queue_size) self.device = driver.CanalystDevice(device_index=device) self._can_protocol = CanProtocol.CAN_20 @@ -129,7 +130,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: if timeout is not None and not send_result: raise CanTimeoutError(f"Send timed out after {timeout} seconds") - def _recv_from_queue(self) -> Tuple[Message, bool]: + def _recv_from_queue(self) -> tuple[Message, bool]: """Return a message from the internal receive queue""" channel, raw_msg = self.rx_queue.popleft() @@ -166,7 +167,7 @@ def poll_received_messages(self) -> None: def _recv_internal( self, timeout: Optional[float] = None - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: """ :param timeout: float in seconds diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 2bfcbf427..9d4d0bd2a 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -1,5 +1,5 @@ import time -from typing import Dict, List, Optional, Tuple +from typing import Optional import can from can.exceptions import CanInitializationError @@ -16,7 +16,7 @@ def __init__( bitrate: int = 1000000, fd: bool = True, data_bitrate: int = 2000000, - **kwargs: Dict[str, any], + **kwargs: dict[str, any], ): self.receive_own_messages = receive_own_messages self._can_protocol = can.CanProtocol.CAN_FD if fd else can.CanProtocol.CAN_20 @@ -122,7 +122,7 @@ def __init__( def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[can.Message], bool]: + ) -> tuple[Optional[can.Message], bool]: ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)() ociMsg = OCI_CANMessageEx() ociMsgs[0] = ctypes.pointer(ociMsg) @@ -295,12 +295,12 @@ def state(self, new_state: can.BusState) -> None: raise NotImplementedError("Setting state is not implemented.") @staticmethod - def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: + def _detect_available_configs() -> list[can.typechecking.AutoDetectedConfig]: nodeRange = CSI_NodeRange(CSI_NODE_MIN, CSI_NODE_MAX) tree = ctypes.POINTER(CSI_Tree)() CSI_CreateProtocolTree(ctypes.c_char_p(b""), nodeRange, ctypes.byref(tree)) - nodes: List[Dict[str, str]] = [] + nodes: list[dict[str, str]] = [] def _findNodes(tree, prefix): uri = f"{prefix}/{tree.contents.item.uriName.decode()}" diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 6268350ee..4ab541f43 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -1,5 +1,5 @@ import logging -from typing import Optional, Tuple +from typing import Optional import usb from gs_usb.constants import CAN_EFF_FLAG, CAN_ERR_FLAG, CAN_MAX_DLC, CAN_RTR_FLAG @@ -119,7 +119,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[can.Message], bool]: + ) -> tuple[Optional[can.Message], bool]: """ Read a message from the bus and tell whether it was filtered. This methods may be called by :meth:`~can.BusABC.recv` diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index be0b0dae8..79b4f754d 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -5,7 +5,7 @@ import ctypes import logging import time -from typing import Optional, Tuple, Union +from typing import Optional, Union from can import ( BusABC, @@ -117,7 +117,7 @@ def __init__( def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: raw_msg = MessageExStruct() end_time = time.time() + timeout if timeout is not None else None while True: diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index a1693aeed..e6ad25d57 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,4 +1,5 @@ -from typing import Callable, List, Optional, Sequence, Union +from collections.abc import Sequence +from typing import Callable, Optional, Union import can.interfaces.ixxat.canlib_vcinpl as vcinpl import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 @@ -174,5 +175,5 @@ def state(self) -> BusState: return self.bus.state @staticmethod - def _detect_available_configs() -> List[AutoDetectedConfig]: + def _detect_available_configs() -> list[AutoDetectedConfig]: return vcinpl._detect_available_configs() diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 567dd0cd9..ba8f1870b 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -14,7 +14,8 @@ import logging import sys import warnings -from typing import Callable, List, Optional, Sequence, Tuple, Union +from collections.abc import Sequence +from typing import Callable, Optional, Union from can import ( BusABC, @@ -64,7 +65,7 @@ def __vciFormatErrorExtended( - library_instance: CLibrary, function: Callable, vret: int, args: Tuple + library_instance: CLibrary, function: Callable, vret: int, args: tuple ): """Format a VCI error and attach failed function, decoded HRESULT and arguments :param CLibrary library_instance: @@ -961,7 +962,7 @@ def get_ixxat_hwids(): return hwids -def _detect_available_configs() -> List[AutoDetectedConfig]: +def _detect_available_configs() -> list[AutoDetectedConfig]: config_list = [] # list in wich to store the resulting bus kwargs # used to detect HWID diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 79ad34c4f..f9ac5346b 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -15,7 +15,8 @@ import sys import time import warnings -from typing import Callable, Optional, Sequence, Tuple, Union +from collections.abc import Sequence +from typing import Callable, Optional, Union from can import ( BusABC, @@ -62,7 +63,7 @@ def __vciFormatErrorExtended( - library_instance: CLibrary, function: Callable, vret: int, args: Tuple + library_instance: CLibrary, function: Callable, vret: int, args: tuple ): """Format a VCI error and attach failed function, decoded HRESULT and arguments :param CLibrary library_instance: diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 8a2efade7..1abf0b35f 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -16,7 +16,7 @@ import ctypes import logging import sys -from typing import Optional, Tuple, Type +from typing import Optional import can.typechecking from can import ( @@ -112,7 +112,7 @@ def check_status( result: int, function, arguments, - error_class: Type[NicanError] = NicanOperationError, + error_class: type[NicanError] = NicanOperationError, ) -> int: if result > 0: logger.warning(get_error_message(result)) @@ -281,7 +281,7 @@ def __init__( def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: """ Read a message from a NI-CAN bus. diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index 2b3cbd69a..c723d1f52 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -14,7 +14,7 @@ import warnings from queue import SimpleQueue from types import ModuleType -from typing import Any, List, Optional, Tuple, Union +from typing import Any, Optional, Union import can.typechecking from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message @@ -203,7 +203,7 @@ def fd(self) -> bool: def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: end_time = time.perf_counter() + timeout if timeout is not None else None while True: @@ -327,7 +327,7 @@ def shutdown(self) -> None: self._session_receive.close() @staticmethod - def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: + def _detect_available_configs() -> list[can.typechecking.AutoDetectedConfig]: configs = [] try: diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index d0372a83c..ef3b23e3b 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -6,7 +6,7 @@ import platform import time import warnings -from typing import Any, List, Optional, Tuple, Union +from typing import Any, Optional, Union from packaging import version @@ -502,7 +502,7 @@ def set_device_number(self, device_number): def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: end_time = time.time() + timeout if timeout is not None else None while True: @@ -726,7 +726,7 @@ def _detect_available_configs(): res, value = library_handle.GetValue(PCAN_NONEBUS, PCAN_ATTACHED_CHANNELS) if res != PCAN_ERROR_OK: return interfaces - channel_information: List[TPCANChannelInformation] = list(value) + channel_information: list[TPCANChannelInformation] = list(value) for channel in channel_information: # find channel name in PCAN_CHANNEL_NAMES by value channel_name = next( diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 476cbd624..9fe715267 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -10,7 +10,7 @@ import io import logging import struct -from typing import Any, List, Optional, Tuple +from typing import Any, Optional from can import ( BusABC, @@ -38,7 +38,7 @@ from serial.tools.list_ports import comports as list_comports except ImportError: # If unavailable on some platform, just return nothing - def list_comports() -> List[Any]: + def list_comports() -> list[Any]: return [] @@ -160,7 +160,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: """ Read a message from the serial device. @@ -229,7 +229,7 @@ def fileno(self) -> int: raise CanOperationError("Cannot fetch fileno") from exception @staticmethod - def _detect_available_configs() -> List[AutoDetectedConfig]: + def _detect_available_configs() -> list[AutoDetectedConfig]: return [ {"interface": "serial", "channel": port.device} for port in list_comports() ] diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index ddd2bec23..c51b298cc 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -7,7 +7,7 @@ import time import warnings from queue import SimpleQueue -from typing import Any, Optional, Tuple, Union, cast +from typing import Any, Optional, Union, cast from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message, typechecking from can.exceptions import ( @@ -230,7 +230,7 @@ def close(self) -> None: def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: canId = None remote = False extended = False @@ -315,7 +315,7 @@ def fileno(self) -> int: def get_version( self, timeout: Optional[float] - ) -> Tuple[Optional[int], Optional[int]]: + ) -> tuple[Optional[int], Optional[int]]: """Get HW and SW version of the slcan interface. :param timeout: diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index a5819dcb5..30b75108a 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -15,7 +15,8 @@ import threading import time import warnings -from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type, Union +from collections.abc import Sequence +from typing import Callable, Optional, Union import can from can import BusABC, CanProtocol, Message @@ -50,13 +51,13 @@ # Setup BCM struct def bcm_header_factory( - fields: List[Tuple[str, Union[Type[ctypes.c_uint32], Type[ctypes.c_long]]]], + fields: list[tuple[str, Union[type[ctypes.c_uint32], type[ctypes.c_long]]]], alignment: int = 8, ): curr_stride = 0 - results: List[ - Tuple[ - str, Union[Type[ctypes.c_uint8], Type[ctypes.c_uint32], Type[ctypes.c_long]] + results: list[ + tuple[ + str, Union[type[ctypes.c_uint8], type[ctypes.c_uint32], type[ctypes.c_long]] ] ] = [] pad_index = 0 @@ -292,7 +293,7 @@ def build_bcm_transmit_header( # Note `TX_COUNTEVT` creates the message TX_EXPIRED when count expires flags |= constants.TX_COUNTEVT - def split_time(value: float) -> Tuple[int, int]: + def split_time(value: float) -> tuple[int, int]: """Given seconds as a float, return whole seconds and microseconds""" seconds = int(value) microseconds = int(1e6 * (value - seconds)) @@ -326,7 +327,7 @@ def is_frame_fd(frame: bytes): return len(frame) == constants.CANFD_MTU -def dissect_can_frame(frame: bytes) -> Tuple[int, int, int, bytes]: +def dissect_can_frame(frame: bytes) -> tuple[int, int, int, bytes]: can_id, data_len, flags, len8_dlc = CAN_FRAME_HEADER_STRUCT.unpack_from(frame) if data_len not in can.util.CAN_FD_DLC: @@ -739,7 +740,7 @@ def __init__( self.socket = create_socket() self.channel = channel self.channel_info = f"socketcan channel '{channel}'" - self._bcm_sockets: Dict[str, socket.socket] = {} + self._bcm_sockets: dict[str, socket.socket] = {} self._is_filtered = False self._task_id = 0 self._task_id_guard = threading.Lock() @@ -819,7 +820,7 @@ def shutdown(self) -> None: def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: try: # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) @@ -992,7 +993,7 @@ def fileno(self) -> int: return self.socket.fileno() @staticmethod - def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: + def _detect_available_configs() -> list[can.typechecking.AutoDetectedConfig]: return [ {"interface": "socketcan", "channel": channel} for channel in find_available_interfaces() diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 1505c6cf8..80dcb203f 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -9,7 +9,7 @@ import struct import subprocess import sys -from typing import List, Optional, cast +from typing import Optional, cast from can import typechecking from can.interfaces.socketcan.constants import CAN_EFF_FLAG @@ -39,7 +39,7 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes return struct.pack(can_filter_fmt, *filter_data) -def find_available_interfaces() -> List[str]: +def find_available_interfaces() -> list[str]: """Returns the names of all open can/vcan interfaces The function calls the ``ip link list`` command. If the lookup fails, an error diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 7a2cc6fd0..3ecda082b 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -17,7 +17,6 @@ import urllib.parse as urlparselib import xml.etree.ElementTree as ET from collections import deque -from typing import List import can @@ -27,7 +26,7 @@ DEFAULT_SOCKETCAND_DISCOVERY_PORT = 42000 -def detect_beacon(timeout_ms: int = 3100) -> List[can.typechecking.AutoDetectedConfig]: +def detect_beacon(timeout_ms: int = 3100) -> list[can.typechecking.AutoDetectedConfig]: """ Detects socketcand servers @@ -340,7 +339,7 @@ def shutdown(self): self.__socket.close() @staticmethod - def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: + def _detect_available_configs() -> list[can.typechecking.AutoDetectedConfig]: try: return detect_beacon() except Exception as e: diff --git a/can/interfaces/systec/exceptions.py b/can/interfaces/systec/exceptions.py index dcd94bdbf..8768b412a 100644 --- a/can/interfaces/systec/exceptions.py +++ b/can/interfaces/systec/exceptions.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import Dict from can import CanError @@ -22,7 +21,7 @@ def __init__(self, result, func, arguments): @property @abstractmethod - def _error_message_mapping(self) -> Dict[ReturnCode, str]: ... + def _error_message_mapping(self) -> dict[ReturnCode, str]: ... class UcanError(UcanException): @@ -51,7 +50,7 @@ class UcanError(UcanException): } @property - def _error_message_mapping(self) -> Dict[ReturnCode, str]: + def _error_message_mapping(self) -> dict[ReturnCode, str]: return UcanError._ERROR_MESSAGES @@ -77,7 +76,7 @@ class UcanCmdError(UcanException): } @property - def _error_message_mapping(self) -> Dict[ReturnCode, str]: + def _error_message_mapping(self) -> dict[ReturnCode, str]: return UcanCmdError._ERROR_MESSAGES @@ -102,5 +101,5 @@ class UcanWarning(UcanException): } @property - def _error_message_mapping(self) -> Dict[ReturnCode, str]: + def _error_message_mapping(self) -> dict[ReturnCode, str]: return UcanWarning._ERROR_MESSAGES diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index dd114278c..31744c2b8 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -5,7 +5,7 @@ import struct import time import warnings -from typing import List, Optional, Tuple, Union +from typing import Optional, Union import can from can import BusABC, CanProtocol @@ -26,8 +26,8 @@ # see socket.getaddrinfo() -IPv4_ADDRESS_INFO = Tuple[str, int] # address, port -IPv6_ADDRESS_INFO = Tuple[str, int, int, int] # address, port, flowinfo, scope_id +IPv4_ADDRESS_INFO = tuple[str, int] # address, port +IPv6_ADDRESS_INFO = tuple[str, int, int, int] # address, port, flowinfo, scope_id IP_ADDRESS_INFO = Union[IPv4_ADDRESS_INFO, IPv6_ADDRESS_INFO] # Additional constants for the interaction with Unix kernels @@ -172,7 +172,7 @@ def shutdown(self) -> None: self._multicast.shutdown() @staticmethod - def _detect_available_configs() -> List[AutoDetectedConfig]: + def _detect_available_configs() -> list[AutoDetectedConfig]: if hasattr(socket, "CMSG_SPACE"): return [ { @@ -341,7 +341,7 @@ def send(self, data: bytes, timeout: Optional[float] = None) -> None: def recv( self, timeout: Optional[float] = None - ) -> Optional[Tuple[bytes, IP_ADDRESS_INFO, float]]: + ) -> Optional[tuple[bytes, IP_ADDRESS_INFO, float]]: """ Receive up to **max_buffer** bytes. diff --git a/can/interfaces/udp_multicast/utils.py b/can/interfaces/udp_multicast/utils.py index 35a0df185..5c9454ed4 100644 --- a/can/interfaces/udp_multicast/utils.py +++ b/can/interfaces/udp_multicast/utils.py @@ -2,7 +2,7 @@ Defines common functions. """ -from typing import Any, Dict, Optional +from typing import Any, Optional from can import CanInterfaceNotImplementedError, Message from can.typechecking import ReadableBytesLike @@ -44,7 +44,7 @@ def pack_message(message: Message) -> bytes: def unpack_message( data: ReadableBytesLike, - replace: Optional[Dict[str, Any]] = None, + replace: Optional[dict[str, Any]] = None, check: bool = False, ) -> Message: """Unpack a can.Message from a msgpack byte blob. diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index de29f03fe..9f3b7185e 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -1,7 +1,6 @@ """ """ import logging -from typing import List log = logging.getLogger("can.usb2can") @@ -43,7 +42,7 @@ def WMIDateStringToDate(dtmDate) -> str: return strDateTime -def find_serial_devices(serial_matcher: str = "") -> List[str]: +def find_serial_devices(serial_matcher: str = "") -> list[str]: """ Finds a list of USB devices where the serial number (partially) matches the given string. diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index d51d74529..986f52002 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,17 +10,13 @@ import os import time import warnings +from collections.abc import Iterator, Sequence from types import ModuleType from typing import ( Any, Callable, - Dict, - Iterator, - List, NamedTuple, Optional, - Sequence, - Tuple, Union, cast, ) @@ -204,8 +200,8 @@ def __init__( is_fd = isinstance(timing, BitTimingFd) if timing else fd self.mask = 0 - self.channel_masks: Dict[int, int] = {} - self.index_to_channel: Dict[int, int] = {} + self.channel_masks: dict[int, int] = {} + self.index_to_channel: dict[int, int] = {} self._can_protocol = CanProtocol.CAN_FD if is_fd else CanProtocol.CAN_20 self._listen_only = listen_only @@ -383,7 +379,7 @@ def _find_global_channel_idx( channel: int, serial: Optional[int], app_name: Optional[str], - channel_configs: List["VectorChannelConfig"], + channel_configs: list["VectorChannelConfig"], ) -> int: if serial is not None: serial_found = False @@ -439,7 +435,7 @@ def _has_init_access(self, channel: int) -> bool: return bool(self.permission_mask & self.channel_masks[channel]) def _read_bus_params( - self, channel_index: int, vcc_list: List["VectorChannelConfig"] + self, channel_index: int, vcc_list: list["VectorChannelConfig"] ) -> "VectorBusParams": for vcc in vcc_list: if vcc.channel_index == channel_index: @@ -712,7 +708,7 @@ def _apply_filters(self, filters: Optional[CanFilters]) -> None: def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: end_time = time.time() + timeout if timeout is not None else None while True: @@ -986,7 +982,7 @@ def reset(self) -> None: ) @staticmethod - def _detect_available_configs() -> List[AutoDetectedConfig]: + def _detect_available_configs() -> list[AutoDetectedConfig]: configs = [] channel_configs = get_channel_configs() LOG.info("Found %d channels", len(channel_configs)) @@ -1037,7 +1033,7 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: @staticmethod def get_application_config( app_name: str, app_channel: int - ) -> Tuple[Union[int, xldefine.XL_HardwareType], int, int]: + ) -> tuple[Union[int, xldefine.XL_HardwareType], int, int]: """Retrieve information for an application in Vector Hardware Configuration. :param app_name: @@ -1243,14 +1239,14 @@ def _read_bus_params_from_c_struct( ) -def get_channel_configs() -> List[VectorChannelConfig]: +def get_channel_configs() -> list[VectorChannelConfig]: """Read channel properties from Vector XL API.""" try: driver_config = _get_xl_driver_config() except VectorError: return [] - channel_list: List[VectorChannelConfig] = [] + channel_list: list[VectorChannelConfig] = [] for i in range(driver_config.channelCount): xlcc: xlclass.XLchannelConfig = driver_config.channel[i] vcc = VectorChannelConfig( diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 62ad0cfe3..aa858913e 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -12,7 +12,7 @@ from copy import deepcopy from random import randint from threading import RLock -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, Optional from can import CanOperationError from can.bus import BusABC, CanProtocol @@ -25,7 +25,7 @@ # Channels are lists of queues, one for each connection if TYPE_CHECKING: # https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime - channels: Dict[Optional[Any], List[queue.Queue[Message]]] = {} + channels: dict[Optional[Any], list[queue.Queue[Message]]] = {} else: channels = {} channels_lock = RLock() @@ -125,7 +125,7 @@ def _check_if_open(self) -> None: def _recv_internal( self, timeout: Optional[float] - ) -> Tuple[Optional[Message], bool]: + ) -> tuple[Optional[Message], bool]: self._check_if_open() try: msg = self.queue.get(block=True, timeout=timeout) @@ -168,7 +168,7 @@ def shutdown(self) -> None: del channels[self.channel_id] @staticmethod - def _detect_available_configs() -> List[AutoDetectedConfig]: + def _detect_available_configs() -> list[AutoDetectedConfig]: """ Returns all currently used channels as well as one other currently unused channel. diff --git a/can/io/asc.py b/can/io/asc.py index deb7d429e..0bea823fd 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -8,8 +8,9 @@ import logging import re +from collections.abc import Generator from datetime import datetime -from typing import Any, Dict, Final, Generator, Optional, TextIO, Union +from typing import Any, Final, Optional, TextIO, Union from ..message import Message from ..typechecking import StringPathLike @@ -153,7 +154,7 @@ def _datetime_to_timestamp(datetime_string: str) -> float: raise ValueError(f"Incompatible datetime string {datetime_string}") - def _extract_can_id(self, str_can_id: str, msg_kwargs: Dict[str, Any]) -> None: + def _extract_can_id(self, str_can_id: str, msg_kwargs: dict[str, Any]) -> None: if str_can_id[-1:].lower() == "x": msg_kwargs["is_extended_id"] = True can_id = int(str_can_id[0:-1], self._converted_base) @@ -169,7 +170,7 @@ def _check_base(base: str) -> int: return BASE_DEC if base == "dec" else BASE_HEX def _process_data_string( - self, data_str: str, data_length: int, msg_kwargs: Dict[str, Any] + self, data_str: str, data_length: int, msg_kwargs: dict[str, Any] ) -> None: frame = bytearray() data = data_str.split() @@ -178,7 +179,7 @@ def _process_data_string( msg_kwargs["data"] = frame def _process_classic_can_frame( - self, line: str, msg_kwargs: Dict[str, Any] + self, line: str, msg_kwargs: dict[str, Any] ) -> Message: # CAN error frame if line.strip()[0:10].lower() == "errorframe": @@ -213,7 +214,7 @@ def _process_classic_can_frame( return Message(**msg_kwargs) - def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Message: + def _process_fd_can_frame(self, line: str, msg_kwargs: dict[str, Any]) -> Message: channel, direction, rest_of_message = line.split(None, 2) # See ASCWriter msg_kwargs["channel"] = int(channel) - 1 @@ -285,7 +286,7 @@ def __iter__(self) -> Generator[Message, None, None]: # J1939 message or some other unsupported event continue - msg_kwargs: Dict[str, Union[float, bool, int]] = {} + msg_kwargs: dict[str, Union[float, bool, int]] = {} try: _timestamp, channel, rest_of_message = line.split(None, 2) timestamp = float(_timestamp) + self.start_time diff --git a/can/io/blf.py b/can/io/blf.py index e64a2247d..6a1231fcc 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -17,15 +17,16 @@ import struct import time import zlib +from collections.abc import Generator from decimal import Decimal -from typing import Any, BinaryIO, Generator, List, Optional, Tuple, Union, cast +from typing import Any, BinaryIO, Optional, Union, cast from ..message import Message from ..typechecking import StringPathLike from ..util import channel2int, dlc2len, len2dlc from .generic import BinaryIOMessageReader, FileIOMessageWriter -TSystemTime = Tuple[int, int, int, int, int, int, int, int] +TSystemTime = tuple[int, int, int, int, int, int, int, int] class BLFParseError(Exception): @@ -422,7 +423,7 @@ def __init__( assert self.file is not None self.channel = channel self.compression_level = compression_level - self._buffer: List[bytes] = [] + self._buffer: list[bytes] = [] self._buffer_size = 0 # If max container size is located in kwargs, then update the instance if kwargs.get("max_container_size", False): diff --git a/can/io/canutils.py b/can/io/canutils.py index cc978d4f2..e83c21926 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -5,7 +5,8 @@ """ import logging -from typing import Any, Generator, TextIO, Union +from collections.abc import Generator +from typing import Any, TextIO, Union from can.message import Message diff --git a/can/io/csv.py b/can/io/csv.py index 2abaeb70e..dcc7996f7 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -10,7 +10,8 @@ """ from base64 import b64decode, b64encode -from typing import Any, Generator, TextIO, Union +from collections.abc import Generator +from typing import Any, TextIO, Union from can.message import Message diff --git a/can/io/generic.py b/can/io/generic.py index 93840f807..82523c3cd 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -3,16 +3,15 @@ import gzip import locale from abc import ABCMeta +from collections.abc import Iterable +from contextlib import AbstractContextManager from types import TracebackType from typing import ( Any, BinaryIO, - ContextManager, - Iterable, Literal, Optional, TextIO, - Type, Union, cast, ) @@ -24,7 +23,7 @@ from ..message import Message -class BaseIOHandler(ContextManager, metaclass=ABCMeta): +class BaseIOHandler(AbstractContextManager): """A generic file handler that can be used for reading and writing. Can be used as a context manager. @@ -60,10 +59,7 @@ def __init__( # pylint: disable=consider-using-with # file is some path-like object self.file = cast( - "typechecking.FileLike", - open( - cast("typechecking.StringPathLike", file), mode, encoding=encoding - ), + "typechecking.FileLike", open(file, mode, encoding=encoding) ) # for multiple inheritance @@ -74,7 +70,7 @@ def __enter__(self) -> Self: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: diff --git a/can/io/logger.py b/can/io/logger.py index 359aae4ac..f9f029759 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -12,13 +12,9 @@ Any, Callable, ClassVar, - Dict, Final, Literal, Optional, - Set, - Tuple, - Type, cast, ) @@ -43,7 +39,7 @@ #: A map of file suffixes to their corresponding #: :class:`can.io.generic.MessageWriter` class -MESSAGE_WRITERS: Final[Dict[str, Type[MessageWriter]]] = { +MESSAGE_WRITERS: Final[dict[str, type[MessageWriter]]] = { ".asc": ASCWriter, ".blf": BLFWriter, ".csv": CSVWriter, @@ -66,7 +62,7 @@ def _update_writer_plugins() -> None: MESSAGE_WRITERS[entry_point.key] = writer_class -def _get_logger_for_suffix(suffix: str) -> Type[MessageWriter]: +def _get_logger_for_suffix(suffix: str) -> type[MessageWriter]: try: return MESSAGE_WRITERS[suffix] except KeyError: @@ -77,7 +73,7 @@ def _get_logger_for_suffix(suffix: str) -> Type[MessageWriter]: def _compress( filename: StringPathLike, **kwargs: Any -) -> Tuple[Type[MessageWriter], FileLike]: +) -> tuple[type[MessageWriter], FileLike]: """ Return the suffix and io object of the decompressed file. File will automatically recompress upon close. @@ -171,7 +167,7 @@ class BaseRotatingLogger(MessageWriter, ABC): Subclasses must set the `_writer` attribute upon initialization. """ - _supported_formats: ClassVar[Set[str]] = set() + _supported_formats: ClassVar[set[str]] = set() #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename` #: method delegates to this callable. The parameters passed to the callable are @@ -290,7 +286,7 @@ def __enter__(self) -> Self: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: @@ -347,7 +343,7 @@ class SizedRotatingLogger(BaseRotatingLogger): :meth:`~can.Listener.stop` is called. """ - _supported_formats: ClassVar[Set[str]] = {".asc", ".blf", ".csv", ".log", ".txt"} + _supported_formats: ClassVar[set[str]] = {".asc", ".blf", ".csv", ".log", ".txt"} def __init__( self, diff --git a/can/io/mf4.py b/can/io/mf4.py index 9efa1d83d..557d882e1 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -8,11 +8,12 @@ import abc import heapq import logging +from collections.abc import Generator, Iterator from datetime import datetime from hashlib import md5 from io import BufferedIOBase, BytesIO from pathlib import Path -from typing import Any, BinaryIO, Dict, Generator, Iterator, List, Optional, Union, cast +from typing import Any, BinaryIO, Optional, Union, cast from ..message import Message from ..typechecking import StringPathLike @@ -339,7 +340,7 @@ def __iter__(self) -> Generator[Message, None, None]: for i in range(len(data)): data_length = int(data["CAN_DataFrame.DataLength"][i]) - kv: Dict[str, Any] = { + kv: dict[str, Any] = { "timestamp": float(data.timestamps[i]) + self._start_timestamp, "arbitration_id": int(data["CAN_DataFrame.ID"][i]) & 0x1FFFFFFF, "data": data["CAN_DataFrame.DataBytes"][i][ @@ -377,7 +378,7 @@ def __iter__(self) -> Generator[Message, None, None]: names = data.samples[0].dtype.names for i in range(len(data)): - kv: Dict[str, Any] = { + kv: dict[str, Any] = { "timestamp": float(data.timestamps[i]) + self._start_timestamp, "is_error_frame": True, } @@ -428,7 +429,7 @@ def __iter__(self) -> Generator[Message, None, None]: names = data.samples[0].dtype.names for i in range(len(data)): - kv: Dict[str, Any] = { + kv: dict[str, Any] = { "timestamp": float(data.timestamps[i]) + self._start_timestamp, "arbitration_id": int(data["CAN_RemoteFrame.ID"][i]) & 0x1FFFFFFF, @@ -474,7 +475,7 @@ def __init__( def __iter__(self) -> Iterator[Message]: # To handle messages split over multiple channel groups, create a single iterator per # channel group and merge these iterators into a single iterator using heapq. - iterators: List[FrameIterator] = [] + iterators: list[FrameIterator] = [] for group_index, group in enumerate(self._mdf.groups): channel_group: ChannelGroup = group.channel_group diff --git a/can/io/player.py b/can/io/player.py index 214112164..2451eab41 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -7,14 +7,10 @@ import gzip import pathlib import time +from collections.abc import Generator, Iterable from typing import ( Any, - Dict, Final, - Generator, - Iterable, - Tuple, - Type, Union, ) @@ -32,7 +28,7 @@ #: A map of file suffixes to their corresponding #: :class:`can.io.generic.MessageReader` class -MESSAGE_READERS: Final[Dict[str, Type[MessageReader]]] = { +MESSAGE_READERS: Final[dict[str, type[MessageReader]]] = { ".asc": ASCReader, ".blf": BLFReader, ".csv": CSVReader, @@ -54,7 +50,7 @@ def _update_reader_plugins() -> None: MESSAGE_READERS[entry_point.key] = reader_class -def _get_logger_for_suffix(suffix: str) -> Type[MessageReader]: +def _get_logger_for_suffix(suffix: str) -> type[MessageReader]: """Find MessageReader class for given suffix.""" try: return MESSAGE_READERS[suffix] @@ -64,7 +60,7 @@ def _get_logger_for_suffix(suffix: str) -> Type[MessageReader]: def _decompress( filename: StringPathLike, -) -> Tuple[Type[MessageReader], Union[str, FileLike]]: +) -> tuple[type[MessageReader], Union[str, FileLike]]: """ Return the suffix and io object of the decompressed file. """ diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 43fd761e9..686e2d038 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -8,7 +8,8 @@ import sqlite3 import threading import time -from typing import Any, Generator +from collections.abc import Generator +from typing import Any from can.listener import BufferedReader from can.message import Message diff --git a/can/io/trc.py b/can/io/trc.py index 2dbe3763c..a07a53a4d 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -9,9 +9,10 @@ import logging import os +from collections.abc import Generator from datetime import datetime, timedelta, timezone from enum import Enum -from typing import Any, Callable, Dict, Generator, Optional, TextIO, Tuple, Union +from typing import Any, Callable, Optional, TextIO, Union from ..message import Message from ..typechecking import StringPathLike @@ -56,13 +57,13 @@ def __init__( super().__init__(file, mode="r") self.file_version = TRCFileVersion.UNKNOWN self._start_time: float = 0 - self.columns: Dict[str, int] = {} + self.columns: dict[str, int] = {} self._num_columns = -1 if not self.file: raise ValueError("The given file cannot be None") - self._parse_cols: Callable[[Tuple[str, ...]], Optional[Message]] = ( + self._parse_cols: Callable[[tuple[str, ...]], Optional[Message]] = ( lambda x: None ) @@ -140,7 +141,7 @@ def _extract_header(self): return line - def _parse_msg_v1_0(self, cols: Tuple[str, ...]) -> Optional[Message]: + def _parse_msg_v1_0(self, cols: tuple[str, ...]) -> Optional[Message]: arbit_id = cols[2] if arbit_id == "FFFFFFFF": logger.info("TRCReader: Dropping bus info line") @@ -155,7 +156,7 @@ def _parse_msg_v1_0(self, cols: Tuple[str, ...]) -> Optional[Message]: msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) return msg - def _parse_msg_v1_1(self, cols: Tuple[str, ...]) -> Optional[Message]: + def _parse_msg_v1_1(self, cols: tuple[str, ...]) -> Optional[Message]: arbit_id = cols[3] msg = Message() @@ -168,7 +169,7 @@ def _parse_msg_v1_1(self, cols: Tuple[str, ...]) -> Optional[Message]: msg.is_rx = cols[2] == "Rx" return msg - def _parse_msg_v1_3(self, cols: Tuple[str, ...]) -> Optional[Message]: + def _parse_msg_v1_3(self, cols: tuple[str, ...]) -> Optional[Message]: arbit_id = cols[4] msg = Message() @@ -181,7 +182,7 @@ def _parse_msg_v1_3(self, cols: Tuple[str, ...]) -> Optional[Message]: msg.is_rx = cols[3] == "Rx" return msg - def _parse_msg_v2_x(self, cols: Tuple[str, ...]) -> Optional[Message]: + def _parse_msg_v2_x(self, cols: tuple[str, ...]) -> Optional[Message]: type_ = cols[self.columns["T"]] bus = self.columns.get("B", None) @@ -208,7 +209,7 @@ def _parse_msg_v2_x(self, cols: Tuple[str, ...]) -> Optional[Message]: return msg - def _parse_cols_v1_1(self, cols: Tuple[str, ...]) -> Optional[Message]: + def _parse_cols_v1_1(self, cols: tuple[str, ...]) -> Optional[Message]: dtype = cols[2] if dtype in ("Tx", "Rx"): return self._parse_msg_v1_1(cols) @@ -216,7 +217,7 @@ def _parse_cols_v1_1(self, cols: Tuple[str, ...]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_v1_3(self, cols: Tuple[str, ...]) -> Optional[Message]: + def _parse_cols_v1_3(self, cols: tuple[str, ...]) -> Optional[Message]: dtype = cols[3] if dtype in ("Tx", "Rx"): return self._parse_msg_v1_3(cols) @@ -224,7 +225,7 @@ def _parse_cols_v1_3(self, cols: Tuple[str, ...]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_v2_x(self, cols: Tuple[str, ...]) -> Optional[Message]: + def _parse_cols_v2_x(self, cols: tuple[str, ...]) -> Optional[Message]: dtype = cols[self.columns["T"]] if dtype in {"DT", "FD", "FB", "FE", "BI"}: return self._parse_msg_v2_x(cols) diff --git a/can/listener.py b/can/listener.py index 1f19c3acd..1e95fa990 100644 --- a/can/listener.py +++ b/can/listener.py @@ -6,8 +6,9 @@ import sys import warnings from abc import ABCMeta, abstractmethod +from collections.abc import AsyncIterator from queue import Empty, SimpleQueue -from typing import Any, AsyncIterator, Optional +from typing import Any, Optional from can.bus import BusABC from can.message import Message diff --git a/can/logger.py b/can/logger.py index 4167558d8..9c1134257 100644 --- a/can/logger.py +++ b/can/logger.py @@ -2,15 +2,12 @@ import errno import re import sys +from collections.abc import Sequence from datetime import datetime from typing import ( TYPE_CHECKING, Any, - Dict, - List, Optional, - Sequence, - Tuple, Union, ) @@ -111,7 +108,7 @@ def _create_bus(parsed_args: argparse.Namespace, **kwargs: Any) -> can.BusABC: logging_level_names = ["critical", "error", "warning", "info", "debug", "subdebug"] can.set_logging_level(logging_level_names[min(5, parsed_args.verbosity)]) - config: Dict[str, Any] = {"single_handle": True, **kwargs} + config: dict[str, Any] = {"single_handle": True, **kwargs} if parsed_args.interface: config["interface"] = parsed_args.interface if parsed_args.bitrate: @@ -140,7 +137,7 @@ def __call__( raise argparse.ArgumentError(None, "Invalid filter argument") print(f"Adding filter(s): {values}") - can_filters: List[CanFilter] = [] + can_filters: list[CanFilter] = [] for filt in values: if ":" in filt: @@ -169,7 +166,7 @@ def __call__( if not isinstance(values, list): raise argparse.ArgumentError(None, "Invalid --timing argument") - timing_dict: Dict[str, int] = {} + timing_dict: dict[str, int] = {} for arg in values: try: key, value_string = arg.split("=") @@ -193,19 +190,19 @@ def _parse_additional_config(unknown_args: Sequence[str]) -> TAdditionalCliArgs: if not re.match(r"^--[a-zA-Z][a-zA-Z0-9\-]*=\S*?$", arg): raise ValueError(f"Parsing argument {arg} failed") - def _split_arg(_arg: str) -> Tuple[str, str]: + def _split_arg(_arg: str) -> tuple[str, str]: left, right = _arg.split("=", 1) return left.lstrip("-").replace("-", "_"), right - args: Dict[str, Union[str, int, float, bool]] = {} + args: dict[str, Union[str, int, float, bool]] = {} for key, string_val in map(_split_arg, unknown_args): args[key] = cast_from_string(string_val) return args def _parse_logger_args( - args: List[str], -) -> Tuple[argparse.Namespace, TAdditionalCliArgs]: + args: list[str], +) -> tuple[argparse.Namespace, TAdditionalCliArgs]: """Parse command line arguments for logger script.""" parser = argparse.ArgumentParser( diff --git a/can/notifier.py b/can/notifier.py index 088f0802e..237c874da 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -7,7 +7,8 @@ import logging import threading import time -from typing import Any, Awaitable, Callable, Iterable, List, Optional, Union +from collections.abc import Awaitable, Iterable +from typing import Any, Callable, Optional, Union from can.bus import BusABC from can.listener import Listener @@ -21,7 +22,7 @@ class Notifier: def __init__( self, - bus: Union[BusABC, List[BusABC]], + bus: Union[BusABC, list[BusABC]], listeners: Iterable[MessageRecipient], timeout: float = 1.0, loop: Optional[asyncio.AbstractEventLoop] = None, @@ -43,7 +44,7 @@ def __init__( :param timeout: An optional maximum number of seconds to wait for any :class:`~can.Message`. :param loop: An :mod:`asyncio` event loop to schedule the ``listeners`` in. """ - self.listeners: List[MessageRecipient] = list(listeners) + self.listeners: list[MessageRecipient] = list(listeners) self.bus = bus self.timeout = timeout self._loop = loop @@ -54,7 +55,7 @@ def __init__( self._running = True self._lock = threading.Lock() - self._readers: List[Union[int, threading.Thread]] = [] + self._readers: list[Union[int, threading.Thread]] = [] buses = self.bus if isinstance(self.bus, list) else [self.bus] for each_bus in buses: self.add_bus(each_bus) diff --git a/can/player.py b/can/player.py index 9deb0c51f..38b76a331 100644 --- a/can/player.py +++ b/can/player.py @@ -16,7 +16,7 @@ from .logger import _create_base_argument_parser, _create_bus, _parse_additional_config if TYPE_CHECKING: - from typing import Iterable + from collections.abc import Iterable from can import Message diff --git a/can/typechecking.py b/can/typechecking.py index d993d6bd3..36343ddaa 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -50,13 +50,13 @@ class CanFilterExtended(TypedDict): StringPathLike = typing.Union[str, "os.PathLike[str]"] AcceptedIOType = typing.Union[FileLike, StringPathLike] -BusConfig = typing.NewType("BusConfig", typing.Dict[str, typing.Any]) +BusConfig = typing.NewType("BusConfig", dict[str, typing.Any]) # Used by CLI scripts -TAdditionalCliArgs: TypeAlias = typing.Dict[str, typing.Union[str, int, float, bool]] -TDataStructs: TypeAlias = typing.Dict[ - typing.Union[int, typing.Tuple[int, ...]], - typing.Union[struct.Struct, typing.Tuple, None], +TAdditionalCliArgs: TypeAlias = dict[str, typing.Union[str, int, float, bool]] +TDataStructs: TypeAlias = dict[ + typing.Union[int, tuple[int, ...]], + typing.Union[struct.Struct, tuple, None], ] diff --git a/can/util.py b/can/util.py index bb835b846..2f32dda8e 100644 --- a/can/util.py +++ b/can/util.py @@ -12,15 +12,13 @@ import platform import re import warnings +from collections.abc import Iterable from configparser import ConfigParser from time import get_clock_info, perf_counter, time from typing import ( Any, Callable, - Dict, - Iterable, Optional, - Tuple, TypeVar, Union, cast, @@ -53,7 +51,7 @@ def load_file_config( path: Optional[typechecking.AcceptedIOType] = None, section: str = "default" -) -> Dict[str, str]: +) -> dict[str, str]: """ Loads configuration from file with following content:: @@ -77,7 +75,7 @@ def load_file_config( else: config.read(path) - _config: Dict[str, str] = {} + _config: dict[str, str] = {} if config.has_section(section): _config.update(config.items(section)) @@ -85,7 +83,7 @@ def load_file_config( return _config -def load_environment_config(context: Optional[str] = None) -> Dict[str, str]: +def load_environment_config(context: Optional[str] = None) -> dict[str, str]: """ Loads config dict from environmental variables (if set): @@ -111,7 +109,7 @@ def load_environment_config(context: Optional[str] = None) -> Dict[str, str]: context_suffix = f"_{context}" if context else "" can_config_key = f"CAN_CONFIG{context_suffix}" - config: Dict[str, str] = json.loads(os.environ.get(can_config_key, "{}")) + config: dict[str, str] = json.loads(os.environ.get(can_config_key, "{}")) for key, val in mapper.items(): config_option = os.environ.get(val + context_suffix, None) @@ -123,7 +121,7 @@ def load_environment_config(context: Optional[str] = None) -> Dict[str, str]: def load_config( path: Optional[typechecking.AcceptedIOType] = None, - config: Optional[Dict[str, Any]] = None, + config: Optional[dict[str, Any]] = None, context: Optional[str] = None, ) -> typechecking.BusConfig: """ @@ -178,7 +176,7 @@ def load_config( # Use the given dict for default values config_sources = cast( - "Iterable[Union[Dict[str, Any], Callable[[Any], Dict[str, Any]]]]", + "Iterable[Union[dict[str, Any], Callable[[Any], dict[str, Any]]]]", [ given_config, can.rc, @@ -212,7 +210,7 @@ def load_config( return bus_config -def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: +def _create_bus_config(config: dict[str, Any]) -> typechecking.BusConfig: """Validates some config values, performs compatibility mappings and creates specific structures (e.g. for bit timings). @@ -254,7 +252,7 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: return cast("typechecking.BusConfig", config) -def _dict2timing(data: Dict[str, Any]) -> Union[BitTiming, BitTimingFd, None]: +def _dict2timing(data: dict[str, Any]) -> Union[BitTiming, BitTimingFd, None]: """Try to instantiate a :class:`~can.BitTiming` or :class:`~can.BitTimingFd` from a dictionary. Return `None` if not possible.""" @@ -396,8 +394,8 @@ def _rename_kwargs( func_name: str, start: str, end: Optional[str], - kwargs: Dict[str, Any], - aliases: Dict[str, Optional[str]], + kwargs: dict[str, Any], + aliases: dict[str, Optional[str]], ) -> None: """Helper function for `deprecated_args_alias`""" for alias, new in aliases.items(): @@ -468,7 +466,7 @@ def check_or_adjust_timing_clock(timing: T2, valid_clocks: Iterable[int]) -> T2: ) from None -def time_perfcounter_correlation() -> Tuple[float, float]: +def time_perfcounter_correlation() -> tuple[float, float]: """Get the `perf_counter` value nearest to when time.time() is updated Computed if the default timer used by `time.time` on this platform has a resolution diff --git a/can/viewer.py b/can/viewer.py index 57b3d6f7d..3eed727ab 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -27,7 +27,6 @@ import struct import sys import time -from typing import Dict, List, Tuple from can import __version__ from can.logger import ( @@ -161,7 +160,7 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod - def unpack_data(cmd: int, cmd_to_struct: Dict, data: bytes) -> List[float]: + def unpack_data(cmd: int, cmd_to_struct: dict, data: bytes) -> list[float]: if not cmd_to_struct or not data: # These messages do not contain a data package return [] @@ -390,8 +389,8 @@ def _fill_text(self, text, width, indent): def _parse_viewer_args( - args: List[str], -) -> Tuple[argparse.Namespace, TDataStructs, TAdditionalCliArgs]: + args: list[str], +) -> tuple[argparse.Namespace, TDataStructs, TAdditionalCliArgs]: # Parse command line arguments parser = argparse.ArgumentParser( "python -m can.viewer", @@ -524,7 +523,7 @@ def _parse_viewer_args( key, fmt = int(tmp[0], base=16), tmp[1] # The scaling - scaling: List[float] = [] + scaling: list[float] = [] for t in tmp[2:]: # First try to convert to int, if that fails, then convert to a float try: diff --git a/pyproject.toml b/pyproject.toml index c65275559..2e8be3e2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "typing_extensions>=3.10.0.0", "msgpack~=1.1.0", ] -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "LGPL v3" } classifiers = [ "Development Status :: 5 - Production/Stable", @@ -29,7 +29,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -61,9 +60,9 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==3.2.*", - "ruff==0.10.0", + "ruff==0.11.12", "black==25.1.*", - "mypy==1.15.*", + "mypy==1.16.*", ] pywin32 = ["pywin32>=305; platform_system == 'Windows' and platform_python_implementation == 'CPython'"] seeedstudio = ["pyserial>=3.0"] diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py index 3adb1e7c6..fe87f33b0 100644 --- a/test/contextmanager_test.py +++ b/test/contextmanager_test.py @@ -17,9 +17,10 @@ def setUp(self): ) def test_open_buses(self): - with can.Bus(interface="virtual") as bus_send, can.Bus( - interface="virtual" - ) as bus_recv: + with ( + can.Bus(interface="virtual") as bus_send, + can.Bus(interface="virtual") as bus_recv, + ): bus_send.send(self.msg_send) msg_recv = bus_recv.recv() @@ -27,9 +28,10 @@ def test_open_buses(self): self.assertTrue(msg_recv) def test_use_closed_bus(self): - with can.Bus(interface="virtual") as bus_send, can.Bus( - interface="virtual" - ) as bus_recv: + with ( + can.Bus(interface="virtual") as bus_send, + can.Bus(interface="virtual") as bus_recv, + ): bus_send.send(self.msg_send) # Receiving a frame after bus has been closed should raise a CanException From 6058ab9dfe8b3ba5d23cbff670c0fe767147390a Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 30 May 2025 15:14:24 +0200 Subject: [PATCH 1185/1235] Use dependency groups for docs/lint/test (#1945) * use dependency groups for docs/lint/test --------- Co-authored-by: zariiii9003 --- .github/workflows/ci.yml | 7 ++++-- .readthedocs.yml | 8 ++++-- can/interfaces/udp_multicast/bus.py | 4 +-- can/interfaces/udp_multicast/utils.py | 22 +++++++++++++---- can/listener.py | 2 +- doc/development.rst | 2 +- doc/doc-requirements.txt | 5 ---- doc/interfaces/udp_multicast.rst | 9 +++++++ pyproject.toml | 35 +++++++++++++++++++++------ test/back2back_test.py | 12 ++++++--- test/listener_test.py | 7 +++++- tox.ini | 27 +++++++++------------ 12 files changed, 94 insertions(+), 46 deletions(-) delete mode 100644 doc/doc-requirements.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab14b6b6a..f04654f0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[lint] + pip install --group lint -e . - name: mypy 3.9 run: | mypy --python-version 3.9 . @@ -92,6 +92,9 @@ jobs: - name: mypy 3.12 run: | mypy --python-version 3.12 . + - name: mypy 3.13 + run: | + mypy --python-version 3.13 . - name: ruff run: | ruff check can @@ -115,7 +118,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[lint] + pip install --group lint - name: Code Format Check with Black run: | black --check --verbose . diff --git a/.readthedocs.yml b/.readthedocs.yml index dad8c28db..6fe4009e4 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -10,6 +10,9 @@ build: os: ubuntu-22.04 tools: python: "3.12" + jobs: + post_install: + - pip install --group docs # Build documentation in the docs/ directory with Sphinx sphinx: @@ -23,10 +26,11 @@ formats: # Optionally declare the Python requirements required to build your docs python: install: - - requirements: doc/doc-requirements.txt - method: pip path: . extra_requirements: - canalystii - - gs_usb + - gs-usb - mf4 + - remote + - serial diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 31744c2b8..ec94e22b5 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -11,7 +11,7 @@ from can import BusABC, CanProtocol from can.typechecking import AutoDetectedConfig -from .utils import check_msgpack_installed, pack_message, unpack_message +from .utils import is_msgpack_installed, pack_message, unpack_message ioctl_supported = True @@ -104,7 +104,7 @@ def __init__( fd: bool = True, **kwargs, ) -> None: - check_msgpack_installed() + is_msgpack_installed() if receive_own_messages: raise can.CanInterfaceNotImplementedError( diff --git a/can/interfaces/udp_multicast/utils.py b/can/interfaces/udp_multicast/utils.py index 5c9454ed4..c6b2630a5 100644 --- a/can/interfaces/udp_multicast/utils.py +++ b/can/interfaces/udp_multicast/utils.py @@ -13,10 +13,22 @@ msgpack = None -def check_msgpack_installed() -> None: - """Raises a :class:`can.CanInterfaceNotImplementedError` if `msgpack` is not installed.""" +def is_msgpack_installed(raise_exception: bool = True) -> bool: + """Check whether the ``msgpack`` module is installed. + + :param raise_exception: + If True, raise a :class:`can.CanInterfaceNotImplementedError` when ``msgpack`` is not installed. + If False, return False instead. + :return: + True if ``msgpack`` is installed, False otherwise. + :raises can.CanInterfaceNotImplementedError: + If ``msgpack`` is not installed and ``raise_exception`` is True. + """ if msgpack is None: - raise CanInterfaceNotImplementedError("msgpack not installed") + if raise_exception: + raise CanInterfaceNotImplementedError("msgpack not installed") + return False + return True def pack_message(message: Message) -> bytes: @@ -25,7 +37,7 @@ def pack_message(message: Message) -> bytes: :param message: the message to be packed """ - check_msgpack_installed() + is_msgpack_installed() as_dict = { "timestamp": message.timestamp, "arbitration_id": message.arbitration_id, @@ -58,7 +70,7 @@ def unpack_message( :raise ValueError: if `check` is true and the message metadata is invalid in some way :raise Exception: if there was another problem while unpacking """ - check_msgpack_installed() + is_msgpack_installed() as_dict = msgpack.unpackb(data, raw=False) if replace is not None: as_dict.update(replace) diff --git a/can/listener.py b/can/listener.py index 1e95fa990..b450cf36d 100644 --- a/can/listener.py +++ b/can/listener.py @@ -136,6 +136,7 @@ class AsyncBufferedReader( """ def __init__(self, **kwargs: Any) -> None: + self._is_stopped: bool = False self.buffer: asyncio.Queue[Message] if "loop" in kwargs: @@ -150,7 +151,6 @@ def __init__(self, **kwargs: Any) -> None: return self.buffer = asyncio.Queue() - self._is_stopped: bool = False def on_message_received(self, msg: Message) -> None: """Append a message to the buffer. diff --git a/doc/development.rst b/doc/development.rst index 31a7ae077..074c1318d 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -52,7 +52,7 @@ The documentation can be built with:: The linters can be run with:: - pip install -e .[lint] + pip install --group lint -e . black --check can mypy can ruff check can diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt deleted file mode 100644 index 9a01cf589..000000000 --- a/doc/doc-requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -sphinx>=5.2.3 -sphinxcontrib-programoutput -sphinx-inline-tabs -sphinx-copybutton -furo diff --git a/doc/interfaces/udp_multicast.rst b/doc/interfaces/udp_multicast.rst index e41925cb3..b2a049d83 100644 --- a/doc/interfaces/udp_multicast.rst +++ b/doc/interfaces/udp_multicast.rst @@ -22,6 +22,15 @@ sufficiently reliable for this interface to function properly. Please refer to the `Bus class documentation`_ below for configuration options and useful resources for specifying multicast IP addresses. +Installation +------------------- + +The Multicast IP Interface depends on the **msgpack** python library, +which is automatically installed with the `multicast` extra keyword:: + + $ pip install python-can[multicast] + + Supported Platforms ------------------- diff --git a/pyproject.toml b/pyproject.toml index 2e8be3e2a..37abec266 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ dependencies = [ "wrapt~=1.10", "packaging >= 23.1", "typing_extensions>=3.10.0.0", - "msgpack~=1.1.0", ] requires-python = ">=3.9" license = { text = "LGPL v3" } @@ -58,12 +57,6 @@ repository = "https://github.com/hardbyte/python-can" changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] -lint = [ - "pylint==3.2.*", - "ruff==0.11.12", - "black==25.1.*", - "mypy==1.16.*", -] pywin32 = ["pywin32>=305; platform_system == 'Windows' and platform_python_implementation == 'CPython'"] seeedstudio = ["pyserial>=3.0"] serial = ["pyserial~=3.0"] @@ -71,7 +64,7 @@ neovi = ["filelock", "python-ics>=2.12"] canalystii = ["canalystii>=0.1.0"] cantact = ["cantact>=0.0.7"] cvector = ["python-can-cvector"] -gs_usb = ["gs_usb>=0.2.1"] +gs-usb = ["gs-usb>=0.2.1"] nixnet = ["nixnet>=0.3.2"] pcan = ["uptime~=3.0.1"] remote = ["python-can-remote"] @@ -82,6 +75,32 @@ viewer = [ "windows-curses; platform_system == 'Windows' and platform_python_implementation=='CPython'" ] mf4 = ["asammdf>=6.0.0"] +multicast = ["msgpack~=1.1.0"] + +[dependency-groups] +docs = [ + "sphinx>=5.2.3", + "sphinxcontrib-programoutput", + "sphinx-inline-tabs", + "sphinx-copybutton", + "furo", +] +lint = [ + "pylint==3.2.*", + "ruff==0.11.12", + "black==25.1.*", + "mypy==1.16.*", +] +test = [ + "pytest==8.3.*", + "pytest-timeout==2.1.*", + "coveralls==3.3.1", + "pytest-cov==4.0.0", + "coverage==6.5.0", + "hypothesis~=6.35.0", + "pyserial~=3.5", + "parameterized~=0.8", +] [tool.setuptools.dynamic] readme = { file = "README.rst" } diff --git a/test/back2back_test.py b/test/back2back_test.py index fc630fb65..a46597ef4 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -14,14 +14,12 @@ import can from can import CanInterfaceNotImplementedError from can.interfaces.udp_multicast import UdpMulticastBus +from can.interfaces.udp_multicast.utils import is_msgpack_installed from .config import ( IS_CI, IS_OSX, IS_PYPY, - IS_TRAVIS, - IS_UNIX, - IS_WINDOWS, TEST_CAN_FD, TEST_INTERFACE_SOCKETCAN, ) @@ -307,6 +305,10 @@ class BasicTestSocketCan(Back2BackTestCase): IS_CI and IS_OSX, "not supported for macOS CI", ) +@unittest.skipUnless( + is_msgpack_installed(raise_exception=False), + "msgpack not installed", +) class BasicTestUdpMulticastBusIPv4(Back2BackTestCase): INTERFACE_1 = "udp_multicast" CHANNEL_1 = UdpMulticastBus.DEFAULT_GROUP_IPv4 @@ -324,6 +326,10 @@ def test_unique_message_instances(self): IS_CI and IS_OSX, "not supported for macOS CI", ) +@unittest.skipUnless( + is_msgpack_installed(raise_exception=False), + "msgpack not installed", +) class BasicTestUdpMulticastBusIPv6(Back2BackTestCase): HOST_LOCAL_MCAST_GROUP_IPv6 = "ff11:7079:7468:6f6e:6465:6d6f:6d63:6173" diff --git a/test/listener_test.py b/test/listener_test.py index 54496176b..bbcbed56e 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -159,8 +159,13 @@ def testBufferedListenerReceives(self): def test_deprecated_loop_arg(recwarn): + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + warnings.simplefilter("always") - can.AsyncBufferedReader(loop=asyncio.get_event_loop()) + can.AsyncBufferedReader(loop=loop) assert len(recwarn) > 0 assert recwarn.pop(DeprecationWarning) recwarn.clear() diff --git a/tox.ini b/tox.ini index e112c22b4..c69f541f4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,21 +1,15 @@ [tox] +min_version = 4.22 [testenv] +dependency_groups = + test deps = - pytest==8.3.* - pytest-timeout==2.1.* - coveralls==3.3.1 - pytest-cov==4.0.0 - coverage==6.5.0 - hypothesis~=6.35.0 - pyserial~=3.5 - parameterized~=0.8 - asammdf>=6.0; platform_python_implementation=="CPython" and python_version<"3.13" + asammdf>=6.0; platform_python_implementation=="CPython" and python_version<"3.14" + msgpack~=1.1.0; python_version<"3.14" pywin32>=305; platform_system=="Windows" and platform_python_implementation=="CPython" and python_version<"3.14" - commands = pytest {posargs} - extras = canalystii @@ -30,13 +24,14 @@ passenv = [testenv:docs] description = Build and test the documentation basepython = py312 -deps = - -r doc/doc-requirements.txt - gs-usb - +dependency_groups = + docs extras = canalystii - + gs-usb + mf4 + remote + serial commands = python -m sphinx -b html -Wan --keep-going doc build python -m sphinx -b doctest -W --keep-going doc build From c46492b55a9613ef3e6df77b6f59a38e85416d5b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 30 May 2025 16:53:21 +0200 Subject: [PATCH 1186/1235] rename zlgcan-driver-py to zlgcan (#1946) Co-authored-by: zariiii9003 --- doc/plugin-interface.rst | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index bfdedf3c6..8e60c50c2 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -73,7 +73,7 @@ The table below lists interface drivers that can be added by installing addition +----------------------------+-------------------------------------------------------+ | `python-can-sontheim`_ | CAN Driver for Sontheim CAN interfaces (e.g. CANfox) | +----------------------------+-------------------------------------------------------+ -| `zlgcan-driver-py`_ | Python wrapper for zlgcan-driver-rs | +| `zlgcan`_ | Python wrapper for zlgcan-driver-rs | +----------------------------+-------------------------------------------------------+ | `python-can-cando`_ | Python wrapper for Netronics' CANdo and CANdoISO | +----------------------------+-------------------------------------------------------+ @@ -82,6 +82,6 @@ The table below lists interface drivers that can be added by installing addition .. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector .. _python-can-remote: https://github.com/christiansandberg/python-can-remote .. _python-can-sontheim: https://github.com/MattWoodhead/python-can-sontheim -.. _zlgcan-driver-py: https://github.com/zhuyu4839/zlgcan-driver +.. _zlgcan: https://github.com/jesses2025smith/zlgcan-driver .. _python-can-cando: https://github.com/belliriccardo/python-can-cando diff --git a/pyproject.toml b/pyproject.toml index 37abec266..6ad9ee7b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ pcan = ["uptime~=3.0.1"] remote = ["python-can-remote"] sontheim = ["python-can-sontheim>=0.1.2"] canine = ["python-can-canine>=0.2.2"] -zlgcan = ["zlgcan-driver-py"] +zlgcan = ["zlgcan"] viewer = [ "windows-curses; platform_system == 'Windows' and platform_python_implementation=='CPython'" ] From b075a68824b0063d6d1747af187a88e3a4fea768 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 30 May 2025 19:17:07 +0200 Subject: [PATCH 1187/1235] use ThreadPoolExecutor in detect_available_configs(), add timeout param (#1947) Co-authored-by: zariiii9003 --- can/interface.py | 95 +++++++++++++++-------- can/interfaces/usb2can/serial_selector.py | 2 + doc/conf.py | 2 +- doc/interfaces/gs_usb.rst | 2 +- pyproject.toml | 3 +- 5 files changed, 67 insertions(+), 37 deletions(-) diff --git a/can/interface.py b/can/interface.py index e017b9514..eee58ff41 100644 --- a/can/interface.py +++ b/can/interface.py @@ -4,9 +4,10 @@ CyclicSendTasks. """ +import concurrent.futures.thread import importlib import logging -from collections.abc import Iterable +from collections.abc import Callable, Iterable from typing import Any, Optional, Union, cast from . import util @@ -140,6 +141,7 @@ def Bus( # noqa: N802 def detect_available_configs( interfaces: Union[None, str, Iterable[str]] = None, + timeout: float = 5.0, ) -> list[AutoDetectedConfig]: """Detect all configurations/channels that the interfaces could currently connect with. @@ -148,59 +150,84 @@ def detect_available_configs( Automated configuration detection may not be implemented by every interface on every platform. This method will not raise - an error in that case, but with rather return an empty list + an error in that case, but will rather return an empty list for that interface. :param interfaces: either - the name of an interface to be searched in as a string, - an iterable of interface names to search in, or - `None` to search in all known interfaces. + :param timeout: maximum number of seconds to wait for all interface + detection tasks to complete. If exceeded, any pending tasks + will be cancelled, a warning will be logged, and the method + will return results gathered so far. :rtype: list[dict] :return: an iterable of dicts, each suitable for usage in - the constructor of :class:`can.BusABC`. + the constructor of :class:`can.BusABC`. Interfaces that + timed out will be logged as warnings and excluded. """ - # Figure out where to search + # Determine which interfaces to search if interfaces is None: interfaces = BACKENDS elif isinstance(interfaces, str): interfaces = (interfaces,) - # else it is supposed to be an iterable of strings + # otherwise assume iterable of strings - result = [] - for interface in interfaces: + # Collect detection callbacks + callbacks: dict[str, Callable[[], list[AutoDetectedConfig]]] = {} + for interface_keyword in interfaces: try: - bus_class = _get_class_for_interface(interface) + bus_class = _get_class_for_interface(interface_keyword) + callbacks[interface_keyword] = ( + bus_class._detect_available_configs # pylint: disable=protected-access + ) except CanInterfaceNotImplementedError: log_autodetect.debug( 'interface "%s" cannot be loaded for detection of available configurations', - interface, + interface_keyword, ) - continue - # get available channels - try: - available = list( - bus_class._detect_available_configs() # pylint: disable=protected-access - ) - except NotImplementedError: - log_autodetect.debug( - 'interface "%s" does not support detection of available configurations', - interface, - ) - else: - log_autodetect.debug( - 'interface "%s" detected %i available configurations', - interface, - len(available), - ) - - # add the interface name to the configs if it is not already present - for config in available: - if "interface" not in config: - config["interface"] = interface - - # append to result - result += available + result: list[AutoDetectedConfig] = [] + # Use manual executor to allow shutdown without waiting + executor = concurrent.futures.ThreadPoolExecutor() + try: + futures_to_keyword = { + executor.submit(func): kw for kw, func in callbacks.items() + } + done, not_done = concurrent.futures.wait( + futures_to_keyword, + timeout=timeout, + return_when=concurrent.futures.ALL_COMPLETED, + ) + # Log timed-out tasks + if not_done: + log_autodetect.warning( + "Timeout (%.2fs) reached for interfaces: %s", + timeout, + ", ".join(sorted(futures_to_keyword[fut] for fut in not_done)), + ) + # Process completed futures + for future in done: + keyword = futures_to_keyword[future] + try: + available = future.result() + except NotImplementedError: + log_autodetect.debug( + 'interface "%s" does not support detection of available configurations', + keyword, + ) + else: + log_autodetect.debug( + 'interface "%s" detected %i available configurations', + keyword, + len(available), + ) + for config in available: + config.setdefault("interface", keyword) + result.extend(available) + finally: + # shutdown immediately, do not wait for pending threads + executor.shutdown(wait=False, cancel_futures=True) return result diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index 9f3b7185e..18ad3f873 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -5,6 +5,7 @@ log = logging.getLogger("can.usb2can") try: + import pythoncom import win32com.client except ImportError: log.warning( @@ -50,6 +51,7 @@ def find_serial_devices(serial_matcher: str = "") -> list[str]: only device IDs starting with this string are returned """ serial_numbers = [] + pythoncom.CoInitialize() wmi = win32com.client.GetObject("winmgmts:") for usb_controller in wmi.InstancesOf("Win32_USBControllerDevice"): usb_device = wmi.Get(usb_controller.Dependent) diff --git a/doc/conf.py b/doc/conf.py index 34ce385cb..f4a9ab95f 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -136,7 +136,7 @@ ] # mock windows specific attributes -autodoc_mock_imports = ["win32com"] +autodoc_mock_imports = ["win32com", "pythoncom"] ctypes.windll = MagicMock() ctypesutil.HRESULT = ctypes.c_long diff --git a/doc/interfaces/gs_usb.rst b/doc/interfaces/gs_usb.rst index e9c0131c5..8bab07c6f 100755 --- a/doc/interfaces/gs_usb.rst +++ b/doc/interfaces/gs_usb.rst @@ -6,7 +6,7 @@ Geschwister Schneider and candleLight Windows/Linux/Mac CAN driver based on usbfs or WinUSB WCID for Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces. -Install: ``pip install "python-can[gs_usb]"`` +Install: ``pip install "python-can[gs-usb]"`` Usage: pass device ``index`` or ``channel`` (starting from 0) if using automatic device detection: diff --git a/pyproject.toml b/pyproject.toml index 6ad9ee7b2..a9f3fcbb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ docs = [ "furo", ] lint = [ - "pylint==3.2.*", + "pylint==3.3.*", "ruff==0.11.12", "black==25.1.*", "mypy==1.16.*", @@ -205,6 +205,7 @@ disable = [ "too-many-branches", "too-many-instance-attributes", "too-many-locals", + "too-many-positional-arguments", "too-many-public-methods", "too-many-statements", ] From 9fe17e4ce9eb05b63aa4e0ada0a8002d7ceb1de0 Mon Sep 17 00:00:00 2001 From: Nathan Pennie Date: Sat, 31 May 2025 17:48:26 +0000 Subject: [PATCH 1188/1235] Support 11-bit identifiers in the serial interface (#1758) --- can/interfaces/serial/serial_can.py | 14 +++++++++++--- doc/interfaces/serial.rst | 22 +++++++++++++++++++++- test/serial_test.py | 22 ++++++++++++++++++++-- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 9fe715267..e87a32063 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -135,7 +135,8 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: # Pack arbitration ID try: - arbitration_id = struct.pack("= 0x20000000: + is_extended_id = False if arbitration_id & 0x20000000 else True + arbitration_id -= 0 if is_extended_id else 0x20000000 + if is_extended_id and arbitration_id >= 0x20000000: raise ValueError( - "received arbitration id may not exceed 2^29 (0x20000000)" + "received arbitration id may not exceed or equal 2^29 (0x20000000) if extended" + ) + if not is_extended_id and arbitration_id >= 0x800: + raise ValueError( + "received arbitration id may not exceed or equal 2^11 (0x800) if not extended" ) data = self._ser.read(dlc) @@ -204,6 +211,7 @@ def _recv_internal( arbitration_id=arbitration_id, dlc=dlc, data=data, + is_extended_id=is_extended_id, ) return msg, False diff --git a/doc/interfaces/serial.rst b/doc/interfaces/serial.rst index 99ee54df6..316fc143b 100644 --- a/doc/interfaces/serial.rst +++ b/doc/interfaces/serial.rst @@ -30,7 +30,9 @@ six parts. The start and the stop byte for the frame, the timestamp, DLC, arbitration ID and the payload. The payload has a variable length of between 0 and 8 bytes, the other parts are fixed. Both, the timestamp and the arbitration ID will be interpreted as 4 byte unsigned integers. The DLC is -also an unsigned integer with a length of 1 byte. +also an unsigned integer with a length of 1 byte. Non-extended (11-bit) +identifiers are encoded by adding 0x20000000 to the 11-bit ID. For example, an +11-bit CAN ID of 0x123 is encoded with an arbitration ID of 0x20000123. Serial frame format ^^^^^^^^^^^^^^^^^^^ @@ -102,3 +104,21 @@ Examples of serial frames +================+=====================+======+=====================+==============+ | 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x00 | 0xBB | +----------------+---------------------+------+---------------------+--------------+ + +.. rubric:: CAN message with 0 byte payload with an 11-bit CAN ID + ++----------------+---------+ +| CAN message | ++----------------+---------+ +| Arbitration ID | Payload | ++================+=========+ +| 0x20000001 (1) | None | ++----------------+---------+ + ++----------------+---------------------+------+---------------------+--------------+ +| Serial frame | ++----------------+---------------------+------+---------------------+--------------+ +| Start of frame | Timestamp | DLC | Arbitration ID | End of frame | ++================+=====================+======+=====================+==============+ +| 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x20 | 0xBB | ++----------------+---------------------+------+---------------------+--------------+ diff --git a/test/serial_test.py b/test/serial_test.py index 5fa90704b..e183e8408 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -86,7 +86,7 @@ def test_rx_tx_data_none(self): def test_rx_tx_min_id(self): """ - Tests the transfer with the lowest arbitration id + Tests the transfer with the lowest extended arbitration id """ msg = can.Message(arbitration_id=0) self.bus.send(msg) @@ -95,13 +95,31 @@ def test_rx_tx_min_id(self): def test_rx_tx_max_id(self): """ - Tests the transfer with the highest arbitration id + Tests the transfer with the highest extended arbitration id """ msg = can.Message(arbitration_id=536870911) self.bus.send(msg) msg_receive = self.bus.recv() self.assertMessageEqual(msg, msg_receive) + def test_rx_tx_min_nonext_id(self): + """ + Tests the transfer with the lowest non-extended arbitration id + """ + msg = can.Message(arbitration_id=0x000, is_extended_id=False) + self.bus.send(msg) + msg_receive = self.bus.recv() + self.assertMessageEqual(msg, msg_receive) + + def test_rx_tx_max_nonext_id(self): + """ + Tests the transfer with the highest non-extended arbitration id + """ + msg = can.Message(arbitration_id=0x7FF, is_extended_id=False) + self.bus.send(msg) + msg_receive = self.bus.recv() + self.assertMessageEqual(msg, msg_receive) + def test_rx_tx_max_timestamp(self): """ Tests the transfer with the highest possible timestamp From 2b1f6f6d04fe3585dbd5b093e2fe52637378b624 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 31 May 2025 20:31:24 +0200 Subject: [PATCH 1189/1235] Add Support for Remote and Error Frames to SerialBus (#1948) --- .github/workflows/ci.yml | 1 + can/interfaces/serial/serial_can.py | 56 +++++++++++++++-------------- doc/interfaces/serial.rst | 12 +++---- test/serial_test.py | 46 +++++++++++++++++------- 4 files changed, 71 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f04654f0d..588d9a96b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,7 @@ jobs: - name: Setup SocketCAN if: ${{ matrix.os == 'ubuntu-latest' }} run: | + sudo apt-get update sudo apt-get -y install linux-modules-extra-$(uname -r) sudo ./test/open_vcan.sh - name: Test with pytest via tox diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index e87a32063..12ce5aff1 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -42,6 +42,13 @@ def list_comports() -> list[Any]: return [] +CAN_ERR_FLAG = 0x20000000 +CAN_RTR_FLAG = 0x40000000 +CAN_EFF_FLAG = 0x80000000 +CAN_ID_MASK_EXT = 0x1FFFFFFF +CAN_ID_MASK_STD = 0x7FF + + class SerialBus(BusABC): """ Enable basic can communication over a serial device. @@ -116,9 +123,6 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: :param msg: Message to send. - .. note:: Flags like ``extended_id``, ``is_remote_frame`` and - ``is_error_frame`` will be ignored. - .. note:: If the timestamp is a float value it will be converted to an integer. @@ -134,20 +138,25 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: raise ValueError(f"Timestamp is out of range: {msg.timestamp}") from None # Pack arbitration ID - try: - arbitration_id = msg.arbitration_id + (0 if msg.is_extended_id else 0x20000000) - arbitration_id = struct.pack("= 0x20000000: - raise ValueError( - "received arbitration id may not exceed or equal 2^29 (0x20000000) if extended" - ) - if not is_extended_id and arbitration_id >= 0x800: - raise ValueError( - "received arbitration id may not exceed or equal 2^11 (0x800) if not extended" - ) + is_extended_id = bool(arbitration_id & CAN_EFF_FLAG) + is_error_frame = bool(arbitration_id & CAN_ERR_FLAG) + is_remote_frame = bool(arbitration_id & CAN_RTR_FLAG) + + if is_extended_id: + arbitration_id = arbitration_id & CAN_ID_MASK_EXT + else: + arbitration_id = arbitration_id & CAN_ID_MASK_STD data = self._ser.read(dlc) @@ -212,6 +214,8 @@ def _recv_internal( dlc=dlc, data=data, is_extended_id=is_extended_id, + is_error_frame=is_error_frame, + is_remote_frame=is_remote_frame, ) return msg, False diff --git a/doc/interfaces/serial.rst b/doc/interfaces/serial.rst index 316fc143b..566ec7755 100644 --- a/doc/interfaces/serial.rst +++ b/doc/interfaces/serial.rst @@ -30,9 +30,9 @@ six parts. The start and the stop byte for the frame, the timestamp, DLC, arbitration ID and the payload. The payload has a variable length of between 0 and 8 bytes, the other parts are fixed. Both, the timestamp and the arbitration ID will be interpreted as 4 byte unsigned integers. The DLC is -also an unsigned integer with a length of 1 byte. Non-extended (11-bit) -identifiers are encoded by adding 0x20000000 to the 11-bit ID. For example, an -11-bit CAN ID of 0x123 is encoded with an arbitration ID of 0x20000123. +also an unsigned integer with a length of 1 byte. Extended (29-bit) +identifiers are encoded by adding 0x80000000 to the ID. For example, a +29-bit CAN ID of 0x123 is encoded with an arbitration ID of 0x80000123. Serial frame format ^^^^^^^^^^^^^^^^^^^ @@ -105,14 +105,14 @@ Examples of serial frames | 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x00 | 0xBB | +----------------+---------------------+------+---------------------+--------------+ -.. rubric:: CAN message with 0 byte payload with an 11-bit CAN ID +.. rubric:: Extended Frame CAN message with 0 byte payload with an 29-bit CAN ID +----------------+---------+ | CAN message | +----------------+---------+ | Arbitration ID | Payload | +================+=========+ -| 0x20000001 (1) | None | +| 0x80000001 (1) | None | +----------------+---------+ +----------------+---------------------+------+---------------------+--------------+ @@ -120,5 +120,5 @@ Examples of serial frames +----------------+---------------------+------+---------------------+--------------+ | Start of frame | Timestamp | DLC | Arbitration ID | End of frame | +================+=====================+======+=====================+==============+ -| 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x20 | 0xBB | +| 0xAA | 0x66 0x73 0x00 0x00 | 0x00 | 0x01 0x00 0x00 0x80 | 0xBB | +----------------+---------------------+------+---------------------+--------------+ diff --git a/test/serial_test.py b/test/serial_test.py index e183e8408..409485112 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -84,38 +84,38 @@ def test_rx_tx_data_none(self): msg_receive = self.bus.recv() self.assertMessageEqual(msg, msg_receive) - def test_rx_tx_min_id(self): + def test_rx_tx_min_std_id(self): """ - Tests the transfer with the lowest extended arbitration id + Tests the transfer with the lowest standard arbitration id """ - msg = can.Message(arbitration_id=0) + msg = can.Message(arbitration_id=0, is_extended_id=False) self.bus.send(msg) msg_receive = self.bus.recv() self.assertMessageEqual(msg, msg_receive) - def test_rx_tx_max_id(self): + def test_rx_tx_max_std_id(self): """ - Tests the transfer with the highest extended arbitration id + Tests the transfer with the highest standard arbitration id """ - msg = can.Message(arbitration_id=536870911) + msg = can.Message(arbitration_id=0x7FF, is_extended_id=False) self.bus.send(msg) msg_receive = self.bus.recv() self.assertMessageEqual(msg, msg_receive) - def test_rx_tx_min_nonext_id(self): + def test_rx_tx_min_ext_id(self): """ - Tests the transfer with the lowest non-extended arbitration id + Tests the transfer with the lowest extended arbitration id """ - msg = can.Message(arbitration_id=0x000, is_extended_id=False) + msg = can.Message(arbitration_id=0x000, is_extended_id=True) self.bus.send(msg) msg_receive = self.bus.recv() self.assertMessageEqual(msg, msg_receive) - def test_rx_tx_max_nonext_id(self): + def test_rx_tx_max_ext_id(self): """ - Tests the transfer with the highest non-extended arbitration id + Tests the transfer with the highest extended arbitration id """ - msg = can.Message(arbitration_id=0x7FF, is_extended_id=False) + msg = can.Message(arbitration_id=0x1FFFFFFF, is_extended_id=True) self.bus.send(msg) msg_receive = self.bus.recv() self.assertMessageEqual(msg, msg_receive) @@ -155,6 +155,28 @@ def test_rx_tx_min_timestamp_error(self): msg = can.Message(timestamp=-1) self.assertRaises(ValueError, self.bus.send, msg) + def test_rx_tx_err_frame(self): + """ + Test the transfer of error frames. + """ + msg = can.Message( + is_extended_id=False, is_error_frame=True, is_remote_frame=False + ) + self.bus.send(msg) + msg_receive = self.bus.recv() + self.assertMessageEqual(msg, msg_receive) + + def test_rx_tx_rtr_frame(self): + """ + Test the transfer of remote frames. + """ + msg = can.Message( + is_extended_id=False, is_error_frame=False, is_remote_frame=True + ) + self.bus.send(msg) + msg_receive = self.bus.recv() + self.assertMessageEqual(msg, msg_receive) + def test_when_no_fileno(self): """ Tests for the fileno method catching the missing pyserial implementeation on the Windows platform From dcc33a7027ef491893305dce9f60e28e7a2364ad Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 31 May 2025 21:40:43 +0200 Subject: [PATCH 1190/1235] Keep Track of Active Notifiers, Make Notifier usable as ContextManager (#1890) --- can/notifier.py | 179 ++++++++++++++++++++++++++++++++---- examples/asyncio_demo.py | 44 +++++---- examples/cyclic_checksum.py | 5 +- examples/print_notifier.py | 15 ++- examples/send_multiple.py | 2 +- examples/serial_com.py | 2 +- examples/vcan_filtered.py | 13 +-- pyproject.toml | 1 + test/notifier_test.py | 36 ++++++++ 9 files changed, 234 insertions(+), 63 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index 237c874da..2b9944450 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -8,7 +8,16 @@ import threading import time from collections.abc import Awaitable, Iterable -from typing import Any, Callable, Optional, Union +from contextlib import AbstractContextManager +from types import TracebackType +from typing import ( + Any, + Callable, + Final, + NamedTuple, + Optional, + Union, +) from can.bus import BusABC from can.listener import Listener @@ -19,7 +28,85 @@ MessageRecipient = Union[Listener, Callable[[Message], Union[Awaitable[None], None]]] -class Notifier: +class _BusNotifierPair(NamedTuple): + bus: "BusABC" + notifier: "Notifier" + + +class _NotifierRegistry: + """A registry to manage the association between CAN buses and Notifiers. + + This class ensures that a bus is not added to multiple active Notifiers. + """ + + def __init__(self) -> None: + """Initialize the registry with an empty list of bus-notifier pairs and a threading lock.""" + self.pairs: list[_BusNotifierPair] = [] + self.lock = threading.Lock() + + def register(self, bus: BusABC, notifier: "Notifier") -> None: + """Register a bus and its associated notifier. + + Ensures that a bus is not added to multiple active :class:`~can.Notifier` instances. + + :param bus: + The CAN bus to register. + :param notifier: + The :class:`~can.Notifier` instance associated with the bus. + :raises ValueError: + If the bus is already assigned to an active Notifier. + """ + with self.lock: + for pair in self.pairs: + if bus is pair.bus and not pair.notifier.stopped: + raise ValueError( + "A bus can not be added to multiple active Notifier instances." + ) + self.pairs.append(_BusNotifierPair(bus, notifier)) + + def unregister(self, bus: BusABC, notifier: "Notifier") -> None: + """Unregister a bus and its associated notifier. + + Removes the bus-notifier pair from the registry. + + :param bus: + The CAN bus to unregister. + :param notifier: + The :class:`~can.Notifier` instance associated with the bus. + """ + with self.lock: + registered_pairs_to_remove: list[_BusNotifierPair] = [] + for pair in self.pairs: + if pair.bus is bus and pair.notifier is notifier: + registered_pairs_to_remove.append(pair) + for pair in registered_pairs_to_remove: + self.pairs.remove(pair) + + def find_instances(self, bus: BusABC) -> tuple["Notifier", ...]: + """Find the :class:`~can.Notifier` instances associated with a given CAN bus. + + This method searches the registry for the :class:`~can.Notifier` + that is linked to the specified bus. If the bus is found, the + corresponding :class:`~can.Notifier` instances are returned. If the bus is not + found in the registry, an empty tuple is returned. + + :param bus: + The CAN bus for which to find the associated :class:`~can.Notifier` . + :return: + A tuple of :class:`~can.Notifier` instances associated with the given bus. + """ + instance_list = [] + with self.lock: + for pair in self.pairs: + if bus is pair.bus: + instance_list.append(pair.notifier) + return tuple(instance_list) + + +class Notifier(AbstractContextManager): + + _registry: Final = _NotifierRegistry() + def __init__( self, bus: Union[BusABC, list[BusABC]], @@ -33,61 +120,81 @@ def __init__( .. Note:: - Remember to call `stop()` after all messages are received as + Remember to call :meth:`~can.Notifier.stop` after all messages are received as many listeners carry out flush operations to persist data. - :param bus: A :ref:`bus` or a list of buses to listen to. + :param bus: + A :ref:`bus` or a list of buses to consume messages from. :param listeners: An iterable of :class:`~can.Listener` or callables that receive a :class:`~can.Message` and return nothing. - :param timeout: An optional maximum number of seconds to wait for any :class:`~can.Message`. - :param loop: An :mod:`asyncio` event loop to schedule the ``listeners`` in. + :param timeout: + An optional maximum number of seconds to wait for any :class:`~can.Message`. + :param loop: + An :mod:`asyncio` event loop to schedule the ``listeners`` in. + :raises ValueError: + If a passed in *bus* is already assigned to an active :class:`~can.Notifier`. """ self.listeners: list[MessageRecipient] = list(listeners) - self.bus = bus + self._bus_list: list[BusABC] = [] self.timeout = timeout self._loop = loop #: Exception raised in thread self.exception: Optional[Exception] = None - self._running = True + self._stopped = False self._lock = threading.Lock() self._readers: list[Union[int, threading.Thread]] = [] - buses = self.bus if isinstance(self.bus, list) else [self.bus] - for each_bus in buses: + _bus_list: list[BusABC] = bus if isinstance(bus, list) else [bus] + for each_bus in _bus_list: self.add_bus(each_bus) + @property + def bus(self) -> Union[BusABC, tuple["BusABC", ...]]: + """Return the associated bus or a tuple of buses.""" + if len(self._bus_list) == 1: + return self._bus_list[0] + return tuple(self._bus_list) + def add_bus(self, bus: BusABC) -> None: """Add a bus for notification. :param bus: CAN bus instance. + :raises ValueError: + If the *bus* is already assigned to an active :class:`~can.Notifier`. """ - reader: int = -1 + # add bus to notifier registry + Notifier._registry.register(bus, self) + + # add bus to internal bus list + self._bus_list.append(bus) + + file_descriptor: int = -1 try: - reader = bus.fileno() + file_descriptor = bus.fileno() except NotImplementedError: # Bus doesn't support fileno, we fall back to thread based reader pass - if self._loop is not None and reader >= 0: + if self._loop is not None and file_descriptor >= 0: # Use bus file descriptor to watch for messages - self._loop.add_reader(reader, self._on_message_available, bus) - self._readers.append(reader) + self._loop.add_reader(file_descriptor, self._on_message_available, bus) + self._readers.append(file_descriptor) else: reader_thread = threading.Thread( target=self._rx_thread, args=(bus,), - name=f'can.notifier for bus "{bus.channel_info}"', + name=f'{self.__class__.__qualname__} for bus "{bus.channel_info}"', ) reader_thread.daemon = True reader_thread.start() self._readers.append(reader_thread) - def stop(self, timeout: float = 5) -> None: + def stop(self, timeout: float = 5.0) -> None: """Stop notifying Listeners when new :class:`~can.Message` objects arrive and call :meth:`~can.Listener.stop` on each Listener. @@ -95,7 +202,7 @@ def stop(self, timeout: float = 5) -> None: Max time in seconds to wait for receive threads to finish. Should be longer than timeout given at instantiation. """ - self._running = False + self._stopped = True end_time = time.time() + timeout for reader in self._readers: if isinstance(reader, threading.Thread): @@ -109,6 +216,10 @@ def stop(self, timeout: float = 5) -> None: if hasattr(listener, "stop"): listener.stop() + # remove bus from registry + for bus in self._bus_list: + Notifier._registry.unregister(bus, self) + def _rx_thread(self, bus: BusABC) -> None: # determine message handling callable early, not inside while loop if self._loop: @@ -119,7 +230,7 @@ def _rx_thread(self, bus: BusABC) -> None: else: handle_message = self._on_message_received - while self._running: + while not self._stopped: try: if msg := bus.recv(self.timeout): with self._lock: @@ -184,3 +295,33 @@ def remove_listener(self, listener: MessageRecipient) -> None: :raises ValueError: if `listener` was never added to this notifier """ self.listeners.remove(listener) + + @property + def stopped(self) -> bool: + """Return ``True``, if Notifier was properly shut down with :meth:`~can.Notifier.stop`.""" + return self._stopped + + @staticmethod + def find_instances(bus: BusABC) -> tuple["Notifier", ...]: + """Find :class:`~can.Notifier` instances associated with a given CAN bus. + + This method searches the registry for the :class:`~can.Notifier` + that is linked to the specified bus. If the bus is found, the + corresponding :class:`~can.Notifier` instances are returned. If the bus is not + found in the registry, an empty tuple is returned. + + :param bus: + The CAN bus for which to find the associated :class:`~can.Notifier` . + :return: + A tuple of :class:`~can.Notifier` instances associated with the given bus. + """ + return Notifier._registry.find_instances(bus) + + def __exit__( + self, + exc_type: Optional[type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + if not self._stopped: + self.stop() diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index d29f03bc5..6befbe7a9 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -5,10 +5,12 @@ """ import asyncio -from typing import List +from typing import TYPE_CHECKING import can -from can.notifier import MessageRecipient + +if TYPE_CHECKING: + from can.notifier import MessageRecipient def print_message(msg: can.Message) -> None: @@ -25,32 +27,28 @@ async def main() -> None: reader = can.AsyncBufferedReader() logger = can.Logger("logfile.asc") - listeners: List[MessageRecipient] = [ + listeners: list[MessageRecipient] = [ print_message, # Callback function reader, # AsyncBufferedReader() listener logger, # Regular Listener object ] # Create Notifier with an explicit loop to use for scheduling of callbacks - loop = asyncio.get_running_loop() - notifier = can.Notifier(bus, listeners, loop=loop) - # Start sending first message - bus.send(can.Message(arbitration_id=0)) - - print("Bouncing 10 messages...") - for _ in range(10): - # Wait for next message from AsyncBufferedReader - msg = await reader.get_message() - # Delay response - await asyncio.sleep(0.5) - msg.arbitration_id += 1 - bus.send(msg) - - # Wait for last message to arrive - await reader.get_message() - print("Done!") - - # Clean-up - notifier.stop() + with can.Notifier(bus, listeners, loop=asyncio.get_running_loop()): + # Start sending first message + bus.send(can.Message(arbitration_id=0)) + + print("Bouncing 10 messages...") + for _ in range(10): + # Wait for next message from AsyncBufferedReader + msg = await reader.get_message() + # Delay response + await asyncio.sleep(0.5) + msg.arbitration_id += 1 + bus.send(msg) + + # Wait for last message to arrive + await reader.get_message() + print("Done!") if __name__ == "__main__": diff --git a/examples/cyclic_checksum.py b/examples/cyclic_checksum.py index 3ab6c78ac..763fcd72b 100644 --- a/examples/cyclic_checksum.py +++ b/examples/cyclic_checksum.py @@ -59,6 +59,5 @@ def compute_xbr_checksum(message: can.Message, counter: int) -> int: if __name__ == "__main__": with can.Bus(channel=0, interface="virtual", receive_own_messages=True) as _bus: - notifier = can.Notifier(bus=_bus, listeners=[print]) - cyclic_checksum_send(_bus) - notifier.stop() + with can.Notifier(bus=_bus, listeners=[print]): + cyclic_checksum_send(_bus) diff --git a/examples/print_notifier.py b/examples/print_notifier.py index 8d55ca1dc..e6e11dbec 100755 --- a/examples/print_notifier.py +++ b/examples/print_notifier.py @@ -8,14 +8,13 @@ def main(): with can.Bus(interface="virtual", receive_own_messages=True) as bus: print_listener = can.Printer() - notifier = can.Notifier(bus, [print_listener]) - - bus.send(can.Message(arbitration_id=1, is_extended_id=True)) - bus.send(can.Message(arbitration_id=2, is_extended_id=True)) - bus.send(can.Message(arbitration_id=1, is_extended_id=False)) - - time.sleep(1.0) - notifier.stop() + with can.Notifier(bus, listeners=[print_listener]): + # using Notifier as a context manager automatically calls `Notifier.stop()` + # at the end of the `with` block + bus.send(can.Message(arbitration_id=1, is_extended_id=True)) + bus.send(can.Message(arbitration_id=2, is_extended_id=True)) + bus.send(can.Message(arbitration_id=1, is_extended_id=False)) + time.sleep(1.0) if __name__ == "__main__": diff --git a/examples/send_multiple.py b/examples/send_multiple.py index fdcaa5b59..9123e1bc8 100755 --- a/examples/send_multiple.py +++ b/examples/send_multiple.py @@ -4,8 +4,8 @@ This demo creates multiple processes of producers to spam a socketcan bus. """ -from time import sleep from concurrent.futures import ProcessPoolExecutor +from time import sleep import can diff --git a/examples/serial_com.py b/examples/serial_com.py index 538c8d12f..9f203b2e0 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -18,8 +18,8 @@ com0com: http://com0com.sourceforge.net/ """ -import time import threading +import time import can diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index 9c67390ab..22bca706c 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -18,14 +18,11 @@ def main(): # print all incoming messages, which includes the ones sent, # since we set receive_own_messages to True # assign to some variable so it does not garbage collected - notifier = can.Notifier(bus, [can.Printer()]) # pylint: disable=unused-variable - - bus.send(can.Message(arbitration_id=1, is_extended_id=True)) - bus.send(can.Message(arbitration_id=2, is_extended_id=True)) - bus.send(can.Message(arbitration_id=1, is_extended_id=False)) - - time.sleep(1.0) - notifier.stop() + with can.Notifier(bus, [can.Printer()]): # pylint: disable=unused-variable + bus.send(can.Message(arbitration_id=1, is_extended_id=True)) + bus.send(can.Message(arbitration_id=2, is_extended_id=True)) + bus.send(can.Message(arbitration_id=1, is_extended_id=False)) + time.sleep(1.0) if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index a9f3fcbb1..2a5735598 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -184,6 +184,7 @@ ignore = [ ] "can/logger.py" = ["T20"] # flake8-print "can/player.py" = ["T20"] # flake8-print +"examples/*" = ["T20"] # flake8-print [tool.ruff.lint.isort] known-first-party = ["can"] diff --git a/test/notifier_test.py b/test/notifier_test.py index 6982130cf..c21d51f04 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -12,16 +12,19 @@ def test_single_bus(self): with can.Bus("test", interface="virtual", receive_own_messages=True) as bus: reader = can.BufferedReader() notifier = can.Notifier(bus, [reader], 0.1) + self.assertFalse(notifier.stopped) msg = can.Message() bus.send(msg) self.assertIsNotNone(reader.get_message(1)) notifier.stop() + self.assertTrue(notifier.stopped) def test_multiple_bus(self): with can.Bus(0, interface="virtual", receive_own_messages=True) as bus1: with can.Bus(1, interface="virtual", receive_own_messages=True) as bus2: reader = can.BufferedReader() notifier = can.Notifier([bus1, bus2], [reader], 0.1) + self.assertFalse(notifier.stopped) msg = can.Message() bus1.send(msg) time.sleep(0.1) @@ -33,6 +36,39 @@ def test_multiple_bus(self): self.assertIsNotNone(recv_msg) self.assertEqual(recv_msg.channel, 1) notifier.stop() + self.assertTrue(notifier.stopped) + + def test_context_manager(self): + with can.Bus("test", interface="virtual", receive_own_messages=True) as bus: + reader = can.BufferedReader() + with can.Notifier(bus, [reader], 0.1) as notifier: + self.assertFalse(notifier.stopped) + msg = can.Message() + bus.send(msg) + self.assertIsNotNone(reader.get_message(1)) + notifier.stop() + self.assertTrue(notifier.stopped) + + def test_registry(self): + with can.Bus("test", interface="virtual", receive_own_messages=True) as bus: + reader = can.BufferedReader() + with can.Notifier(bus, [reader], 0.1) as notifier: + # creating a second notifier for the same bus must fail + self.assertRaises(ValueError, can.Notifier, bus, [reader], 0.1) + + # find_instance must return the existing instance + self.assertEqual(can.Notifier.find_instances(bus), (notifier,)) + + # Notifier is stopped, find_instances() must return an empty tuple + self.assertEqual(can.Notifier.find_instances(bus), ()) + + # now the first notifier is stopped, a new notifier can be created without error: + with can.Notifier(bus, [reader], 0.1) as notifier: + # the next notifier call should fail again since there is an active notifier already + self.assertRaises(ValueError, can.Notifier, bus, [reader], 0.1) + + # find_instance must return the existing instance + self.assertEqual(can.Notifier.find_instances(bus), (notifier,)) class AsyncNotifierTest(unittest.TestCase): From f43bedbc68777162132251bad91aee6ed77d6b8b Mon Sep 17 00:00:00 2001 From: Joachim Stolberg <106076945+HMS-jost@users.noreply.github.com> Date: Sat, 31 May 2025 21:55:54 +0200 Subject: [PATCH 1191/1235] Handle timer overflow message and build timestamp according to the epoch in IXXATBus (#1934) * Handle timer overflow message and build timestamp according to the epoch * Revert "Handle timer overflow message and build timestamp according to the epoch" This reverts commit e0ccfb0d4b3fef9fbed24c9a6e673e5199e35c52. * Handle timer overflow message and build timestamp according to the epoch * Fix formating issues * Format with black --- can/interfaces/ixxat/canlib_vcinpl.py | 24 +++++++++++++++++++----- can/interfaces/ixxat/canlib_vcinpl2.py | 24 ++++++++++++++++++------ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index ba8f1870b..098b022bb 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -13,6 +13,7 @@ import functools import logging import sys +import time import warnings from collections.abc import Sequence from typing import Callable, Optional, Union @@ -620,7 +621,15 @@ def __init__( log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) # Start the CAN controller. Messages will be forwarded to the channel + start_begin = time.time() _canlib.canControlStart(self._control_handle, constants.TRUE) + start_end = time.time() + + # Calculate an offset to make them relative to epoch + # Assume that the time offset is in the middle of the start command + self._timeoffset = start_begin + (start_end - start_begin / 2) + self._overrunticks = 0 + self._starttickoffset = 0 # For cyclic transmit list. Set when .send_periodic() is first called self._scheduler = None @@ -693,6 +702,9 @@ def _recv_internal(self, timeout): f"Unknown CAN info message code {self._message.abData[0]}", ) ) + # Handle CAN start info message + if self._message.abData[0] == constants.CAN_INFO_START: + self._starttickoffset = self._message.dwTime elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR: if self._message.uMsgInfo.Bytes.bFlags & constants.CAN_MSGFLAGS_OVR: log.warning("CAN error: data overrun") @@ -709,7 +721,8 @@ def _recv_internal(self, timeout): self._message.uMsgInfo.Bytes.bFlags, ) elif self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR: - pass + # Add the number of timestamp overruns to the high word + self._overrunticks += self._message.dwMsgId << 32 else: log.warning( "Unexpected message info type 0x%X", @@ -741,11 +754,12 @@ def _recv_internal(self, timeout): # Timed out / can message type is not DATA return None, True - # The _message.dwTime is a 32bit tick value and will overrun, - # so expect to see the value restarting from 0 rx_msg = Message( - timestamp=self._message.dwTime - / self._tick_resolution, # Relative time in s + timestamp=( + (self._message.dwTime + self._overrunticks - self._starttickoffset) + / self._tick_resolution + ) + + self._timeoffset, is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr), is_extended_id=bool(self._message.uMsgInfo.Bits.ext), arbitration_id=self._message.dwMsgId, diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index f9ac5346b..b7698277f 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -727,7 +727,15 @@ def __init__( log.info("Accepting ID: 0x%X MASK: 0x%X", code, mask) # Start the CAN controller. Messages will be forwarded to the channel + start_begin = time.time() _canlib.canControlStart(self._control_handle, constants.TRUE) + start_end = time.time() + + # Calculate an offset to make them relative to epoch + # Assume that the time offset is in the middle of the start command + self._timeoffset = start_begin + (start_end - start_begin / 2) + self._overrunticks = 0 + self._starttickoffset = 0 # For cyclic transmit list. Set when .send_periodic() is first called self._scheduler = None @@ -832,7 +840,9 @@ def _recv_internal(self, timeout): f"Unknown CAN info message code {self._message.abData[0]}", ) ) - + # Handle CAN start info message + elif self._message.abData[0] == constants.CAN_INFO_START: + self._starttickoffset = self._message.dwTime elif ( self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_ERROR ): @@ -854,7 +864,8 @@ def _recv_internal(self, timeout): self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_TIMEOVR ): - pass + # Add the number of timestamp overruns to the high word + self._overrunticks += self._message.dwMsgId << 32 else: log.warning("Unexpected message info type") @@ -868,11 +879,12 @@ def _recv_internal(self, timeout): return None, True data_len = dlc2len(self._message.uMsgInfo.Bits.dlc) - # The _message.dwTime is a 32bit tick value and will overrun, - # so expect to see the value restarting from 0 rx_msg = Message( - timestamp=self._message.dwTime - / self._tick_resolution, # Relative time in s + timestamp=( + (self._message.dwTime + self._overrunticks - self._starttickoffset) + / self._tick_resolution + ) + + self._timeoffset, is_remote_frame=bool(self._message.uMsgInfo.Bits.rtr), is_fd=bool(self._message.uMsgInfo.Bits.edl), is_rx=True, From 3b75c338f4bd6e7eaa006e2b6483c059ac9bd475 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 6 Jun 2025 18:29:22 +0200 Subject: [PATCH 1192/1235] Add public functions for creating bus command lines options (#1949) --- can/cli.py | 320 ++++++++++++++++++++++++++++++++++++++++++++ can/logger.py | 219 +++--------------------------- can/player.py | 34 +++-- can/viewer.py | 32 ++--- doc/utils.rst | 3 + pyproject.toml | 2 + test/test_cli.py | 154 +++++++++++++++++++++ test/test_logger.py | 15 ++- test/test_viewer.py | 26 ++-- 9 files changed, 559 insertions(+), 246 deletions(-) create mode 100644 can/cli.py create mode 100644 test/test_cli.py diff --git a/can/cli.py b/can/cli.py new file mode 100644 index 000000000..6e3850354 --- /dev/null +++ b/can/cli.py @@ -0,0 +1,320 @@ +import argparse +import re +from collections.abc import Sequence +from typing import Any, Optional, Union + +import can +from can.typechecking import CanFilter, TAdditionalCliArgs +from can.util import _dict2timing, cast_from_string + + +def add_bus_arguments( + parser: argparse.ArgumentParser, + *, + filter_arg: bool = False, + prefix: Optional[str] = None, + group_title: Optional[str] = None, +) -> None: + """Adds CAN bus configuration options to an argument parser. + + :param parser: + The argument parser to which the options will be added. + :param filter_arg: + Whether to include the filter argument. + :param prefix: + An optional prefix for the argument names, allowing configuration of multiple buses. + :param group_title: + The title of the argument group. If not provided, a default title will be generated + based on the prefix. For example, "bus arguments (prefix)" if a prefix is specified, + or "bus arguments" otherwise. + """ + if group_title is None: + group_title = f"bus arguments ({prefix})" if prefix else "bus arguments" + + group = parser.add_argument_group(group_title) + + flags = [f"--{prefix}-channel"] if prefix else ["-c", "--channel"] + dest = f"{prefix}_channel" if prefix else "channel" + group.add_argument( + *flags, + dest=dest, + default=argparse.SUPPRESS, + metavar="CHANNEL", + help=r"Most backend interfaces require some sort of channel. For " + r"example with the serial interface the channel might be a rfcomm" + r' device: "/dev/rfcomm0". With the socketcan interface valid ' + r'channel examples include: "can0", "vcan0".', + ) + + flags = [f"--{prefix}-interface"] if prefix else ["-i", "--interface"] + dest = f"{prefix}_interface" if prefix else "interface" + group.add_argument( + *flags, + dest=dest, + default=argparse.SUPPRESS, + choices=sorted(can.VALID_INTERFACES), + help="""Specify the backend CAN interface to use. If left blank, + fall back to reading from configuration files.""", + ) + + flags = [f"--{prefix}-bitrate"] if prefix else ["-b", "--bitrate"] + dest = f"{prefix}_bitrate" if prefix else "bitrate" + group.add_argument( + *flags, + dest=dest, + type=int, + default=argparse.SUPPRESS, + metavar="BITRATE", + help="Bitrate to use for the CAN bus.", + ) + + flags = [f"--{prefix}-fd"] if prefix else ["--fd"] + dest = f"{prefix}_fd" if prefix else "fd" + group.add_argument( + *flags, + dest=dest, + default=argparse.SUPPRESS, + action="store_true", + help="Activate CAN-FD support", + ) + + flags = [f"--{prefix}-data-bitrate"] if prefix else ["--data-bitrate"] + dest = f"{prefix}_data_bitrate" if prefix else "data_bitrate" + group.add_argument( + *flags, + dest=dest, + type=int, + default=argparse.SUPPRESS, + metavar="DATA_BITRATE", + help="Bitrate to use for the data phase in case of CAN-FD.", + ) + + flags = [f"--{prefix}-timing"] if prefix else ["--timing"] + dest = f"{prefix}_timing" if prefix else "timing" + group.add_argument( + *flags, + dest=dest, + action=_BitTimingAction, + nargs=argparse.ONE_OR_MORE, + default=argparse.SUPPRESS, + metavar="TIMING_ARG", + help="Configure bit rate and bit timing. For example, use " + "`--timing f_clock=8_000_000 tseg1=5 tseg2=2 sjw=2 brp=2 nof_samples=1` for classical CAN " + "or `--timing f_clock=80_000_000 nom_tseg1=119 nom_tseg2=40 nom_sjw=40 nom_brp=1 " + "data_tseg1=29 data_tseg2=10 data_sjw=10 data_brp=1` for CAN FD. " + "Check the python-can documentation to verify whether your " + "CAN interface supports the `timing` argument.", + ) + + if filter_arg: + flags = [f"--{prefix}-filter"] if prefix else ["--filter"] + dest = f"{prefix}_can_filters" if prefix else "can_filters" + group.add_argument( + *flags, + dest=dest, + nargs=argparse.ONE_OR_MORE, + action=_CanFilterAction, + default=argparse.SUPPRESS, + metavar="{:,~}", + help="R|Space separated CAN filters for the given CAN interface:" + "\n : (matches when & mask ==" + " can_id & mask)" + "\n ~ (matches when & mask !=" + " can_id & mask)" + "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" + "\n python -m can.viewer --filter 100:7FC 200:7F0" + "\nNote that the ID and mask are always interpreted as hex values", + ) + + flags = [f"--{prefix}-bus-kwargs"] if prefix else ["--bus-kwargs"] + dest = f"{prefix}_bus_kwargs" if prefix else "bus_kwargs" + group.add_argument( + *flags, + dest=dest, + action=_BusKwargsAction, + nargs=argparse.ONE_OR_MORE, + default=argparse.SUPPRESS, + metavar="BUS_KWARG", + help="Pass keyword arguments down to the instantiation of the bus class. " + "For example, `-i vector -c 1 --bus-kwargs app_name=MyCanApp serial=1234` is equivalent " + "to opening the bus with `can.Bus('vector', channel=1, app_name='MyCanApp', serial=1234)", + ) + + +def create_bus_from_namespace( + namespace: argparse.Namespace, + *, + prefix: Optional[str] = None, + **kwargs: Any, +) -> can.BusABC: + """Creates and returns a CAN bus instance based on the provided namespace and arguments. + + :param namespace: + The namespace containing parsed arguments. + :param prefix: + An optional prefix for the argument names, enabling support for multiple buses. + :param kwargs: + Additional keyword arguments to configure the bus. + :return: + A CAN bus instance. + """ + config: dict[str, Any] = {"single_handle": True, **kwargs} + + for keyword in ( + "channel", + "interface", + "bitrate", + "fd", + "data_bitrate", + "can_filters", + "timing", + "bus_kwargs", + ): + prefixed_keyword = f"{prefix}_{keyword}" if prefix else keyword + + if prefixed_keyword in namespace: + value = getattr(namespace, prefixed_keyword) + + if keyword == "bus_kwargs": + config.update(value) + else: + config[keyword] = value + + try: + return can.Bus(**config) + except Exception as exc: + err_msg = f"Unable to instantiate bus from arguments {vars(namespace)}." + raise argparse.ArgumentError(None, err_msg) from exc + + +class _CanFilterAction(argparse.Action): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, + ) -> None: + if not isinstance(values, list): + raise argparse.ArgumentError(self, "Invalid filter argument") + + print(f"Adding filter(s): {values}") + can_filters: list[CanFilter] = [] + + for filt in values: + if ":" in filt: + parts = filt.split(":") + can_id = int(parts[0], base=16) + can_mask = int(parts[1], base=16) + elif "~" in filt: + parts = filt.split("~") + can_id = int(parts[0], base=16) | 0x20000000 # CAN_INV_FILTER + can_mask = int(parts[1], base=16) & 0x20000000 # socket.CAN_ERR_FLAG + else: + raise argparse.ArgumentError(self, "Invalid filter argument") + can_filters.append({"can_id": can_id, "can_mask": can_mask}) + + setattr(namespace, self.dest, can_filters) + + +class _BitTimingAction(argparse.Action): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, + ) -> None: + if not isinstance(values, list): + raise argparse.ArgumentError(self, "Invalid --timing argument") + + timing_dict: dict[str, int] = {} + for arg in values: + try: + key, value_string = arg.split("=") + value = int(value_string) + timing_dict[key] = value + except ValueError: + raise argparse.ArgumentError( + self, f"Invalid timing argument: {arg}" + ) from None + + if not (timing := _dict2timing(timing_dict)): + err_msg = "Invalid --timing argument. Incomplete parameters." + raise argparse.ArgumentError(self, err_msg) + + setattr(namespace, self.dest, timing) + print(timing) + + +class _BusKwargsAction(argparse.Action): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, + ) -> None: + if not isinstance(values, list): + raise argparse.ArgumentError(self, "Invalid --bus-kwargs argument") + + bus_kwargs: dict[str, Union[str, int, float, bool]] = {} + + for arg in values: + try: + match = re.match( + r"^(?P[_a-zA-Z][_a-zA-Z0-9]*)=(?P\S*?)$", + arg, + ) + if not match: + raise ValueError + key = match["name"].replace("-", "_") + string_val = match["value"] + bus_kwargs[key] = cast_from_string(string_val) + except ValueError: + raise argparse.ArgumentError( + self, + f"Unable to parse bus keyword argument '{arg}'", + ) from None + + setattr(namespace, self.dest, bus_kwargs) + + +def _add_extra_args( + parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup], +) -> None: + parser.add_argument( + "extra_args", + nargs=argparse.REMAINDER, + help="The remaining arguments will be used for logger/player initialisation. " + "For example, `can_logger -i virtual -c test -f logfile.blf --compression-level=9` " + "passes the keyword argument `compression_level=9` to the BlfWriter.", + ) + + +def _parse_additional_config(unknown_args: Sequence[str]) -> TAdditionalCliArgs: + for arg in unknown_args: + if not re.match(r"^--[a-zA-Z][a-zA-Z0-9\-]*=\S*?$", arg): + raise ValueError(f"Parsing argument {arg} failed") + + def _split_arg(_arg: str) -> tuple[str, str]: + left, right = _arg.split("=", 1) + return left.lstrip("-").replace("-", "_"), right + + args: dict[str, Union[str, int, float, bool]] = {} + for key, string_val in map(_split_arg, unknown_args): + args[key] = cast_from_string(string_val) + return args + + +def _set_logging_level_from_namespace(namespace: argparse.Namespace) -> None: + if "verbosity" in namespace: + logging_level_names = [ + "critical", + "error", + "warning", + "info", + "debug", + "subdebug", + ] + can.set_logging_level(logging_level_names[min(5, namespace.verbosity)]) diff --git a/can/logger.py b/can/logger.py index 9c1134257..8274d6668 100644 --- a/can/logger.py +++ b/can/logger.py @@ -1,203 +1,25 @@ import argparse import errno -import re import sys -from collections.abc import Sequence from datetime import datetime from typing import ( TYPE_CHECKING, - Any, - Optional, Union, ) -import can -from can import Bus, BusState, Logger, SizedRotatingLogger +from can import BusState, Logger, SizedRotatingLogger +from can.cli import ( + _add_extra_args, + _parse_additional_config, + _set_logging_level_from_namespace, + add_bus_arguments, + create_bus_from_namespace, +) from can.typechecking import TAdditionalCliArgs -from can.util import _dict2timing, cast_from_string if TYPE_CHECKING: from can.io import BaseRotatingLogger from can.io.generic import MessageWriter - from can.typechecking import CanFilter - - -def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None: - """Adds common options to an argument parser.""" - - parser.add_argument( - "-c", - "--channel", - help=r"Most backend interfaces require some sort of channel. For " - r"example with the serial interface the channel might be a rfcomm" - r' device: "/dev/rfcomm0". With the socketcan interface valid ' - r'channel examples include: "can0", "vcan0".', - ) - - parser.add_argument( - "-i", - "--interface", - dest="interface", - help="""Specify the backend CAN interface to use. If left blank, - fall back to reading from configuration files.""", - choices=sorted(can.VALID_INTERFACES), - ) - - parser.add_argument( - "-b", "--bitrate", type=int, help="Bitrate to use for the CAN bus." - ) - - parser.add_argument("--fd", help="Activate CAN-FD support", action="store_true") - - parser.add_argument( - "--data_bitrate", - type=int, - help="Bitrate to use for the data phase in case of CAN-FD.", - ) - - parser.add_argument( - "--timing", - action=_BitTimingAction, - nargs=argparse.ONE_OR_MORE, - help="Configure bit rate and bit timing. For example, use " - "`--timing f_clock=8_000_000 tseg1=5 tseg2=2 sjw=2 brp=2 nof_samples=1` for classical CAN " - "or `--timing f_clock=80_000_000 nom_tseg1=119 nom_tseg2=40 nom_sjw=40 nom_brp=1 " - "data_tseg1=29 data_tseg2=10 data_sjw=10 data_brp=1` for CAN FD. " - "Check the python-can documentation to verify whether your " - "CAN interface supports the `timing` argument.", - metavar="TIMING_ARG", - ) - - parser.add_argument( - "extra_args", - nargs=argparse.REMAINDER, - help="The remaining arguments will be used for the interface and " - "logger/player initialisation. " - "For example, `-i vector -c 1 --app-name=MyCanApp` is the equivalent " - "to opening the bus with `Bus('vector', channel=1, app_name='MyCanApp')", - ) - - -def _append_filter_argument( - parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup], - *args: str, - **kwargs: Any, -) -> None: - """Adds the ``filter`` option to an argument parser.""" - - parser.add_argument( - *args, - "--filter", - help="R|Space separated CAN filters for the given CAN interface:" - "\n : (matches when & mask ==" - " can_id & mask)" - "\n ~ (matches when & mask !=" - " can_id & mask)" - "\nFx to show only frames with ID 0x100 to 0x103 and 0x200 to 0x20F:" - "\n python -m can.viewer --filter 100:7FC 200:7F0" - "\nNote that the ID and mask are always interpreted as hex values", - metavar="{:,~}", - nargs=argparse.ONE_OR_MORE, - action=_CanFilterAction, - dest="can_filters", - **kwargs, - ) - - -def _create_bus(parsed_args: argparse.Namespace, **kwargs: Any) -> can.BusABC: - logging_level_names = ["critical", "error", "warning", "info", "debug", "subdebug"] - can.set_logging_level(logging_level_names[min(5, parsed_args.verbosity)]) - - config: dict[str, Any] = {"single_handle": True, **kwargs} - if parsed_args.interface: - config["interface"] = parsed_args.interface - if parsed_args.bitrate: - config["bitrate"] = parsed_args.bitrate - if parsed_args.fd: - config["fd"] = True - if parsed_args.data_bitrate: - config["data_bitrate"] = parsed_args.data_bitrate - if getattr(parsed_args, "can_filters", None): - config["can_filters"] = parsed_args.can_filters - if parsed_args.timing: - config["timing"] = parsed_args.timing - - return Bus(parsed_args.channel, **config) - - -class _CanFilterAction(argparse.Action): - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - values: Union[str, Sequence[Any], None], - option_string: Optional[str] = None, - ) -> None: - if not isinstance(values, list): - raise argparse.ArgumentError(None, "Invalid filter argument") - - print(f"Adding filter(s): {values}") - can_filters: list[CanFilter] = [] - - for filt in values: - if ":" in filt: - parts = filt.split(":") - can_id = int(parts[0], base=16) - can_mask = int(parts[1], base=16) - elif "~" in filt: - parts = filt.split("~") - can_id = int(parts[0], base=16) | 0x20000000 # CAN_INV_FILTER - can_mask = int(parts[1], base=16) & 0x20000000 # socket.CAN_ERR_FLAG - else: - raise argparse.ArgumentError(None, "Invalid filter argument") - can_filters.append({"can_id": can_id, "can_mask": can_mask}) - - setattr(namespace, self.dest, can_filters) - - -class _BitTimingAction(argparse.Action): - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - values: Union[str, Sequence[Any], None], - option_string: Optional[str] = None, - ) -> None: - if not isinstance(values, list): - raise argparse.ArgumentError(None, "Invalid --timing argument") - - timing_dict: dict[str, int] = {} - for arg in values: - try: - key, value_string = arg.split("=") - value = int(value_string) - timing_dict[key] = value - except ValueError: - raise argparse.ArgumentError( - None, f"Invalid timing argument: {arg}" - ) from None - - if not (timing := _dict2timing(timing_dict)): - err_msg = "Invalid --timing argument. Incomplete parameters." - raise argparse.ArgumentError(None, err_msg) - - setattr(namespace, self.dest, timing) - print(timing) - - -def _parse_additional_config(unknown_args: Sequence[str]) -> TAdditionalCliArgs: - for arg in unknown_args: - if not re.match(r"^--[a-zA-Z][a-zA-Z0-9\-]*=\S*?$", arg): - raise ValueError(f"Parsing argument {arg} failed") - - def _split_arg(_arg: str) -> tuple[str, str]: - left, right = _arg.split("=", 1) - return left.lstrip("-").replace("-", "_"), right - - args: dict[str, Union[str, int, float, bool]] = {} - for key, string_val in map(_split_arg, unknown_args): - args[key] = cast_from_string(string_val) - return args def _parse_logger_args( @@ -210,11 +32,9 @@ def _parse_logger_args( "given file.", ) - # Generate the standard arguments: - # Channel, bitrate, data_bitrate, interface, app_name, CAN-FD support - _create_base_argument_parser(parser) + logger_group = parser.add_argument_group("logger arguments") - parser.add_argument( + logger_group.add_argument( "-f", "--file_name", dest="log_file", @@ -222,7 +42,7 @@ def _parse_logger_args( default=None, ) - parser.add_argument( + logger_group.add_argument( "-a", "--append", dest="append", @@ -230,7 +50,7 @@ def _parse_logger_args( action="store_true", ) - parser.add_argument( + logger_group.add_argument( "-s", "--file_size", dest="file_size", @@ -242,7 +62,7 @@ def _parse_logger_args( default=None, ) - parser.add_argument( + logger_group.add_argument( "-v", action="count", dest="verbosity", @@ -251,9 +71,7 @@ def _parse_logger_args( default=2, ) - _append_filter_argument(parser) - - state_group = parser.add_mutually_exclusive_group(required=False) + state_group = logger_group.add_mutually_exclusive_group(required=False) state_group.add_argument( "--active", help="Start the bus as active, this is applied by default.", @@ -263,6 +81,12 @@ def _parse_logger_args( "--passive", help="Start the bus as passive.", action="store_true" ) + # handle remaining arguments + _add_extra_args(logger_group) + + # add bus options + add_bus_arguments(parser, filter_arg=True) + # print help message when no arguments were given if not args: parser.print_help(sys.stderr) @@ -275,7 +99,8 @@ def _parse_logger_args( def main() -> None: results, additional_config = _parse_logger_args(sys.argv[1:]) - bus = _create_bus(results, **additional_config) + bus = create_bus_from_namespace(results) + _set_logging_level_from_namespace(results) if results.active: bus.state = BusState.ACTIVE diff --git a/can/player.py b/can/player.py index 38b76a331..a92cccc3d 100644 --- a/can/player.py +++ b/can/player.py @@ -12,8 +12,13 @@ from typing import TYPE_CHECKING, cast from can import LogReader, MessageSync - -from .logger import _create_base_argument_parser, _create_bus, _parse_additional_config +from can.cli import ( + _add_extra_args, + _parse_additional_config, + _set_logging_level_from_namespace, + add_bus_arguments, + create_bus_from_namespace, +) if TYPE_CHECKING: from collections.abc import Iterable @@ -24,9 +29,9 @@ def main() -> None: parser = argparse.ArgumentParser(description="Replay CAN traffic.") - _create_base_argument_parser(parser) + player_group = parser.add_argument_group("Player arguments") - parser.add_argument( + player_group.add_argument( "-f", "--file_name", dest="log_file", @@ -34,7 +39,7 @@ def main() -> None: default=None, ) - parser.add_argument( + player_group.add_argument( "-v", action="count", dest="verbosity", @@ -43,27 +48,27 @@ def main() -> None: default=2, ) - parser.add_argument( + player_group.add_argument( "--ignore-timestamps", dest="timestamps", help="""Ignore timestamps (send all frames immediately with minimum gap between frames)""", action="store_false", ) - parser.add_argument( + player_group.add_argument( "--error-frames", help="Also send error frames to the interface.", action="store_true", ) - parser.add_argument( + player_group.add_argument( "-g", "--gap", type=float, help=" minimum time between replayed frames", default=0.0001, ) - parser.add_argument( + player_group.add_argument( "-s", "--skip", type=float, @@ -71,13 +76,19 @@ def main() -> None: help=" skip gaps greater than 's' seconds", ) - parser.add_argument( + player_group.add_argument( "infile", metavar="input-file", type=str, help="The file to replay. For supported types see can.LogReader.", ) + # handle remaining arguments + _add_extra_args(player_group) + + # add bus options + add_bus_arguments(parser) + # print help message when no arguments were given if len(sys.argv) < 2: parser.print_help(sys.stderr) @@ -86,11 +97,12 @@ def main() -> None: results, unknown_args = parser.parse_known_args() additional_config = _parse_additional_config([*results.extra_args, *unknown_args]) + _set_logging_level_from_namespace(results) verbosity = results.verbosity error_frames = results.error_frames - with _create_bus(results, **additional_config) as bus: + with create_bus_from_namespace(results) as bus: with LogReader(results.infile, **additional_config) as reader: in_sync = MessageSync( cast("Iterable[Message]", reader), diff --git a/can/viewer.py b/can/viewer.py index 3eed727ab..81e8942a4 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -29,13 +29,12 @@ import time from can import __version__ -from can.logger import ( - _append_filter_argument, - _create_base_argument_parser, - _create_bus, - _parse_additional_config, +from can.cli import ( + _set_logging_level_from_namespace, + add_bus_arguments, + create_bus_from_namespace, ) -from can.typechecking import TAdditionalCliArgs, TDataStructs +from can.typechecking import TDataStructs logger = logging.getLogger("can.viewer") @@ -390,7 +389,7 @@ def _fill_text(self, text, width, indent): def _parse_viewer_args( args: list[str], -) -> tuple[argparse.Namespace, TDataStructs, TAdditionalCliArgs]: +) -> tuple[argparse.Namespace, TDataStructs]: # Parse command line arguments parser = argparse.ArgumentParser( "python -m can.viewer", @@ -411,9 +410,8 @@ def _parse_viewer_args( allow_abbrev=False, ) - # Generate the standard arguments: - # Channel, bitrate, data_bitrate, interface, app_name, CAN-FD support - _create_base_argument_parser(parser) + # add bus options group + add_bus_arguments(parser, filter_arg=True, group_title="Bus arguments") optional = parser.add_argument_group("Optional arguments") @@ -470,8 +468,6 @@ def _parse_viewer_args( default="", ) - _append_filter_argument(optional, "-f") - optional.add_argument( "-v", action="count", @@ -487,6 +483,8 @@ def _parse_viewer_args( raise SystemExit(errno.EINVAL) parsed_args, unknown_args = parser.parse_known_args(args) + if unknown_args: + print("Unknown arguments:", unknown_args) # Dictionary used to convert between Python values and C structs represented as Python strings. # If the value is 'None' then the message does not contain any data package. @@ -536,15 +534,13 @@ def _parse_viewer_args( else: data_structs[key] = struct.Struct(fmt) - additional_config = _parse_additional_config( - [*parsed_args.extra_args, *unknown_args] - ) - return parsed_args, data_structs, additional_config + return parsed_args, data_structs def main() -> None: - parsed_args, data_structs, additional_config = _parse_viewer_args(sys.argv[1:]) - bus = _create_bus(parsed_args, **additional_config) + parsed_args, data_structs = _parse_viewer_args(sys.argv[1:]) + bus = create_bus_from_namespace(parsed_args) + _set_logging_level_from_namespace(parsed_args) curses.wrapper(CanViewer, bus, data_structs) # type: ignore[attr-defined,unused-ignore] diff --git a/doc/utils.rst b/doc/utils.rst index a87d411a9..9c742e2fb 100644 --- a/doc/utils.rst +++ b/doc/utils.rst @@ -4,4 +4,7 @@ Utilities .. autofunction:: can.detect_available_configs +.. autofunction:: can.cli.add_bus_arguments + +.. autofunction:: can.cli.create_bus_from_namespace diff --git a/pyproject.toml b/pyproject.toml index 2a5735598..ee98fec24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -182,8 +182,10 @@ ignore = [ "PGH003", # blanket-type-ignore "RUF012", # mutable-class-default ] +"can/cli.py" = ["T20"] # flake8-print "can/logger.py" = ["T20"] # flake8-print "can/player.py" = ["T20"] # flake8-print +"can/viewer.py" = ["T20"] # flake8-print "examples/*" = ["T20"] # flake8-print [tool.ruff.lint.isort] diff --git a/test/test_cli.py b/test/test_cli.py new file mode 100644 index 000000000..ecc662832 --- /dev/null +++ b/test/test_cli.py @@ -0,0 +1,154 @@ +import argparse +import unittest +from unittest.mock import patch + +from can.cli import add_bus_arguments, create_bus_from_namespace + + +class TestCliUtils(unittest.TestCase): + def test_add_bus_arguments(self): + parser = argparse.ArgumentParser() + add_bus_arguments(parser, filter_arg=True, prefix="test") + + parsed_args = parser.parse_args( + [ + "--test-channel", + "0", + "--test-interface", + "vector", + "--test-timing", + "f_clock=8000000", + "brp=4", + "tseg1=11", + "tseg2=4", + "sjw=2", + "nof_samples=3", + "--test-filter", + "100:7FF", + "200~7F0", + "--test-bus-kwargs", + "app_name=MyApp", + "serial=1234", + ] + ) + + self.assertNotIn("channel", parsed_args) + self.assertNotIn("test_bitrate", parsed_args) + self.assertNotIn("test_data_bitrate", parsed_args) + self.assertNotIn("test_fd", parsed_args) + + self.assertEqual(parsed_args.test_channel, "0") + self.assertEqual(parsed_args.test_interface, "vector") + self.assertEqual(parsed_args.test_timing.f_clock, 8000000) + self.assertEqual(parsed_args.test_timing.brp, 4) + self.assertEqual(parsed_args.test_timing.tseg1, 11) + self.assertEqual(parsed_args.test_timing.tseg2, 4) + self.assertEqual(parsed_args.test_timing.sjw, 2) + self.assertEqual(parsed_args.test_timing.nof_samples, 3) + self.assertEqual(len(parsed_args.test_can_filters), 2) + self.assertEqual(parsed_args.test_can_filters[0]["can_id"], 0x100) + self.assertEqual(parsed_args.test_can_filters[0]["can_mask"], 0x7FF) + self.assertEqual(parsed_args.test_can_filters[1]["can_id"], 0x200 | 0x20000000) + self.assertEqual( + parsed_args.test_can_filters[1]["can_mask"], 0x7F0 & 0x20000000 + ) + self.assertEqual(parsed_args.test_bus_kwargs["app_name"], "MyApp") + self.assertEqual(parsed_args.test_bus_kwargs["serial"], 1234) + + def test_add_bus_arguments_no_prefix(self): + parser = argparse.ArgumentParser() + add_bus_arguments(parser, filter_arg=True) + + parsed_args = parser.parse_args( + [ + "--channel", + "0", + "--interface", + "vector", + "--timing", + "f_clock=8000000", + "brp=4", + "tseg1=11", + "tseg2=4", + "sjw=2", + "nof_samples=3", + "--filter", + "100:7FF", + "200~7F0", + "--bus-kwargs", + "app_name=MyApp", + "serial=1234", + ] + ) + + self.assertEqual(parsed_args.channel, "0") + self.assertEqual(parsed_args.interface, "vector") + self.assertEqual(parsed_args.timing.f_clock, 8000000) + self.assertEqual(parsed_args.timing.brp, 4) + self.assertEqual(parsed_args.timing.tseg1, 11) + self.assertEqual(parsed_args.timing.tseg2, 4) + self.assertEqual(parsed_args.timing.sjw, 2) + self.assertEqual(parsed_args.timing.nof_samples, 3) + self.assertEqual(len(parsed_args.can_filters), 2) + self.assertEqual(parsed_args.can_filters[0]["can_id"], 0x100) + self.assertEqual(parsed_args.can_filters[0]["can_mask"], 0x7FF) + self.assertEqual(parsed_args.can_filters[1]["can_id"], 0x200 | 0x20000000) + self.assertEqual(parsed_args.can_filters[1]["can_mask"], 0x7F0 & 0x20000000) + self.assertEqual(parsed_args.bus_kwargs["app_name"], "MyApp") + self.assertEqual(parsed_args.bus_kwargs["serial"], 1234) + + @patch("can.Bus") + def test_create_bus_from_namespace(self, mock_bus): + namespace = argparse.Namespace( + test_channel="vcan0", + test_interface="virtual", + test_bitrate=500000, + test_data_bitrate=2000000, + test_fd=True, + test_can_filters=[{"can_id": 0x100, "can_mask": 0x7FF}], + test_bus_kwargs={"app_name": "MyApp", "serial": 1234}, + ) + + create_bus_from_namespace(namespace, prefix="test") + + mock_bus.assert_called_once_with( + channel="vcan0", + interface="virtual", + bitrate=500000, + data_bitrate=2000000, + fd=True, + can_filters=[{"can_id": 0x100, "can_mask": 0x7FF}], + app_name="MyApp", + serial=1234, + single_handle=True, + ) + + @patch("can.Bus") + def test_create_bus_from_namespace_no_prefix(self, mock_bus): + namespace = argparse.Namespace( + channel="vcan0", + interface="virtual", + bitrate=500000, + data_bitrate=2000000, + fd=True, + can_filters=[{"can_id": 0x100, "can_mask": 0x7FF}], + bus_kwargs={"app_name": "MyApp", "serial": 1234}, + ) + + create_bus_from_namespace(namespace) + + mock_bus.assert_called_once_with( + channel="vcan0", + interface="virtual", + bitrate=500000, + data_bitrate=2000000, + fd=True, + can_filters=[{"can_id": 0x100, "can_mask": 0x7FF}], + app_name="MyApp", + serial=1234, + single_handle=True, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_logger.py b/test/test_logger.py index d9f200e00..41778ab6a 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -14,6 +14,7 @@ import pytest import can +import can.cli import can.logger @@ -89,7 +90,7 @@ def test_log_virtual_with_config(self): "--bitrate", "250000", "--fd", - "--data_bitrate", + "--data-bitrate", "2000000", ] can.logger.main() @@ -111,7 +112,7 @@ def test_parse_logger_args(self): "--bitrate", "250000", "--fd", - "--data_bitrate", + "--data-bitrate", "2000000", "--receive-own-messages=True", ] @@ -205,7 +206,7 @@ def test_parse_additional_config(self): "--offset=1.5", "--tseg1-abr=127", ] - parsed_args = can.logger._parse_additional_config(unknown_args) + parsed_args = can.cli._parse_additional_config(unknown_args) assert "app_name" in parsed_args assert parsed_args["app_name"] == "CANalyzer" @@ -232,16 +233,16 @@ def test_parse_additional_config(self): assert parsed_args["tseg1_abr"] == 127 with pytest.raises(ValueError): - can.logger._parse_additional_config(["--wrong-format"]) + can.cli._parse_additional_config(["--wrong-format"]) with pytest.raises(ValueError): - can.logger._parse_additional_config(["-wrongformat=value"]) + can.cli._parse_additional_config(["-wrongformat=value"]) with pytest.raises(ValueError): - can.logger._parse_additional_config(["--wrongformat=value1 value2"]) + can.cli._parse_additional_config(["--wrongformat=value1 value2"]) with pytest.raises(ValueError): - can.logger._parse_additional_config(["wrongformat="]) + can.cli._parse_additional_config(["wrongformat="]) class TestLoggerCompressedFile(unittest.TestCase): diff --git a/test/test_viewer.py b/test/test_viewer.py index 3bd32b25a..e71d06dc8 100644 --- a/test/test_viewer.py +++ b/test/test_viewer.py @@ -397,19 +397,19 @@ def test_pack_unpack(self): ) def test_parse_args(self): - parsed_args, _, _ = _parse_viewer_args(["-b", "250000"]) + parsed_args, _ = _parse_viewer_args(["-b", "250000"]) self.assertEqual(parsed_args.bitrate, 250000) - parsed_args, _, _ = _parse_viewer_args(["--bitrate", "500000"]) + parsed_args, _ = _parse_viewer_args(["--bitrate", "500000"]) self.assertEqual(parsed_args.bitrate, 500000) - parsed_args, _, _ = _parse_viewer_args(["-c", "can0"]) + parsed_args, _ = _parse_viewer_args(["-c", "can0"]) self.assertEqual(parsed_args.channel, "can0") - parsed_args, _, _ = _parse_viewer_args(["--channel", "PCAN_USBBUS1"]) + parsed_args, _ = _parse_viewer_args(["--channel", "PCAN_USBBUS1"]) self.assertEqual(parsed_args.channel, "PCAN_USBBUS1") - parsed_args, data_structs, _ = _parse_viewer_args(["-d", "100: Date: Fri, 13 Jun 2025 00:04:43 +0200 Subject: [PATCH 1193/1235] Parse socketcand error messages to create a CAN error frame (#1941) * parse socketcand error messages to create a CAN error frame * Add test for socketcand convert_ascii_message_to_can_message --- can/interfaces/socketcand/socketcand.py | 32 +++++++++++++++-- test/test_socketcand.py | 46 +++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 test/test_socketcand.py diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 3ecda082b..d401102f7 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -124,10 +124,11 @@ def detect_beacon(timeout_ms: int = 3100) -> list[can.typechecking.AutoDetectedC def convert_ascii_message_to_can_message(ascii_msg: str) -> can.Message: - if not ascii_msg.startswith("< frame ") or not ascii_msg.endswith(" >"): - log.warning(f"Could not parse ascii message: {ascii_msg}") + if not ascii_msg.endswith(" >"): + log.warning(f"Missing ending character in ascii message: {ascii_msg}") return None - else: + + if ascii_msg.startswith("< frame "): # frame_string = ascii_msg.removeprefix("< frame ").removesuffix(" >") frame_string = ascii_msg[8:-2] parts = frame_string.split(" ", 3) @@ -146,6 +147,31 @@ def convert_ascii_message_to_can_message(ascii_msg: str) -> can.Message: ) return can_message + if ascii_msg.startswith("< error "): + frame_string = ascii_msg[8:-2] + parts = frame_string.split(" ", 3) + can_id, timestamp = int(parts[0], 16), float(parts[1]) + is_ext = len(parts[0]) != 3 + + # socketcand sends no data in the error message so we don't have information + # about the error details, therefore the can frame is created with one + # data byte set to zero + data = bytearray([0]) + can_dlc = len(data) + can_message = can.Message( + timestamp=timestamp, + arbitration_id=can_id & 0x1FFFFFFF, + is_error_frame=True, + data=data, + dlc=can_dlc, + is_extended_id=True, + is_rx=True, + ) + return can_message + + log.warning(f"Could not parse ascii message: {ascii_msg}") + return None + def convert_can_message_to_ascii_message(can_message: can.Message) -> str: # Note: socketcan bus adds extended flag, remote_frame_flag & error_flag to id diff --git a/test/test_socketcand.py b/test/test_socketcand.py new file mode 100644 index 000000000..7050b9f20 --- /dev/null +++ b/test/test_socketcand.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +import unittest +import can +from can.interfaces.socketcand import socketcand + + +class TestConvertAsciiMessageToCanMessage(unittest.TestCase): + def test_valid_frame_message(self): + # Example: < frame 123 1680000000.0 01020304 > + ascii_msg = "< frame 123 1680000000.0 01020304 >" + msg = socketcand.convert_ascii_message_to_can_message(ascii_msg) + self.assertIsInstance(msg, can.Message) + self.assertEqual(msg.arbitration_id, 0x123) + self.assertEqual(msg.timestamp, 1680000000.0) + self.assertEqual(msg.data, bytearray([1, 2, 3, 4])) + self.assertEqual(msg.dlc, 4) + self.assertFalse(msg.is_extended_id) + self.assertTrue(msg.is_rx) + + def test_valid_error_message(self): + # Example: < error 1ABCDEF0 1680000001.0 > + ascii_msg = "< error 1ABCDEF0 1680000001.0 >" + msg = socketcand.convert_ascii_message_to_can_message(ascii_msg) + self.assertIsInstance(msg, can.Message) + self.assertEqual(msg.arbitration_id, 0x1ABCDEF0) + self.assertEqual(msg.timestamp, 1680000001.0) + self.assertEqual(msg.data, bytearray([0])) + self.assertEqual(msg.dlc, 1) + self.assertTrue(msg.is_extended_id) + self.assertTrue(msg.is_error_frame) + self.assertTrue(msg.is_rx) + + def test_invalid_message(self): + ascii_msg = "< unknown 123 0.0 >" + msg = socketcand.convert_ascii_message_to_can_message(ascii_msg) + self.assertIsNone(msg) + + def test_missing_ending_character(self): + ascii_msg = "< frame 123 1680000000.0 01020304" + msg = socketcand.convert_ascii_message_to_can_message(ascii_msg) + self.assertIsNone(msg) + + +if __name__ == "__main__": + unittest.main() From 958fc64ed504d6c655e92e66c48e0b49ee8f8aca Mon Sep 17 00:00:00 2001 From: Dennis Johnson <23355179+Dennis-Johnson@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:40:41 +0530 Subject: [PATCH 1194/1235] Fix UDP multicast interface on MacOS (#1940) * Add sock option SO_REUSEPORT to allow udp multicast on macos * macos doesn't support ioctl SIOCGSTAMP * REUSE PORT option not supported on windows * only import ioctl on linux --- can/interfaces/udp_multicast/bus.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index ec94e22b5..45882ec07 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -1,5 +1,6 @@ import errno import logging +import platform import select import socket import struct @@ -13,14 +14,9 @@ from .utils import is_msgpack_installed, pack_message, unpack_message -ioctl_supported = True - -try: +is_linux = platform.system() == "Linux" +if is_linux: from fcntl import ioctl -except ModuleNotFoundError: # Missing on Windows - ioctl_supported = False - pass - log = logging.getLogger(__name__) @@ -275,6 +271,10 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: # Allow multiple programs to access that address + port sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # Option not supported on Windows. + if hasattr(socket, "SO_REUSEPORT"): + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + # set how to receive timestamps try: sock.setsockopt(socket.SOL_SOCKET, SO_TIMESTAMPNS, 1) @@ -401,7 +401,8 @@ def recv( self.max_buffer ) - if ioctl_supported: + if is_linux: + # This ioctl isn't supported on Darwin & Windows. result_buffer = ioctl( self._socket.fileno(), SIOCGSTAMP, From 82d52fcd5b231fa05f5164beb26108d0db8a1bda Mon Sep 17 00:00:00 2001 From: Denis Jullien Date: Mon, 21 Jul 2025 10:55:32 +0200 Subject: [PATCH 1195/1235] Add TRCReader RTR frames support (#1953) * New trc test files with RTR frame * trc reader support for RTR frames parsing * black format --- can/io/trc.py | 20 ++++++++++---- test/data/test_CanMessage_V1_0_BUS1.trc | 7 ++--- test/data/test_CanMessage_V1_1.trc | 3 ++- test/data/test_CanMessage_V1_3.trc | 35 ++++++++++++++----------- test/data/test_CanMessage_V2_0_BUS1.trc | 11 ++++---- test/data/test_CanMessage_V2_1.trc | 11 ++++---- test/logformats_test.py | 16 +++++++++++ 7 files changed, 68 insertions(+), 35 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index a07a53a4d..e1eaa077c 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -153,7 +153,10 @@ def _parse_msg_v1_0(self, cols: tuple[str, ...]) -> Optional[Message]: msg.is_extended_id = len(arbit_id) > 4 msg.channel = 1 msg.dlc = int(cols[3]) - msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) + if len(cols) > 4 and cols[4] == "RTR": + msg.is_remote_frame = True + else: + msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) return msg def _parse_msg_v1_1(self, cols: tuple[str, ...]) -> Optional[Message]: @@ -165,7 +168,10 @@ def _parse_msg_v1_1(self, cols: tuple[str, ...]) -> Optional[Message]: msg.is_extended_id = len(arbit_id) > 4 msg.channel = 1 msg.dlc = int(cols[4]) - msg.data = bytearray([int(cols[i + 5], 16) for i in range(msg.dlc)]) + if len(cols) > 5 and cols[5] == "RTR": + msg.is_remote_frame = True + else: + msg.data = bytearray([int(cols[i + 5], 16) for i in range(msg.dlc)]) msg.is_rx = cols[2] == "Rx" return msg @@ -178,7 +184,10 @@ def _parse_msg_v1_3(self, cols: tuple[str, ...]) -> Optional[Message]: msg.is_extended_id = len(arbit_id) > 4 msg.channel = int(cols[2]) msg.dlc = int(cols[6]) - msg.data = bytearray([int(cols[i + 7], 16) for i in range(msg.dlc)]) + if len(cols) > 7 and cols[7] == "RTR": + msg.is_remote_frame = True + else: + msg.data = bytearray([int(cols[i + 7], 16) for i in range(msg.dlc)]) msg.is_rx = cols[3] == "Rx" return msg @@ -200,7 +209,8 @@ def _parse_msg_v2_x(self, cols: tuple[str, ...]) -> Optional[Message]: msg.is_extended_id = len(cols[self.columns["I"]]) > 4 msg.channel = int(cols[bus]) if bus is not None else 1 msg.dlc = dlc - if dlc: + msg.is_remote_frame = type_ in {"RR"} + if dlc and not msg.is_remote_frame: msg.data = bytearray.fromhex(cols[self.columns["D"]]) msg.is_rx = cols[self.columns["d"]] == "Rx" msg.is_fd = type_ in {"FD", "FB", "FE", "BI"} @@ -227,7 +237,7 @@ def _parse_cols_v1_3(self, cols: tuple[str, ...]) -> Optional[Message]: def _parse_cols_v2_x(self, cols: tuple[str, ...]) -> Optional[Message]: dtype = cols[self.columns["T"]] - if dtype in {"DT", "FD", "FB", "FE", "BI"}: + if dtype in {"DT", "FD", "FB", "FE", "BI", "RR"}: return self._parse_msg_v2_x(cols) else: logger.info("TRCReader: Unsupported type '%s'", dtype) diff --git a/test/data/test_CanMessage_V1_0_BUS1.trc b/test/data/test_CanMessage_V1_0_BUS1.trc index 8985db188..c3905ae47 100644 --- a/test/data/test_CanMessage_V1_0_BUS1.trc +++ b/test/data/test_CanMessage_V1_0_BUS1.trc @@ -1,10 +1,10 @@ ;########################################################################## -; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_0_BUS1.trc +; C:\NewFileName_BUS1.trc ; -; CAN activities imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +; CAN activities imported from C:\test_CanMessage_V1_1.trc ; Start time: 18.12.2021 14:28:07.062 ; PCAN-Net: N/A -; Generated by PEAK-Converter Version 2.2.4.136 +; Generated by PEAK-Converter Version 3.0.4.594 ; ; Columns description: ; ~~~~~~~~~~~~~~~~~~~~~ @@ -26,3 +26,4 @@ 9) 20798 00000100 8 00 00 00 00 00 00 00 00 10) 20956 00000100 8 00 00 00 00 00 00 00 00 11) 21097 00000100 8 00 00 00 00 00 00 00 00 + 12) 48937 0704 1 RTR \ No newline at end of file diff --git a/test/data/test_CanMessage_V1_1.trc b/test/data/test_CanMessage_V1_1.trc index 5a02cd59b..9a9cc7574 100644 --- a/test/data/test_CanMessage_V1_1.trc +++ b/test/data/test_CanMessage_V1_1.trc @@ -22,4 +22,5 @@ 8) 20592.7 Tx 00000100 8 00 00 00 00 00 00 00 00 9) 20798.6 Tx 00000100 8 00 00 00 00 00 00 00 00 10) 20956.0 Tx 00000100 8 00 00 00 00 00 00 00 00 - 11) 21097.1 Tx 00000100 8 00 00 00 00 00 00 00 00 + 11) 21097.1 Tx 00000100 8 00 00 00 00 00 00 00 00 + 12) 48937.6 Rx 0704 1 RTR diff --git a/test/data/test_CanMessage_V1_3.trc b/test/data/test_CanMessage_V1_3.trc index 5b0bf060a..96db1748c 100644 --- a/test/data/test_CanMessage_V1_3.trc +++ b/test/data/test_CanMessage_V1_3.trc @@ -1,13 +1,14 @@ ;$FILEVERSION=1.3 ;$STARTTIME=44548.6028595139 +; C:\NewFileName_V1_3.trc ; -; C:\test.trc -; Start time: 18.12.2021 14:28:07.062.0 -; Generated by PCAN-Explorer v5.4.0 +; Start time: 18.12.2021 14:28:07.062.1 +; +; Generated by PEAK-Converter Version 3.0.4.594 +; Data imported from C:\test_CanMessage_V1_1.trc ;------------------------------------------------------------------------------- -; Bus Name Connection Protocol Bit rate -; 1 PCAN Untitled@pcan_usb CAN 500 kbit/s -; 2 PTCAN PCANLight_USB_16@pcan_usb CAN +; Bus Name Connection Protocol +; N/A N/A N/A CAN ;------------------------------------------------------------------------------- ; Message Number ; | Time Offset (ms) @@ -20,13 +21,15 @@ ; | | | | | | | | ; | | | | | | | | ;---+-- ------+------ +- --+-- ----+--- +- -+-- -+ -- -- -- -- -- -- -- - 1) 17535.4 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 - 2) 17700.3 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 - 3) 17873.8 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 - 4) 19295.4 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 - 5) 19500.6 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 - 6) 19705.2 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 - 7) 20592.7 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 - 8) 20798.6 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 - 9) 20956.0 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 - 10) 21097.1 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 1) 17535.400 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 2) 17540.300 1 Warng FFFFFFFF - 4 00 00 00 08 BUSHEAVY + 3) 17700.300 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 4) 17873.800 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 5) 19295.400 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 + 6) 19500.600 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 + 7) 19705.200 1 Tx 0000 - 8 00 00 00 00 00 00 00 00 + 8) 20592.700 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 9) 20798.600 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 10) 20956.000 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 11) 21097.100 1 Tx 00000100 - 8 00 00 00 00 00 00 00 00 + 12) 48937.600 1 Rx 0704 - 1 RTR diff --git a/test/data/test_CanMessage_V2_0_BUS1.trc b/test/data/test_CanMessage_V2_0_BUS1.trc index cf2384df0..c1af8abc1 100644 --- a/test/data/test_CanMessage_V2_0_BUS1.trc +++ b/test/data/test_CanMessage_V2_0_BUS1.trc @@ -2,18 +2,18 @@ ;$STARTTIME=44548.6028595139 ;$COLUMNS=N,O,T,I,d,l,D ; -; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V2_0_BUS1.trc +; C:\test_CanMessage_V2_0_BUS1.trc ; Start time: 18.12.2021 14:28:07.062.001 -; Generated by PEAK-Converter Version 2.2.4.136 -; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +; Generated by PEAK-Converter Version 3.0.4.594 +; Data imported from C:\test_CanMessage_V1_1.trc ;------------------------------------------------------------------------------- ; Connection Bit rate -; N/A N/A +; N/A N/A ;------------------------------------------------------------------------------- ; Message Time Type ID Rx/Tx ; Number Offset | [hex] | Data Length ; | [ms] | | | | Data [hex] ... -; | | | | | | | +; | | | | | | | ;---+-- ------+------ +- --+----- +- +- +- -- -- -- -- -- -- -- 1 17535.400 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 2 17540.300 ST Rx 00 00 00 08 @@ -26,3 +26,4 @@ 9 20798.600 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 10 20956.000 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 11 21097.100 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 12 48937.600 RR 0704 Rx 1 \ No newline at end of file diff --git a/test/data/test_CanMessage_V2_1.trc b/test/data/test_CanMessage_V2_1.trc index 55ceefaf1..0d259f084 100644 --- a/test/data/test_CanMessage_V2_1.trc +++ b/test/data/test_CanMessage_V2_1.trc @@ -2,19 +2,19 @@ ;$STARTTIME=44548.6028595139 ;$COLUMNS=N,O,T,B,I,d,R,L,D ; -; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V2_1.trc +; C:\test_CanMessage_V2_1.trc ; Start time: 18.12.2021 14:28:07.062.001 -; Generated by PEAK-Converter Version 2.2.4.136 -; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +; Generated by PEAK-Converter Version 3.0.4.594 +; Data imported from C:\test_CanMessage_V1_1.trc ;------------------------------------------------------------------------------- ; Bus Name Connection Protocol -; N/A N/A N/A N/A +; N/A N/A N/A N/A ;------------------------------------------------------------------------------- ; Message Time Type ID Rx/Tx ; Number Offset | Bus [hex] | Reserved ; | [ms] | | | | | Data Length Code ; | | | | | | | | Data [hex] ... -; | | | | | | | | | +; | | | | | | | | | ;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- 1 17535.400 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 2 17540.300 ST 1 - Rx - 4 00 00 00 08 @@ -27,3 +27,4 @@ 9 20798.600 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 10 20956.000 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 11 21097.100 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 12 48937.600 RR 1 0704 Rx - 1 \ No newline at end of file diff --git a/test/logformats_test.py b/test/logformats_test.py index f3fe485b2..e9db47af3 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -990,6 +990,7 @@ def test_can_message(self): ("V1_0", "test_CanMessage_V1_0_BUS1.trc", False), ("V1_1", "test_CanMessage_V1_1.trc", True), ("V1_3", "test_CanMessage_V1_3.trc", True), + ("V2_0", "test_CanMessage_V2_0_BUS1.trc", True), ("V2_1", "test_CanMessage_V2_1.trc", True), ] ) @@ -1029,6 +1030,20 @@ def msg_ext(timestamp): msg.is_rx = False return msg + def msg_rtr(timestamp): + msg = can.Message( + timestamp=timestamp + start_time, + arbitration_id=0x704, + is_extended_id=False, + is_remote_frame=True, + channel=1, + dlc=1, + data=[], + ) + if is_rx_support: + msg.is_rx = True + return msg + expected_messages = [ msg_ext(17.5354), msg_ext(17.7003), @@ -1040,6 +1055,7 @@ def msg_ext(timestamp): msg_ext(20.7986), msg_ext(20.9560), msg_ext(21.0971), + msg_rtr(48.9376), ] actual = self._read_log_file(filename) self.assertMessagesEqual(actual, expected_messages) From 141223a47aad9d7663609a4713be0f47d217100c Mon Sep 17 00:00:00 2001 From: Arclight <59406481+chinaheyu@users.noreply.github.com> Date: Mon, 21 Jul 2025 17:06:47 +0800 Subject: [PATCH 1196/1235] Add an alternative plugin interface for gs_usb (#1954) --- doc/plugin-interface.rst | 3 +++ pyproject.toml | 1 + 2 files changed, 4 insertions(+) diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index 8e60c50c2..d841281e8 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -77,6 +77,8 @@ The table below lists interface drivers that can be added by installing addition +----------------------------+-------------------------------------------------------+ | `python-can-cando`_ | Python wrapper for Netronics' CANdo and CANdoISO | +----------------------------+-------------------------------------------------------+ +| `python-can-candle`_ | A full-featured driver for candleLight | ++----------------------------+-------------------------------------------------------+ .. _python-can-canine: https://github.com/tinymovr/python-can-canine .. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector @@ -84,4 +86,5 @@ The table below lists interface drivers that can be added by installing addition .. _python-can-sontheim: https://github.com/MattWoodhead/python-can-sontheim .. _zlgcan: https://github.com/jesses2025smith/zlgcan-driver .. _python-can-cando: https://github.com/belliriccardo/python-can-cando +.. _python-can-candle: https://github.com/BIRLab/python-can-candle diff --git a/pyproject.toml b/pyproject.toml index ee98fec24..a6a7f38c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,7 @@ remote = ["python-can-remote"] sontheim = ["python-can-sontheim>=0.1.2"] canine = ["python-can-canine>=0.2.2"] zlgcan = ["zlgcan"] +candle = ["python-can-candle>=1.2.2"] viewer = [ "windows-curses; platform_system == 'Windows' and platform_python_implementation=='CPython'" ] From c4808b744fac75b4d1a7a1658def9f7601413ada Mon Sep 17 00:00:00 2001 From: fl0gee <132301678+fl0gee@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:10:56 +0200 Subject: [PATCH 1197/1235] Support "no init access" feature of Kvaser interfaces (#1955) * Add keyword argument and set flag accordingly * Add test case * Trim trailing whitespace * Run black formatting --- can/interfaces/kvaser/canlib.py | 6 +++++- test/test_kvaser.py | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 9731a4415..a1dd03e58 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -439,7 +439,8 @@ def __init__( :param int data_bitrate: Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. - + :param bool no_init_access: + Don't open the handle with init access. """ log.info(f"CAN Filters: {can_filters}") @@ -455,6 +456,7 @@ def __init__( exclusive = kwargs.get("exclusive", False) override_exclusive = kwargs.get("override_exclusive", False) accept_virtual = kwargs.get("accept_virtual", True) + no_init_access = kwargs.get("no_init_access", False) fd = isinstance(timing, BitTimingFd) if timing else kwargs.get("fd", False) data_bitrate = kwargs.get("data_bitrate", None) fd_non_iso = kwargs.get("fd_non_iso", False) @@ -491,6 +493,8 @@ def __init__( flags |= canstat.canOPEN_OVERRIDE_EXCLUSIVE if accept_virtual: flags |= canstat.canOPEN_ACCEPT_VIRTUAL + if no_init_access: + flags |= canstat.canOPEN_NO_INIT_ACCESS if fd: if fd_non_iso: flags |= canstat.canOPEN_CAN_FD_NONISO diff --git a/test/test_kvaser.py b/test/test_kvaser.py index c18b3bc15..1ad035dec 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -277,6 +277,19 @@ def test_bus_get_stats(self): self.assertTrue(canlib.canGetBusStatistics.called) self.assertIsInstance(stats, canlib.structures.BusStatistics) + def test_bus_no_init_access(self): + canlib.canOpenChannel.reset_mock() + bus = can.Bus(interface="kvaser", channel=0, no_init_access=True) + + self.assertGreater(canlib.canOpenChannel.call_count, 0) + for call in canlib.canOpenChannel.call_args_list: + self.assertEqual( + call[0][1] & constants.canOPEN_NO_INIT_ACCESS, + constants.canOPEN_NO_INIT_ACCESS, + ) + + bus.shutdown() + @staticmethod def canGetNumberOfChannels(count): count._obj.value = 2 From 85b1cb2dd19f781d8ff2aa224b814dddbda6e037 Mon Sep 17 00:00:00 2001 From: ssj71 Date: Tue, 22 Jul 2025 04:51:31 -0700 Subject: [PATCH 1198/1235] Add FD support to slcan according to CANable 2.0 impementation (#1920) * add FD support to slcan according to CANable 2.0 impementation * allow 0 data bitrate to allow non-FD settings * make interface more consistent with other HW * proper DLC handling for FD frames in slcan * adding tests for slcan FD support * black formatting * adding optional keyword to optional arg --------- Co-authored-by: spencer --- can/interfaces/slcan.py | 77 ++++++++++-- test/test_slcan.py | 258 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+), 7 deletions(-) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index c51b298cc..01ba9c995 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -16,7 +16,12 @@ CanOperationError, error_check, ) -from can.util import check_or_adjust_timing_clock, deprecated_args_alias +from can.util import ( + CAN_FD_DLC, + check_or_adjust_timing_clock, + deprecated_args_alias, + len2dlc, +) logger = logging.getLogger(__name__) @@ -48,6 +53,11 @@ class slcanBus(BusABC): 1000000: "S8", 83300: "S9", } + _DATA_BITRATES = { + 0: "", + 2000000: "Y2", + 5000000: "Y5", + } _SLEEP_AFTER_SERIAL_OPEN = 2 # in seconds @@ -86,7 +96,8 @@ def __init__( If this argument is set then it overrides the bitrate and btr arguments. The `f_clock` value of the timing instance must be set to 8_000_000 (8MHz) for standard CAN. - CAN FD and the :class:`~can.BitTimingFd` class are not supported. + CAN FD and the :class:`~can.BitTimingFd` class have partial support according to the non-standard + slcan protocol implementation in the CANABLE 2.0 firmware: currently only data rates of 2M and 5M. :param poll_interval: Poll interval in seconds when reading messages :param sleep_after_open: @@ -143,9 +154,7 @@ def __init__( timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000]) self.set_bitrate_reg(f"{timing.btr0:02X}{timing.btr1:02X}") elif isinstance(timing, BitTimingFd): - raise NotImplementedError( - f"CAN FD is not supported by {self.__class__.__name__}." - ) + self.set_bitrate(timing.nom_bitrate, timing.data_bitrate) else: if bitrate is not None and btr is not None: raise ValueError("Bitrate and btr mutually exclusive.") @@ -157,10 +166,12 @@ def __init__( super().__init__(channel, **kwargs) - def set_bitrate(self, bitrate: int) -> None: + def set_bitrate(self, bitrate: int, data_bitrate: Optional[int] = None) -> None: """ :param bitrate: Bitrate in bit/s + :param data_bitrate: + Data Bitrate in bit/s for FD frames :raise ValueError: if ``bitrate`` is not among the possible values """ @@ -169,9 +180,15 @@ def set_bitrate(self, bitrate: int) -> None: else: bitrates = ", ".join(str(k) for k in self._BITRATES.keys()) raise ValueError(f"Invalid bitrate, choose one of {bitrates}.") + if data_bitrate in self._DATA_BITRATES: + dbitrate_code = self._DATA_BITRATES[data_bitrate] + else: + dbitrates = ", ".join(str(k) for k in self._DATA_BITRATES.keys()) + raise ValueError(f"Invalid data bitrate, choose one of {dbitrates}.") self.close() self._write(bitrate_code) + self._write(dbitrate_code) self.open() def set_bitrate_reg(self, btr: str) -> None: @@ -235,6 +252,8 @@ def _recv_internal( remote = False extended = False data = None + isFd = False + fdBrs = False if self._queue.qsize(): string: Optional[str] = self._queue.get_nowait() @@ -268,6 +287,34 @@ def _recv_internal( dlc = int(string[9]) extended = True remote = True + elif string[0] == "d": + # FD standard frame + canId = int(string[1:4], 16) + dlc = int(string[4], 16) + isFd = True + data = bytearray.fromhex(string[5 : 5 + CAN_FD_DLC[dlc] * 2]) + elif string[0] == "D": + # FD extended frame + canId = int(string[1:9], 16) + dlc = int(string[9], 16) + extended = True + isFd = True + data = bytearray.fromhex(string[10 : 10 + CAN_FD_DLC[dlc] * 2]) + elif string[0] == "b": + # FD with bitrate switch + canId = int(string[1:4], 16) + dlc = int(string[4], 16) + isFd = True + fdBrs = True + data = bytearray.fromhex(string[5 : 5 + CAN_FD_DLC[dlc] * 2]) + elif string[0] == "B": + # FD extended with bitrate switch + canId = int(string[1:9], 16) + dlc = int(string[9], 16) + extended = True + isFd = True + fdBrs = True + data = bytearray.fromhex(string[10 : 10 + CAN_FD_DLC[dlc] * 2]) if canId is not None: msg = Message( @@ -275,7 +322,9 @@ def _recv_internal( is_extended_id=extended, timestamp=time.time(), # Better than nothing... is_remote_frame=remote, - dlc=dlc, + is_fd=isFd, + bitrate_switch=fdBrs, + dlc=CAN_FD_DLC[dlc], data=data, ) return msg, False @@ -289,6 +338,20 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: sendStr = f"R{msg.arbitration_id:08X}{msg.dlc:d}" else: sendStr = f"r{msg.arbitration_id:03X}{msg.dlc:d}" + elif msg.is_fd: + fd_dlc = len2dlc(msg.dlc) + if msg.bitrate_switch: + if msg.is_extended_id: + sendStr = f"B{msg.arbitration_id:08X}{fd_dlc:X}" + else: + sendStr = f"b{msg.arbitration_id:03X}{fd_dlc:X}" + sendStr += msg.data.hex().upper() + else: + if msg.is_extended_id: + sendStr = f"D{msg.arbitration_id:08X}{fd_dlc:X}" + else: + sendStr = f"d{msg.arbitration_id:03X}{fd_dlc:X}" + sendStr += msg.data.hex().upper() else: if msg.is_extended_id: sendStr = f"T{msg.arbitration_id:08X}{msg.dlc:d}" diff --git a/test/test_slcan.py b/test/test_slcan.py index 220a6d7e0..491800e24 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -178,6 +178,264 @@ def test_send_extended_remote(self): rx_msg = self.bus.recv(TIMEOUT) self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + def test_recv_fd(self): + self.serial.set_input_buffer(b"d123A303132333435363738393a3b3c3d3e3f\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.is_fd, True) + self.assertEqual(msg.bitrate_switch, False) + self.assertEqual(msg.dlc, 16) + self.assertSequenceEqual( + msg.data, + [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) + + def test_send_fd(self): + payload = b"d123A303132333435363738393A3B3C3D3E3F\r" + msg = can.Message( + arbitration_id=0x123, + is_extended_id=False, + is_fd=True, + data=[ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) + self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + + def test_recv_fd_extended(self): + self.serial.set_input_buffer(b"D12ABCDEFA303132333435363738393A3B3C3D3E3F\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 16) + self.assertEqual(msg.bitrate_switch, False) + self.assertTrue(msg.is_fd) + self.assertSequenceEqual( + msg.data, + [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) + + def test_send_fd_extended(self): + payload = b"D12ABCDEFA303132333435363738393A3B3C3D3E3F\r" + msg = can.Message( + arbitration_id=0x12ABCDEF, + is_extended_id=True, + is_fd=True, + data=[ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) + self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + + def test_recv_fd_brs(self): + self.serial.set_input_buffer(b"b123A303132333435363738393a3b3c3d3e3f\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x123) + self.assertEqual(msg.is_extended_id, False) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.is_fd, True) + self.assertEqual(msg.bitrate_switch, True) + self.assertEqual(msg.dlc, 16) + self.assertSequenceEqual( + msg.data, + [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) + + def test_send_fd_brs(self): + payload = b"b123A303132333435363738393A3B3C3D3E3F\r" + msg = can.Message( + arbitration_id=0x123, + is_extended_id=False, + is_fd=True, + bitrate_switch=True, + data=[ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) + self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + + def test_recv_fd_brs_extended(self): + self.serial.set_input_buffer(b"B12ABCDEFA303132333435363738393A3B3C3D3E3F\r") + msg = self.bus.recv(TIMEOUT) + self.assertIsNotNone(msg) + self.assertEqual(msg.arbitration_id, 0x12ABCDEF) + self.assertEqual(msg.is_extended_id, True) + self.assertEqual(msg.is_remote_frame, False) + self.assertEqual(msg.dlc, 16) + self.assertEqual(msg.bitrate_switch, True) + self.assertTrue(msg.is_fd) + self.assertSequenceEqual( + msg.data, + [ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) + + def test_send_fd_brs_extended(self): + payload = b"B12ABCDEFA303132333435363738393A3B3C3D3E3F\r" + msg = can.Message( + arbitration_id=0x12ABCDEF, + is_extended_id=True, + is_fd=True, + bitrate_switch=True, + data=[ + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + ], + ) + self.bus.send(msg) + self.assertEqual(payload, self.serial.get_output_buffer()) + + self.serial.set_input_buffer(payload) + rx_msg = self.bus.recv(TIMEOUT) + self.assertTrue(msg.equals(rx_msg, timestamp_delta=None)) + def test_partial_recv(self): self.serial.set_input_buffer(b"T12ABCDEF") msg = self.bus.recv(TIMEOUT) From 32f07c3117cf9d3c3a20d7a91c271523144a0ad1 Mon Sep 17 00:00:00 2001 From: Greg Brooks Date: Tue, 22 Jul 2025 13:00:13 +0100 Subject: [PATCH 1199/1235] Convert BusState to enum when read with configparser (#1957) * Convert BusState to enum when read with configparser (#1956) * Move BusState conversion to util.py (#1956) * Check state argument type before attempting conversion (#1956) * Add tests (#1956) * Fix formatting (#1956) * Compare enums by identity (#1956) --------- Co-authored-by: Greg Brooks --- can/util.py | 6 ++++++ doc/interfaces/pcan.rst | 2 +- test/test_util.py | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/can/util.py b/can/util.py index 2f32dda8e..895c721ae 100644 --- a/can/util.py +++ b/can/util.py @@ -249,6 +249,12 @@ def _create_bus_config(config: dict[str, Any]) -> typechecking.BusConfig: if "fd" in config: config["fd"] = config["fd"] not in (0, False) + if "state" in config and not isinstance(config["state"], can.BusState): + try: + config["state"] = can.BusState[config["state"]] + except KeyError as e: + raise ValueError("State config not valid!") from e + return cast("typechecking.BusConfig", config) diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index 2f73dd3a7..48e7dba05 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -15,7 +15,7 @@ Here is an example configuration file for using `PCAN-USB None: From 7bc904e66a84e30e8fd669731c452fd8174118e3 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Wed, 23 Jul 2025 10:42:28 +0200 Subject: [PATCH 1200/1235] Improve can.io type annotations (#1951) --- .github/workflows/ci.yml | 2 +- can/_entry_points.py | 2 +- can/io/asc.py | 4 - can/io/blf.py | 46 +++---- can/io/canutils.py | 11 +- can/io/csv.py | 7 +- can/io/generic.py | 273 +++++++++++++++++++++++++++++---------- can/io/logger.py | 59 +++++---- can/io/player.py | 29 ++--- can/io/printer.py | 27 ++-- can/io/sqlite.py | 49 +++---- can/io/trc.py | 22 ++-- can/listener.py | 4 +- can/typechecking.py | 30 ++--- can/util.py | 4 +- doc/conf.py | 6 + doc/file_io.rst | 2 +- doc/internal-api.rst | 10 +- doc/notifier.rst | 3 +- test/logformats_test.py | 21 +-- 20 files changed, 373 insertions(+), 238 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 588d9a96b..ec5c1bbac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.13" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/can/_entry_points.py b/can/_entry_points.py index e8ce92d7c..6320b797b 100644 --- a/can/_entry_points.py +++ b/can/_entry_points.py @@ -30,5 +30,5 @@ def read_entry_points(group: str) -> list[_EntryPoint]: def read_entry_points(group: str) -> list[_EntryPoint]: return [ _EntryPoint(ep.name, *ep.value.split(":", maxsplit=1)) - for ep in entry_points().get(group, []) + for ep in entry_points().get(group, []) # pylint: disable=no-member ] diff --git a/can/io/asc.py b/can/io/asc.py index 0bea823fd..e917953ff 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -39,8 +39,6 @@ class ASCReader(TextIOMessageReader): bus statistics, J1939 Transport Protocol messages) is ignored. """ - file: TextIO - def __init__( self, file: Union[StringPathLike, TextIO], @@ -322,8 +320,6 @@ class ASCWriter(TextIOMessageWriter): It the first message does not have a timestamp, it is set to zero. """ - file: TextIO - FORMAT_MESSAGE = "{channel} {id:<15} {dir:<4} {dtype} {data}" FORMAT_MESSAGE_FD = " ".join( [ diff --git a/can/io/blf.py b/can/io/blf.py index 6a1231fcc..2c9050d54 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -17,14 +17,14 @@ import struct import time import zlib -from collections.abc import Generator +from collections.abc import Generator, Iterator from decimal import Decimal from typing import Any, BinaryIO, Optional, Union, cast from ..message import Message from ..typechecking import StringPathLike from ..util import channel2int, dlc2len, len2dlc -from .generic import BinaryIOMessageReader, FileIOMessageWriter +from .generic import BinaryIOMessageReader, BinaryIOMessageWriter TSystemTime = tuple[int, int, int, int, int, int, int, int] @@ -104,7 +104,7 @@ class BLFParseError(Exception): TIME_ONE_NANS_FACTOR = Decimal("1e-9") -def timestamp_to_systemtime(timestamp: float) -> TSystemTime: +def timestamp_to_systemtime(timestamp: Optional[float]) -> TSystemTime: if timestamp is None or timestamp < 631152000: # Probably not a Unix timestamp return 0, 0, 0, 0, 0, 0, 0, 0 @@ -146,8 +146,6 @@ class BLFReader(BinaryIOMessageReader): silently ignored. """ - file: BinaryIO - def __init__( self, file: Union[StringPathLike, BinaryIO], @@ -206,7 +204,7 @@ def __iter__(self) -> Generator[Message, None, None]: yield from self._parse_container(data) self.stop() - def _parse_container(self, data): + def _parse_container(self, data: bytes) -> Iterator[Message]: if self._tail: data = b"".join((self._tail, data)) try: @@ -217,7 +215,7 @@ def _parse_container(self, data): # Save the remaining data that could not be processed self._tail = data[self._pos :] - def _parse_data(self, data): + def _parse_data(self, data: bytes) -> Iterator[Message]: """Optimized inner loop by making local copies of global variables and class members and hardcoding some values.""" unpack_obj_header_base = OBJ_HEADER_BASE_STRUCT.unpack_from @@ -375,13 +373,11 @@ def _parse_data(self, data): pos = next_pos -class BLFWriter(FileIOMessageWriter): +class BLFWriter(BinaryIOMessageWriter): """ Logs CAN data to a Binary Logging File compatible with Vector's tools. """ - file: BinaryIO - #: Max log container size of uncompressed data max_container_size = 128 * 1024 @@ -412,14 +408,12 @@ def __init__( Z_DEFAULT_COMPRESSION represents a default compromise between speed and compression (currently equivalent to level 6). """ - mode = "rb+" if append else "wb" try: - super().__init__(file, mode=mode) + super().__init__(file, mode="rb+" if append else "wb") except FileNotFoundError: # Trying to append to a non-existing file, create a new one append = False - mode = "wb" - super().__init__(file, mode=mode) + super().__init__(file, mode="wb") assert self.file is not None self.channel = channel self.compression_level = compression_level @@ -452,7 +446,7 @@ def __init__( # Write a default header which will be updated when stopped self._write_header(FILE_HEADER_SIZE) - def _write_header(self, filesize): + def _write_header(self, filesize: int) -> None: header = [b"LOGG", FILE_HEADER_SIZE, self.application_id, 0, 0, 0, 2, 6, 8, 1] # The meaning of "count of objects read" is unknown header.extend([filesize, self.uncompressed_size, self.object_count, 0]) @@ -462,7 +456,7 @@ def _write_header(self, filesize): # Pad to header size self.file.write(b"\x00" * (FILE_HEADER_SIZE - FILE_HEADER_STRUCT.size)) - def on_message_received(self, msg): + def on_message_received(self, msg: Message) -> None: channel = channel2int(msg.channel) if channel is None: channel = self.channel @@ -514,7 +508,7 @@ def on_message_received(self, msg): data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, can_data) self._add_object(CAN_MESSAGE, data, msg.timestamp) - def log_event(self, text, timestamp=None): + def log_event(self, text: str, timestamp: Optional[float] = None) -> None: """Add an arbitrary message to the log file as a global marker. :param str text: @@ -525,17 +519,19 @@ def log_event(self, text, timestamp=None): """ try: # Only works on Windows - text = text.encode("mbcs") + encoded = text.encode("mbcs") except LookupError: - text = text.encode("ascii") + encoded = text.encode("ascii") comment = b"Added by python-can" marker = b"python-can" data = GLOBAL_MARKER_STRUCT.pack( - 0, 0xFFFFFF, 0xFF3300, 0, len(text), len(marker), len(comment) + 0, 0xFFFFFF, 0xFF3300, 0, len(encoded), len(marker), len(comment) ) - self._add_object(GLOBAL_MARKER, data + text + marker + comment, timestamp) + self._add_object(GLOBAL_MARKER, data + encoded + marker + comment, timestamp) - def _add_object(self, obj_type, data, timestamp=None): + def _add_object( + self, obj_type: int, data: bytes, timestamp: Optional[float] = None + ) -> None: if timestamp is None: timestamp = self.stop_timestamp or time.time() if self.start_timestamp is None: @@ -564,7 +560,7 @@ def _add_object(self, obj_type, data, timestamp=None): if self._buffer_size >= self.max_container_size: self._flush() - def _flush(self): + def _flush(self) -> None: """Compresses and writes data in the buffer to file.""" if self.file.closed: return @@ -578,7 +574,7 @@ def _flush(self): self._buffer = [tail] self._buffer_size = len(tail) if not self.compression_level: - data = uncompressed_data + data: "Union[bytes, memoryview[int]]" = uncompressed_data # noqa: UP037 method = NO_COMPRESSION else: data = zlib.compress(uncompressed_data, self.compression_level) @@ -601,7 +597,7 @@ def file_size(self) -> int: """Return an estimate of the current file size in bytes.""" return self.file.tell() + self._buffer_size - def stop(self): + def stop(self) -> None: """Stops logging and closes the file.""" self._flush() if self.file.seekable(): diff --git a/can/io/canutils.py b/can/io/canutils.py index e83c21926..78d081637 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -6,7 +6,7 @@ import logging from collections.abc import Generator -from typing import Any, TextIO, Union +from typing import Any, Optional, TextIO, Union from can.message import Message @@ -34,8 +34,6 @@ class CanutilsLogReader(TextIOMessageReader): ``(0.0) vcan0 001#8d00100100820100`` """ - file: TextIO - def __init__( self, file: Union[StringPathLike, TextIO], @@ -148,13 +146,12 @@ def __init__( :param bool append: if set to `True` messages are appended to the file, else the file is truncated """ - mode = "a" if append else "w" - super().__init__(file, mode=mode) + super().__init__(file, mode="a" if append else "w") self.channel = channel - self.last_timestamp = None + self.last_timestamp: Optional[float] = None - def on_message_received(self, msg): + def on_message_received(self, msg: Message) -> None: # this is the case for the very first message: if self.last_timestamp is None: self.last_timestamp = msg.timestamp or 0.0 diff --git a/can/io/csv.py b/can/io/csv.py index dcc7996f7..865ef9af0 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -28,8 +28,6 @@ class CSVReader(TextIOMessageReader): Any line separator is accepted. """ - file: TextIO - def __init__( self, file: Union[StringPathLike, TextIO], @@ -89,8 +87,6 @@ class CSVWriter(TextIOMessageWriter): Each line is terminated with a platform specific line separator. """ - file: TextIO - def __init__( self, file: Union[StringPathLike, TextIO], @@ -106,8 +102,7 @@ def __init__( the file is truncated and starts with a newly written header line """ - mode = "a" if append else "w" - super().__init__(file, mode=mode) + super().__init__(file, mode="a" if append else "w") # Write a header row if not append: diff --git a/can/io/generic.py b/can/io/generic.py index 82523c3cd..21fc3e8e8 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -1,129 +1,260 @@ -"""Contains generic base classes for file IO.""" +"""This module provides abstract base classes for CAN message reading and writing operations +to various file formats. + +.. note:: + All classes in this module are abstract and should be subclassed to implement + specific file format handling. +""" -import gzip import locale -from abc import ABCMeta +import os +from abc import ABC, abstractmethod from collections.abc import Iterable from contextlib import AbstractContextManager +from io import BufferedIOBase, TextIOWrapper +from pathlib import Path from types import TracebackType from typing import ( + TYPE_CHECKING, Any, BinaryIO, + Generic, Literal, Optional, TextIO, + TypeVar, Union, - cast, ) from typing_extensions import Self -from .. import typechecking from ..listener import Listener from ..message import Message +from ..typechecking import FileLike, StringPathLike +if TYPE_CHECKING: + from _typeshed import ( + OpenBinaryModeReading, + OpenBinaryModeUpdating, + OpenBinaryModeWriting, + OpenTextModeReading, + OpenTextModeUpdating, + OpenTextModeWriting, + ) -class BaseIOHandler(AbstractContextManager): - """A generic file handler that can be used for reading and writing. - Can be used as a context manager. +#: type parameter used in generic classes :class:`MessageReader` and :class:`MessageWriter` +_IoTypeVar = TypeVar("_IoTypeVar", bound=FileLike) - :attr file: - the file-like object that is kept internally, or `None` if none - was opened - """ - file: Optional[typechecking.FileLike] +class MessageWriter(AbstractContextManager["MessageWriter"], Listener, ABC): + """Abstract base class for all CAN message writers. - def __init__( - self, - file: Optional[typechecking.AcceptedIOType], - mode: str = "rt", - **kwargs: Any, - ) -> 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 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 = cast("Optional[typechecking.FileLike]", file) - else: - encoding: Optional[str] = ( - None - if "b" in mode - else kwargs.get("encoding", locale.getpreferredencoding(False)) - ) - # pylint: disable=consider-using-with - # file is some path-like object - self.file = cast( - "typechecking.FileLike", open(file, mode, encoding=encoding) - ) + This class serves as a foundation for implementing different message writer formats. + It combines context manager capabilities with the message listener interface. - # for multiple inheritance - super().__init__() + :param file: Path-like object or string representing the output file location + :param kwargs: Additional keyword arguments for specific writer implementations + """ + + @abstractmethod + def __init__(self, file: StringPathLike, **kwargs: Any) -> None: + pass + + @abstractmethod + def stop(self) -> None: + """Stop handling messages and cleanup any resources.""" def __enter__(self) -> Self: + """Enter the context manager.""" return self def __exit__( self, exc_type: Optional[type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], ) -> Literal[False]: + """Exit the context manager and ensure proper cleanup.""" self.stop() return False + +class SizedMessageWriter(MessageWriter, ABC): + """Abstract base class for message writers that can report their file size. + + This class extends :class:`MessageWriter` with the ability to determine the size + of the output file. + """ + + @abstractmethod + def file_size(self) -> int: + """Get the current size of the output file in bytes. + + :return: The size of the file in bytes + :rtype: int + """ + + +class FileIOMessageWriter(SizedMessageWriter, Generic[_IoTypeVar]): + """Base class for writers that operate on file descriptors. + + This class provides common functionality for writers that work with file objects. + + :param file: A path-like object or file object to write to + :param kwargs: Additional keyword arguments for specific writer implementations + + :ivar file: The file object being written to + """ + + file: _IoTypeVar + + @abstractmethod + def __init__(self, file: Union[StringPathLike, _IoTypeVar], **kwargs: Any) -> None: + pass + def stop(self) -> None: - """Closes the underlying 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() + """Close the file and stop writing.""" + self.file.close() + def file_size(self) -> int: + """Get the current file size.""" + return self.file.tell() -class MessageWriter(BaseIOHandler, Listener, metaclass=ABCMeta): - """The base class for all writers.""" - file: Optional[typechecking.FileLike] +class TextIOMessageWriter(FileIOMessageWriter[Union[TextIO, TextIOWrapper]], ABC): + """Text-based message writer implementation. + :param file: Text file to write to + :param mode: File open mode for text operations + :param kwargs: Additional arguments like encoding + """ + + def __init__( + self, + file: Union[StringPathLike, TextIO, TextIOWrapper], + mode: "Union[OpenTextModeUpdating, OpenTextModeWriting]" = "w", + **kwargs: Any, + ) -> None: + if isinstance(file, (str, os.PathLike)): + encoding: str = kwargs.get("encoding", locale.getpreferredencoding(False)) + # pylint: disable=consider-using-with + self.file = Path(file).open(mode=mode, encoding=encoding) + else: + self.file = file -class FileIOMessageWriter(MessageWriter, metaclass=ABCMeta): - """A specialized base class for all writers with file descriptors.""" - file: typechecking.FileLike +class BinaryIOMessageWriter(FileIOMessageWriter[Union[BinaryIO, BufferedIOBase]], ABC): + """Binary file message writer implementation. + + :param file: Binary file to write to + :param mode: File open mode for binary operations + :param kwargs: Additional implementation specific arguments + """ def __init__( - self, file: typechecking.AcceptedIOType, mode: str = "wt", **kwargs: Any + self, + file: Union[StringPathLike, BinaryIO, BufferedIOBase], + mode: "Union[OpenBinaryModeUpdating, OpenBinaryModeWriting]" = "wb", + **kwargs: Any, ) -> None: - # Not possible with the type signature, but be verbose for user-friendliness - if file is None: - raise ValueError("The given file cannot be None") + if isinstance(file, (str, os.PathLike)): + # pylint: disable=consider-using-with,unspecified-encoding + self.file = Path(file).open(mode=mode) + else: + self.file = file - super().__init__(file, mode, **kwargs) - def file_size(self) -> int: - """Return an estimate of the current file size in bytes.""" - return self.file.tell() +class MessageReader(AbstractContextManager["MessageReader"], Iterable[Message], ABC): + """Abstract base class for all CAN message readers. + + This class serves as a foundation for implementing different message reader formats. + It combines context manager capabilities with iteration interface. + + :param file: Path-like object or string representing the input file location + :param kwargs: Additional keyword arguments for specific reader implementations + """ + + @abstractmethod + def __init__(self, file: StringPathLike, **kwargs: Any) -> None: + pass + + @abstractmethod + def stop(self) -> None: + """Stop reading messages and cleanup any resources.""" + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: Optional[type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> Literal[False]: + self.stop() + return False -class TextIOMessageWriter(FileIOMessageWriter, metaclass=ABCMeta): - file: TextIO +class FileIOMessageReader(MessageReader, Generic[_IoTypeVar]): + """Base class for readers that operate on file descriptors. + This class provides common functionality for readers that work with file objects. -class BinaryIOMessageWriter(FileIOMessageWriter, metaclass=ABCMeta): - file: Union[BinaryIO, gzip.GzipFile] + :param file: A path-like object or file object to read from + :param kwargs: Additional keyword arguments for specific reader implementations + :ivar file: The file object being read from + """ + + file: _IoTypeVar + + @abstractmethod + def __init__(self, file: Union[StringPathLike, _IoTypeVar], **kwargs: Any) -> None: + pass -class MessageReader(BaseIOHandler, Iterable[Message], metaclass=ABCMeta): - """The base class for all readers.""" + def stop(self) -> None: + self.file.close() -class TextIOMessageReader(MessageReader, metaclass=ABCMeta): - file: TextIO +class TextIOMessageReader(FileIOMessageReader[Union[TextIO, TextIOWrapper]], ABC): + """Text-based message reader implementation. + :param file: Text file to read from + :param mode: File open mode for text operations + :param kwargs: Additional arguments like encoding + """ -class BinaryIOMessageReader(MessageReader, metaclass=ABCMeta): - file: Union[BinaryIO, gzip.GzipFile] + def __init__( + self, + file: Union[StringPathLike, TextIO, TextIOWrapper], + mode: "OpenTextModeReading" = "r", + **kwargs: Any, + ) -> None: + if isinstance(file, (str, os.PathLike)): + encoding: str = kwargs.get("encoding", locale.getpreferredencoding(False)) + # pylint: disable=consider-using-with + self.file = Path(file).open(mode=mode, encoding=encoding) + else: + self.file = file + + +class BinaryIOMessageReader(FileIOMessageReader[Union[BinaryIO, BufferedIOBase]], ABC): + """Binary file message reader implementation. + + :param file: Binary file to read from + :param mode: File open mode for binary operations + :param kwargs: Additional implementation specific arguments + """ + + def __init__( + self, + file: Union[StringPathLike, BinaryIO, BufferedIOBase], + mode: "OpenBinaryModeReading" = "rb", + **kwargs: Any, + ) -> None: + if isinstance(file, (str, os.PathLike)): + # pylint: disable=consider-using-with,unspecified-encoding + self.file = Path(file).open(mode=mode) + else: + self.file = file diff --git a/can/io/logger.py b/can/io/logger.py index f9f029759..9febfe680 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -15,14 +15,13 @@ Final, Literal, Optional, - cast, ) from typing_extensions import Self from .._entry_points import read_entry_points from ..message import Message -from ..typechecking import AcceptedIOType, FileLike, StringPathLike +from ..typechecking import StringPathLike from .asc import ASCWriter from .blf import BLFWriter from .canutils import CanutilsLogWriter @@ -31,6 +30,8 @@ BinaryIOMessageWriter, FileIOMessageWriter, MessageWriter, + SizedMessageWriter, + TextIOMessageWriter, ) from .mf4 import MF4Writer from .printer import Printer @@ -71,9 +72,7 @@ def _get_logger_for_suffix(suffix: str) -> type[MessageWriter]: ) from None -def _compress( - filename: StringPathLike, **kwargs: Any -) -> tuple[type[MessageWriter], FileLike]: +def _compress(filename: StringPathLike, **kwargs: Any) -> FileIOMessageWriter[Any]: """ Return the suffix and io object of the decompressed file. File will automatically recompress upon close. @@ -93,11 +92,18 @@ def _compress( append = kwargs.get("append", False) if issubclass(logger_type, BinaryIOMessageWriter): - mode = "ab" if append else "wb" - else: - mode = "at" if append else "wt" + return logger_type( + file=gzip.open(filename=filename, mode="ab" if append else "wb"), **kwargs + ) + + elif issubclass(logger_type, TextIOMessageWriter): + return logger_type( + file=gzip.open(filename=filename, mode="at" if append else "wt"), **kwargs + ) - return logger_type, gzip.open(filename, mode) + raise ValueError( + f"The file type {real_suffix} is currently incompatible with gzip." + ) def Logger( # noqa: N802 @@ -143,12 +149,11 @@ def Logger( # noqa: N802 _update_writer_plugins() suffix = pathlib.PurePath(filename).suffix.lower() - file_or_filename: AcceptedIOType = filename if suffix == ".gz": - logger_type, file_or_filename = _compress(filename, **kwargs) - else: - logger_type = _get_logger_for_suffix(suffix) - return logger_type(file=file_or_filename, **kwargs) + return _compress(filename, **kwargs) + + logger_type = _get_logger_for_suffix(suffix) + return logger_type(file=filename, **kwargs) class BaseRotatingLogger(MessageWriter, ABC): @@ -183,13 +188,11 @@ class BaseRotatingLogger(MessageWriter, ABC): rollover_count: int = 0 def __init__(self, **kwargs: Any) -> None: - super().__init__(**{**kwargs, "file": None}) - self.writer_kwargs = kwargs @property @abstractmethod - def writer(self) -> FileIOMessageWriter: + def writer(self) -> MessageWriter: """This attribute holds an instance of a writer class which manages the actual file IO.""" raise NotImplementedError @@ -243,7 +246,7 @@ def on_message_received(self, msg: Message) -> None: self.writer.on_message_received(msg) - def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: + def _get_new_writer(self, filename: StringPathLike) -> MessageWriter: """Instantiate a new writer. .. note:: @@ -261,10 +264,7 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: if suffix not in self._supported_formats: continue logger = Logger(filename=filename, **self.writer_kwargs) - if isinstance(logger, FileIOMessageWriter): - return logger - elif isinstance(logger, Printer) and logger.file is not None: - return cast("FileIOMessageWriter", logger) + return logger raise ValueError( f'The log format of "{pathlib.Path(filename).name}" ' @@ -366,18 +366,25 @@ def __init__( self._writer = self._get_new_writer(self.base_filename) + def _get_new_writer(self, filename: StringPathLike) -> SizedMessageWriter: + writer = super()._get_new_writer(filename) + if isinstance(writer, SizedMessageWriter): + return writer + raise TypeError + @property - def writer(self) -> FileIOMessageWriter: + def writer(self) -> SizedMessageWriter: return self._writer def should_rollover(self, msg: Message) -> bool: if self.max_bytes <= 0: return False - if self.writer.file_size() >= self.max_bytes: - return True + file_size = self.writer.file_size() + if file_size is None: + return False - return False + return file_size >= self.max_bytes def do_rollover(self) -> None: if self.writer: diff --git a/can/io/player.py b/can/io/player.py index 2451eab41..c0015b185 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -11,17 +11,16 @@ from typing import ( Any, Final, - Union, ) from .._entry_points import read_entry_points from ..message import Message -from ..typechecking import AcceptedIOType, FileLike, StringPathLike +from ..typechecking import StringPathLike from .asc import ASCReader from .blf import BLFReader from .canutils import CanutilsLogReader from .csv import CSVReader -from .generic import BinaryIOMessageReader, MessageReader +from .generic import BinaryIOMessageReader, MessageReader, TextIOMessageReader from .mf4 import MF4Reader from .sqlite import SqliteReader from .trc import TRCReader @@ -58,24 +57,25 @@ def _get_logger_for_suffix(suffix: str) -> type[MessageReader]: raise ValueError(f'No read support for unknown log format "{suffix}"') from None -def _decompress( - filename: StringPathLike, -) -> tuple[type[MessageReader], Union[str, FileLike]]: +def _decompress(filename: StringPathLike, **kwargs: Any) -> MessageReader: """ Return the suffix and io object of the decompressed file. """ suffixes = pathlib.Path(filename).suffixes if len(suffixes) != 2: raise ValueError( - f"No write support for unknown log format \"{''.join(suffixes)}\"" - ) from None + f"No read support for unknown log format \"{''.join(suffixes)}\"" + ) real_suffix = suffixes[-2].lower() reader_type = _get_logger_for_suffix(real_suffix) - mode = "rb" if issubclass(reader_type, BinaryIOMessageReader) else "rt" + if issubclass(reader_type, TextIOMessageReader): + return reader_type(gzip.open(filename, mode="rt"), **kwargs) + elif issubclass(reader_type, BinaryIOMessageReader): + return reader_type(gzip.open(filename, mode="rb"), **kwargs) - return reader_type, gzip.open(filename, mode) + raise ValueError(f"No read support for unknown log format \"{''.join(suffixes)}\"") def LogReader(filename: StringPathLike, **kwargs: Any) -> MessageReader: # noqa: N802 @@ -118,12 +118,11 @@ def LogReader(filename: StringPathLike, **kwargs: Any) -> MessageReader: # noqa _update_reader_plugins() suffix = pathlib.PurePath(filename).suffix.lower() - file_or_filename: AcceptedIOType = filename if suffix == ".gz": - reader_type, file_or_filename = _decompress(filename) - else: - reader_type = _get_logger_for_suffix(suffix) - return reader_type(file=file_or_filename, **kwargs) + return _decompress(filename) + + reader_type = _get_logger_for_suffix(suffix) + return reader_type(file=filename, **kwargs) class MessageSync: diff --git a/can/io/printer.py b/can/io/printer.py index 30bc227ab..786cb7261 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -3,16 +3,18 @@ """ import logging -from typing import Any, Optional, TextIO, Union, cast +import sys +from io import TextIOWrapper +from typing import Any, TextIO, Union from ..message import Message from ..typechecking import StringPathLike -from .generic import MessageWriter +from .generic import TextIOMessageWriter log = logging.getLogger("can.io.printer") -class Printer(MessageWriter): +class Printer(TextIOMessageWriter): """ The Printer class is a subclass of :class:`~can.Listener` which simply prints any messages it receives to the terminal (stdout). A message is turned into a @@ -22,11 +24,9 @@ class Printer(MessageWriter): standard out """ - file: Optional[TextIO] - def __init__( self, - file: Optional[Union[StringPathLike, TextIO]] = None, + file: Union[StringPathLike, TextIO, TextIOWrapper] = sys.stdout, append: bool = False, **kwargs: Any, ) -> None: @@ -38,18 +38,17 @@ def __init__( :param append: If set to `True` messages, are appended to the file, else the file is truncated """ - self.write_to_file = file is not None - mode = "a" if append else "w" - super().__init__(file, mode=mode) + super().__init__(file, mode="a" if append else "w") def on_message_received(self, msg: Message) -> None: - if self.write_to_file: - cast("TextIO", self.file).write(str(msg) + "\n") - else: - print(msg) # noqa: T201 + self.file.write(str(msg) + "\n") def file_size(self) -> int: """Return an estimate of the current file size in bytes.""" - if self.file is not None: + if self.file is not sys.stdout: return self.file.tell() return 0 + + def stop(self) -> None: + if self.file is not sys.stdout: + super().stop() diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 686e2d038..73aa2961c 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -8,9 +8,11 @@ import sqlite3 import threading import time -from collections.abc import Generator +from collections.abc import Generator, Iterator from typing import Any +from typing_extensions import TypeAlias + from can.listener import BufferedReader from can.message import Message @@ -19,6 +21,8 @@ log = logging.getLogger("can.io.sqlite") +_MessageTuple: TypeAlias = "tuple[float, int, bool, bool, bool, int, memoryview[int]]" + class SqliteReader(MessageReader): """ @@ -49,7 +53,6 @@ def __init__( do not accept file-like objects as the `file` parameter. It also runs in ``append=True`` mode all the time. """ - super().__init__(file=None) self._conn = sqlite3.connect(file) self._cursor = self._conn.cursor() self.table_name = table_name @@ -59,7 +62,7 @@ def __iter__(self) -> Generator[Message, None, None]: yield SqliteReader._assemble_message(frame_data) @staticmethod - def _assemble_message(frame_data): + def _assemble_message(frame_data: _MessageTuple) -> Message: timestamp, can_id, is_extended, is_remote, is_error, dlc, data = frame_data return Message( timestamp=timestamp, @@ -71,12 +74,12 @@ def _assemble_message(frame_data): data=data, ) - def __len__(self): + def __len__(self) -> int: # this might not run in constant time result = self._cursor.execute(f"SELECT COUNT(*) FROM {self.table_name}") return int(result.fetchone()[0]) - def read_all(self): + def read_all(self) -> Iterator[Message]: """Fetches all messages in the database. :rtype: Generator[can.Message] @@ -84,9 +87,8 @@ def read_all(self): result = self._cursor.execute(f"SELECT * FROM {self.table_name}").fetchall() return (SqliteReader._assemble_message(frame) for frame in result) - def stop(self): + def stop(self) -> None: """Closes the connection to the database.""" - super().stop() self._conn.close() @@ -154,11 +156,10 @@ def __init__( f"The append argument should not be used in " f"conjunction with the {self.__class__.__name__}." ) - super().__init__(file=None) + BufferedReader.__init__(self) self.table_name = table_name self._db_filename = file self._stop_running_event = threading.Event() - self._conn = None self._writer_thread = threading.Thread(target=self._db_writer_thread) self._writer_thread.start() self.num_frames = 0 @@ -167,7 +168,8 @@ def __init__( f"INSERT INTO {self.table_name} VALUES (?, ?, ?, ?, ?, ?, ?)" ) - def _create_db(self): + @staticmethod + def _create_db(file: StringPathLike, table_name: str) -> sqlite3.Connection: """Creates a new databae or opens a connection to an existing one. .. note:: @@ -175,11 +177,11 @@ def _create_db(self): hence we setup the db here. It has the upside of running async. """ log.debug("Creating sqlite database") - self._conn = sqlite3.connect(self._db_filename) + conn = sqlite3.connect(file) # create table structure - self._conn.cursor().execute( - f"""CREATE TABLE IF NOT EXISTS {self.table_name} + conn.cursor().execute( + f"""CREATE TABLE IF NOT EXISTS {table_name} ( ts REAL, arbitration_id INTEGER, @@ -190,14 +192,16 @@ def _create_db(self): data BLOB )""" ) - self._conn.commit() + conn.commit() + + return conn - def _db_writer_thread(self): - self._create_db() + def _db_writer_thread(self) -> None: + conn = SqliteWriter._create_db(self._db_filename, self.table_name) try: while True: - messages = [] # reset buffer + messages: list[_MessageTuple] = [] # reset buffer msg = self.get_message(self.GET_MESSAGE_TIMEOUT) while msg is not None: @@ -226,10 +230,10 @@ def _db_writer_thread(self): count = len(messages) if count > 0: - with self._conn: + with conn: # log.debug("Writing %d frames to db", count) - self._conn.executemany(self._insert_template, messages) - self._conn.commit() # make the changes visible to the entire database + conn.executemany(self._insert_template, messages) + conn.commit() # make the changes visible to the entire database self.num_frames += count self.last_write = time.time() @@ -238,14 +242,13 @@ def _db_writer_thread(self): break finally: - self._conn.close() + conn.close() log.info("Stopped sqlite writer after writing %d messages", self.num_frames) - def stop(self): + def stop(self) -> None: """Stops the reader an writes all remaining messages to the database. Thus, this might take a while and block. """ BufferedReader.stop(self) self._stop_running_event.set() self._writer_thread.join() - MessageReader.stop(self) diff --git a/can/io/trc.py b/can/io/trc.py index e1eaa077c..fa8ee88e7 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -12,6 +12,7 @@ from collections.abc import Generator from datetime import datetime, timedelta, timezone from enum import Enum +from io import TextIOWrapper from typing import Any, Callable, Optional, TextIO, Union from ..message import Message @@ -31,8 +32,8 @@ class TRCFileVersion(Enum): V2_0 = 200 V2_1 = 201 - def __ge__(self, other): - if self.__class__ is other.__class__: + def __ge__(self, other: Any) -> bool: + if isinstance(other, TRCFileVersion): return self.value >= other.value return NotImplemented @@ -42,8 +43,6 @@ class TRCReader(TextIOMessageReader): Iterator of CAN messages from a TRC logging file. """ - file: TextIO - def __init__( self, file: Union[StringPathLike, TextIO], @@ -73,7 +72,7 @@ def start_time(self) -> Optional[datetime]: return datetime.fromtimestamp(self._start_time, timezone.utc) return None - def _extract_header(self): + def _extract_header(self) -> str: line = "" for _line in self.file: line = _line.strip() @@ -286,9 +285,6 @@ class TRCWriter(TextIOMessageWriter): If the first message does not have a timestamp, it is set to zero. """ - file: TextIO - first_timestamp: Optional[float] - FORMAT_MESSAGE = ( "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" ) @@ -296,7 +292,7 @@ class TRCWriter(TextIOMessageWriter): def __init__( self, - file: Union[StringPathLike, TextIO], + file: Union[StringPathLike, TextIO, TextIOWrapper], channel: int = 1, **kwargs: Any, ) -> None: @@ -318,7 +314,7 @@ def __init__( self.filepath = os.path.abspath(self.file.name) self.header_written = False self.msgnr = 0 - self.first_timestamp = None + self.first_timestamp: Optional[float] = None self.file_version = TRCFileVersion.V2_1 self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 self._format_message = self._format_message_init @@ -370,7 +366,7 @@ def _write_header_v2_1(self, start_time: datetime) -> None: ] self.file.writelines(line + "\n" for line in lines) - def _format_message_by_format(self, msg, channel): + def _format_message_by_format(self, msg: Message, channel: int) -> str: if msg.is_extended_id: arb_id = f"{msg.arbitration_id:07X}" else: @@ -378,6 +374,8 @@ def _format_message_by_format(self, msg, channel): data = [f"{byte:02X}" for byte in msg.data] + if self.first_timestamp is None: + raise ValueError serialized = self._msg_fmt_string.format( msgnr=self.msgnr, time=(msg.timestamp - self.first_timestamp) * 1000, @@ -389,7 +387,7 @@ def _format_message_by_format(self, msg, channel): ) return serialized - def _format_message_init(self, msg, channel): + def _format_message_init(self, msg: Message, channel: int) -> str: if self.file_version == TRCFileVersion.V1_0: self._format_message = self._format_message_by_format self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 diff --git a/can/listener.py b/can/listener.py index b450cf36d..6256d33b6 100644 --- a/can/listener.py +++ b/can/listener.py @@ -147,7 +147,9 @@ def __init__(self, **kwargs: Any) -> None: stacklevel=2, ) if sys.version_info < (3, 10): - self.buffer = asyncio.Queue(loop=kwargs["loop"]) + self.buffer = asyncio.Queue( # pylint: disable=unexpected-keyword-arg + loop=kwargs["loop"] + ) return self.buffer = asyncio.Queue() diff --git a/can/typechecking.py b/can/typechecking.py index 36343ddaa..fc0c87c0d 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -1,9 +1,9 @@ """Types for mypy type-checking""" -import gzip -import struct +import io import sys -import typing +from collections.abc import Iterable, Sequence +from typing import IO, TYPE_CHECKING, Any, NewType, Union if sys.version_info >= (3, 10): from typing import TypeAlias @@ -16,8 +16,9 @@ from typing_extensions import TypedDict -if typing.TYPE_CHECKING: +if TYPE_CHECKING: import os + import struct class CanFilter(TypedDict): @@ -31,32 +32,31 @@ class CanFilterExtended(TypedDict): extended: bool -CanFilters = typing.Sequence[typing.Union[CanFilter, CanFilterExtended]] +CanFilters = Sequence[Union[CanFilter, CanFilterExtended]] # 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]] +CanData = Union[bytes, bytearray, int, Iterable[int]] # Used for the Abstract Base Class ChannelStr = str ChannelInt = int -Channel = typing.Union[ChannelInt, ChannelStr] +Channel = Union[ChannelInt, ChannelStr, Sequence[ChannelInt]] # Used by the IO module -FileLike = typing.Union[typing.TextIO, typing.BinaryIO, gzip.GzipFile] -StringPathLike = typing.Union[str, "os.PathLike[str]"] -AcceptedIOType = typing.Union[FileLike, StringPathLike] +FileLike = Union[IO[Any], io.TextIOWrapper, io.BufferedIOBase] +StringPathLike = Union[str, "os.PathLike[str]"] -BusConfig = typing.NewType("BusConfig", dict[str, typing.Any]) +BusConfig = NewType("BusConfig", dict[str, Any]) # Used by CLI scripts -TAdditionalCliArgs: TypeAlias = dict[str, typing.Union[str, int, float, bool]] +TAdditionalCliArgs: TypeAlias = dict[str, Union[str, int, float, bool]] TDataStructs: TypeAlias = dict[ - typing.Union[int, tuple[int, ...]], - typing.Union[struct.Struct, tuple, None], + Union[int, tuple[int, ...]], + "Union[struct.Struct, tuple[struct.Struct, *tuple[float, ...]]]", ] @@ -65,7 +65,7 @@ class AutoDetectedConfig(TypedDict): channel: Channel -ReadableBytesLike = typing.Union[bytes, bytearray, memoryview] +ReadableBytesLike = Union[bytes, bytearray, memoryview] class BitTimingDict(TypedDict): diff --git a/can/util.py b/can/util.py index 895c721ae..584b7dfa9 100644 --- a/can/util.py +++ b/can/util.py @@ -50,7 +50,7 @@ def load_file_config( - path: Optional[typechecking.AcceptedIOType] = None, section: str = "default" + path: Optional[typechecking.StringPathLike] = None, section: str = "default" ) -> dict[str, str]: """ Loads configuration from file with following content:: @@ -120,7 +120,7 @@ def load_environment_config(context: Optional[str] = None) -> dict[str, str]: def load_config( - path: Optional[typechecking.AcceptedIOType] = None, + path: Optional[typechecking.StringPathLike] = None, config: Optional[dict[str, Any]] = None, context: Optional[str] = None, ) -> typechecking.BusConfig: diff --git a/doc/conf.py b/doc/conf.py index f4a9ab95f..7ae0480d5 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -122,6 +122,12 @@ # disable specific warnings nitpick_ignore = [ # Ignore warnings for type aliases. Remove once Sphinx supports PEP613 + ("py:class", "OpenTextModeUpdating"), + ("py:class", "OpenTextModeWriting"), + ("py:class", "OpenBinaryModeUpdating"), + ("py:class", "OpenBinaryModeWriting"), + ("py:class", "OpenTextModeReading"), + ("py:class", "OpenBinaryModeReading"), ("py:class", "BusConfig"), ("py:class", "can.typechecking.BusConfig"), ("py:class", "can.typechecking.CanFilter"), diff --git a/doc/file_io.rst b/doc/file_io.rst index ff9431695..329ccac53 100644 --- a/doc/file_io.rst +++ b/doc/file_io.rst @@ -94,7 +94,7 @@ as further references can-utils can be used: Log (.log can-utils Logging format) ----------------------------------- -CanutilsLogWriter logs CAN data to an ASCII log file compatible with `can-utils ` +CanutilsLogWriter logs CAN data to an ASCII log file compatible with `can-utils `_ As specification following references can-utils can be used: `asc2log `_, `log2asc `_. diff --git a/doc/internal-api.rst b/doc/internal-api.rst index 73984bf1a..9e544f052 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -90,8 +90,8 @@ About the IO module Handling of the different file formats is implemented in ``can.io``. Each file/IO type is within a separate module and ideally implements both a *Reader* and a *Writer*. -The reader usually extends :class:`can.io.generic.BaseIOHandler`, while -the writer often additionally extends :class:`can.Listener`, +The reader extends :class:`can.io.generic.MessageReader`, while the writer extends +:class:`can.io.generic.MessageWriter`, a subclass of the :class:`can.Listener`, to be able to be passed directly to a :class:`can.Notifier`. @@ -104,9 +104,9 @@ Ideally add both reading and writing support for the new file format, although t 1. Create a new module: *can/io/canstore.py* (*or* simply copy some existing one like *can/io/csv.py*) -2. Implement a reader ``CanstoreReader`` (which often extends :class:`can.io.generic.BaseIOHandler`, but does not have to). +2. Implement a reader ``CanstoreReader`` which extends :class:`can.io.generic.MessageReader`. Besides from a constructor, only ``__iter__(self)`` needs to be implemented. -3. Implement a writer ``CanstoreWriter`` (which often extends :class:`can.io.generic.BaseIOHandler` and :class:`can.Listener`, but does not have to). +3. Implement a writer ``CanstoreWriter`` which extends :class:`can.io.generic.MessageWriter`. Besides from a constructor, only ``on_message_received(self, msg)`` needs to be implemented. 4. Add a case to ``can.io.player.LogReader``'s ``__new__()``. 5. Document the two new classes (and possibly additional helpers) with docstrings and comments. @@ -126,7 +126,9 @@ IO Utilities .. automodule:: can.io.generic + :show-inheritance: :members: + :private-members: :member-order: bysource diff --git a/doc/notifier.rst b/doc/notifier.rst index 05edbd90d..e1b160a6e 100644 --- a/doc/notifier.rst +++ b/doc/notifier.rst @@ -58,7 +58,8 @@ readers are also documented here. be added using the ``can.io.message_writer`` entry point. The format of the entry point is ``reader_name=module:classname`` where ``classname`` - is a :class:`can.io.generic.BaseIOHandler` concrete implementation. + is a concrete implementation of :class:`~can.io.generic.MessageReader` or + :class:`~can.io.generic.MessageWriter`. :: diff --git a/test/logformats_test.py b/test/logformats_test.py index e9db47af3..f4bd1191f 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -137,6 +137,7 @@ def _setup_instance_helper( allowed_timestamp_delta=0.0, preserves_channel=True, adds_default_channel=None, + assert_file_closed=True, ): """ :param Callable writer_constructor: the constructor of the writer class @@ -187,6 +188,7 @@ def _setup_instance_helper( self.reader_constructor = reader_constructor self.binary_file = binary_file self.test_append_enabled = test_append + self.assert_file_closed = assert_file_closed ComparingMessagesTestCase.__init__( self, @@ -212,7 +214,7 @@ def test_path_like_explicit_stop(self): self._write_all(writer) self._ensure_fsync(writer) writer.stop() - if hasattr(writer.file, "closed"): + if self.assert_file_closed: self.assertTrue(writer.file.closed) print("reading all messages") @@ -220,7 +222,7 @@ def test_path_like_explicit_stop(self): read_messages = list(reader) # redundant, but this checks if stop() can be called multiple times reader.stop() - if hasattr(writer.file, "closed"): + if self.assert_file_closed: self.assertTrue(writer.file.closed) # check if at least the number of messages matches @@ -243,7 +245,7 @@ def test_path_like_context_manager(self): self._write_all(writer) self._ensure_fsync(writer) w = writer - if hasattr(w.file, "closed"): + if self.assert_file_closed: self.assertTrue(w.file.closed) # read all written messages @@ -251,7 +253,7 @@ def test_path_like_context_manager(self): with self.reader_constructor(self.test_file_name) as reader: read_messages = list(reader) r = reader - if hasattr(r.file, "closed"): + if self.assert_file_closed: self.assertTrue(r.file.closed) # check if at least the number of messages matches; @@ -274,7 +276,7 @@ def test_file_like_explicit_stop(self): self._write_all(writer) self._ensure_fsync(writer) writer.stop() - if hasattr(my_file, "closed"): + if self.assert_file_closed: self.assertTrue(my_file.closed) print("reading all messages") @@ -283,7 +285,7 @@ def test_file_like_explicit_stop(self): read_messages = list(reader) # redundant, but this checks if stop() can be called multiple times reader.stop() - if hasattr(my_file, "closed"): + if self.assert_file_closed: self.assertTrue(my_file.closed) # check if at least the number of messages matches @@ -307,7 +309,7 @@ def test_file_like_context_manager(self): self._write_all(writer) self._ensure_fsync(writer) w = writer - if hasattr(my_file, "closed"): + if self.assert_file_closed: self.assertTrue(my_file.closed) # read all written messages @@ -316,7 +318,7 @@ def test_file_like_context_manager(self): with self.reader_constructor(my_file) as reader: read_messages = list(reader) r = reader - if hasattr(my_file, "closed"): + if self.assert_file_closed: self.assertTrue(my_file.closed) # check if at least the number of messages matches; @@ -380,7 +382,7 @@ def _write_all(self, writer): writer(msg) def _ensure_fsync(self, io_handler): - if hasattr(io_handler.file, "fileno"): + if hasattr(io_handler, "file") and hasattr(io_handler.file, "fileno"): io_handler.file.flush() os.fsync(io_handler.file.fileno()) @@ -871,6 +873,7 @@ def _setup_instance(self): check_comments=False, preserves_channel=False, adds_default_channel=None, + assert_file_closed=False, ) @unittest.skip("not implemented") From 55474135591a1a1f27b6e42cc9c6fdc91908ea13 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 25 Jul 2025 19:15:59 +0200 Subject: [PATCH 1201/1235] Add CAN bridge script (#1961) * Add basic implementation for can bridge * Implement basic configuration parsing * Add implementation for bridge * Improve exit handling * Add debug logging * Add error handling for wrong arguments * Use stderr * Add custom usage * Add bus configuration info * Code format * Add exception for prints for can_bridge * Add from to exception in exception * Remove assignment to unused variable * Shorten line length * Organize imports * Remove unnecessary else * Add documentation for new script * Add handling for -h and help sub command Necessary for generation of documentation * Add from none to exception * Fix typo busses to bus * Add type annotations * Fix type annotations * Fix type annotations again * Add --help to get help * Add basic print help test * Add basic test file for bridge script * Add very basic test * Add different channels for virtual bus * Add assert for call to exit * Patch correct function * test * fjkdf * once again -.- * Try snakecase * use new api to create cli args for bus1 and bus2 --------- Co-authored-by: Peter Kessen --- can/bridge.py | 66 +++++++++++++++++++++++ doc/scripts.rst | 9 ++++ pyproject.toml | 2 + test/test_bridge.py | 126 +++++++++++++++++++++++++++++++++++++++++++ test/test_scripts.py | 14 +++++ 5 files changed, 217 insertions(+) create mode 100644 can/bridge.py create mode 100644 test/test_bridge.py diff --git a/can/bridge.py b/can/bridge.py new file mode 100644 index 000000000..57ebb368d --- /dev/null +++ b/can/bridge.py @@ -0,0 +1,66 @@ +""" +Creates a bridge between two CAN buses. + +This will connect to two CAN buses. Messages received on one +bus will be sent to the other bus and vice versa. +""" + +import argparse +import errno +import sys +import time +from datetime import datetime +from typing import Final + +from can.cli import add_bus_arguments, create_bus_from_namespace +from can.listener import RedirectReader +from can.notifier import Notifier + +BRIDGE_DESCRIPTION: Final = """\ +Bridge two CAN buses. + +Both can buses will be connected so that messages from bus1 will be sent on +bus2 and messages from bus2 will be sent to bus1. +""" +BUS_1_PREFIX: Final = "bus1" +BUS_2_PREFIX: Final = "bus2" + + +def _parse_bridge_args(args: list[str]) -> argparse.Namespace: + """Parse command line arguments for bridge script.""" + + parser = argparse.ArgumentParser(description=BRIDGE_DESCRIPTION) + add_bus_arguments(parser, prefix=BUS_1_PREFIX, group_title="Bus 1 arguments") + add_bus_arguments(parser, prefix=BUS_2_PREFIX, group_title="Bus 2 arguments") + + # print help message when no arguments were given + if not args: + parser.print_help(sys.stderr) + raise SystemExit(errno.EINVAL) + + results, _unknown_args = parser.parse_known_args(args) + return results + + +def main() -> None: + results = _parse_bridge_args(sys.argv[1:]) + + with ( + create_bus_from_namespace(results, prefix=BUS_1_PREFIX) as bus1, + create_bus_from_namespace(results, prefix=BUS_2_PREFIX) as bus2, + ): + reader1_to_2 = RedirectReader(bus2) + reader2_to_1 = RedirectReader(bus1) + with Notifier(bus1, [reader1_to_2]), Notifier(bus2, [reader2_to_1]): + print(f"CAN Bridge (Started on {datetime.now()})") + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + + print(f"CAN Bridge (Stopped on {datetime.now()})") + + +if __name__ == "__main__": + main() diff --git a/doc/scripts.rst b/doc/scripts.rst index 2d59b7528..1d730a74b 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -57,6 +57,15 @@ The full usage page can be seen below: :shell: +can.bridge +---------- + +A small application that can be used to connect two can buses: + +.. command-output:: python -m can.bridge -h + :shell: + + can.logconvert -------------- diff --git a/pyproject.toml b/pyproject.toml index a6a7f38c4..8fd77de21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ can_logconvert = "can.logconvert:main" can_logger = "can.logger:main" can_player = "can.player:main" can_viewer = "can.viewer:main" +can_bridge = "can.bridge:main" [project.urls] homepage = "https://github.com/hardbyte/python-can" @@ -186,6 +187,7 @@ ignore = [ "can/cli.py" = ["T20"] # flake8-print "can/logger.py" = ["T20"] # flake8-print "can/player.py" = ["T20"] # flake8-print +"can/bridge.py" = ["T20"] # flake8-print "can/viewer.py" = ["T20"] # flake8-print "examples/*" = ["T20"] # flake8-print diff --git a/test/test_bridge.py b/test/test_bridge.py new file mode 100644 index 000000000..ee41bd949 --- /dev/null +++ b/test/test_bridge.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +""" +This module tests the functions inside of bridge.py +""" + +import random +import string +import sys +import threading +import time +from time import sleep as real_sleep +import unittest.mock + +import can +import can.bridge +from can.interfaces import virtual + +from .message_helper import ComparingMessagesTestCase + + +class TestBridgeScriptModule(unittest.TestCase, ComparingMessagesTestCase): + + TIMEOUT = 3.0 + + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + ComparingMessagesTestCase.__init__( + self, + allowed_timestamp_delta=None, + preserves_channel=False, + ) + + def setUp(self) -> None: + self.stop_event = threading.Event() + + self.channel1 = "".join(random.choices(string.ascii_letters, k=8)) + self.channel2 = "".join(random.choices(string.ascii_letters, k=8)) + + self.cli_args = [ + "--bus1-interface", + "virtual", + "--bus1-channel", + self.channel1, + "--bus2-interface", + "virtual", + "--bus2-channel", + self.channel2, + ] + + self.testmsg = can.Message( + arbitration_id=0xC0FFEE, data=[0, 25, 0, 1, 3, 1, 4, 1], is_extended_id=True + ) + + def fake_sleep(self, duration): + """A fake replacement for time.sleep that checks periodically + whether self.stop_event is set, and raises KeyboardInterrupt + if so. + + This allows tests to simulate an interrupt (like Ctrl+C) + during long sleeps, in a controlled and responsive way. + """ + interval = 0.05 # Small interval for responsiveness + t_wakeup = time.perf_counter() + duration + while time.perf_counter() < t_wakeup: + if self.stop_event.is_set(): + raise KeyboardInterrupt("Simulated interrupt from fake_sleep") + real_sleep(interval) + + def test_bridge(self): + with ( + unittest.mock.patch("can.bridge.time.sleep", new=self.fake_sleep), + unittest.mock.patch("can.bridge.sys.argv", [sys.argv[0], *self.cli_args]), + ): + # start script + thread = threading.Thread(target=can.bridge.main) + thread.start() + + # wait until script instantiates virtual buses + t0 = time.perf_counter() + while True: + with virtual.channels_lock: + if ( + self.channel1 in virtual.channels + and self.channel2 in virtual.channels + ): + break + if time.perf_counter() > t0 + 2.0: + raise TimeoutError("Bridge script did not create virtual buses") + real_sleep(0.2) + + # create buses with the same channels as in scripts + with ( + can.interfaces.virtual.VirtualBus(self.channel1) as bus1, + can.interfaces.virtual.VirtualBus(self.channel2) as bus2, + ): + # send test message to bus1, it should be received on bus2 + bus1.send(self.testmsg) + recv_msg = bus2.recv(self.TIMEOUT) + self.assertMessageEqual(self.testmsg, recv_msg) + + # assert that both buses are empty + self.assertIsNone(bus1.recv(0)) + self.assertIsNone(bus2.recv(0)) + + # send test message to bus2, it should be received on bus1 + bus2.send(self.testmsg) + recv_msg = bus1.recv(self.TIMEOUT) + self.assertMessageEqual(self.testmsg, recv_msg) + + # assert that both buses are empty + self.assertIsNone(bus1.recv(0)) + self.assertIsNone(bus2.recv(0)) + + # stop the bridge script + self.stop_event.set() + thread.join() + + # assert that the virtual buses were closed + with virtual.channels_lock: + self.assertNotIn(self.channel1, virtual.channels) + self.assertNotIn(self.channel2, virtual.channels) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_scripts.py b/test/test_scripts.py index 9d8c059cf..c1a6c082d 100644 --- a/test/test_scripts.py +++ b/test/test_scripts.py @@ -98,6 +98,20 @@ def _import(self): return module +class TestBridgeScript(CanScriptTest): + def _commands(self): + commands = [ + "python -m can.bridge --help", + "can_bridge --help", + ] + return commands + + def _import(self): + import can.bridge as module + + return module + + class TestLogconvertScript(CanScriptTest): def _commands(self): commands = [ From afb1204c0e6fafc36ced42c8fc0be04a5c840454 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 27 Jul 2025 13:21:26 +0200 Subject: [PATCH 1202/1235] Update contribution guide, move all checks to tox (#1960) * docs: remove unused extras, use py3.13 * move all checks to tox, use uv in CI * update contribution guidelines * minor type annotation fixes * add missing `install` cmd * disable free-threaded tests, TestThreadSafeBus::test_send_periodic_duration is flaky * update docs as requested by review --------- Co-authored-by: zariiii9003 --- .github/workflows/ci.yml | 116 +++++----------- .gitignore | 3 +- .readthedocs.yml | 5 +- CONTRIBUTING.md | 2 +- can/bus.py | 3 +- can/interfaces/virtual.py | 12 +- can/io/mf4.py | 3 +- doc/conf.py | 2 +- doc/development.rst | 273 ++++++++++++++++++++++++++------------ doc/installation.rst | 7 + pyproject.toml | 13 +- test/back2back_test.py | 10 +- test/test_socketcan.py | 4 + tox.ini | 73 +++++++--- 14 files changed, 309 insertions(+), 217 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec5c1bbac..8c3bb407f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,32 +12,28 @@ env: jobs: test: runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.experimental }} # See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - experimental: [false] - python-version: [ - "3.9", - "3.10", - "3.11", - "3.12", - "3.13", - "pypy-3.9", - "pypy-3.10", + env: [ + "py39", + "py310", + "py311", + "py312", + "py313", + "py314", +# "py313t", +# "py314t", + "pypy310", + "pypy311", ] fail-fast: false steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Install tox + run: uv tool install tox --with tox-uv - name: Setup SocketCAN if: ${{ matrix.os == 'ubuntu-latest' }} run: | @@ -46,11 +42,11 @@ jobs: sudo ./test/open_vcan.sh - name: Test with pytest via tox run: | - tox -e gh + tox -e ${{ matrix.env }} env: # SocketCAN tests currently fail with PyPy because it does not support raw CAN sockets # See: https://foss.heptapod.net/pypy/pypy/-/issues/3809 - TEST_SOCKETCAN: "${{ matrix.os == 'ubuntu-latest' && ! startsWith(matrix.python-version, 'pypy' ) }}" + TEST_SOCKETCAN: "${{ matrix.os == 'ubuntu-latest' && ! startsWith(matrix.env, 'pypy' ) }}" - name: Coveralls Parallel uses: coverallsapp/github-action@v2 with: @@ -73,69 +69,25 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install --group lint -e . - - name: mypy 3.9 - run: | - mypy --python-version 3.9 . - - name: mypy 3.10 + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Install tox + run: uv tool install tox --with tox-uv + - name: Run linters run: | - mypy --python-version 3.10 . - - name: mypy 3.11 + tox -e lint + - name: Run type checker run: | - mypy --python-version 3.11 . - - name: mypy 3.12 - run: | - mypy --python-version 3.12 . - - name: mypy 3.13 - run: | - mypy --python-version 3.13 . - - name: ruff - run: | - ruff check can - - name: pylint - run: | - pylint \ - can/**.py \ - can/io \ - doc/conf.py \ - examples/**.py \ - can/interfaces/socketcan - - format: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install --group lint - - name: Code Format Check with Black - run: | - black --check --verbose . + tox -e type docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Install tox + run: uv tool install tox --with tox-uv - name: Build documentation run: | tox -e docs @@ -147,14 +99,12 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # fetch tags for setuptools-scm - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" + - name: Install uv + uses: astral-sh/setup-uv@v6 - name: Build wheel and sdist - run: pipx run build + run: uvx --from build pyproject-build --installer uv - name: Check build artifacts - run: pipx run twine check --strict dist/* + run: uvx twine check --strict dist/* - name: Save artifacts uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 03775bd7c..e4d402ff4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,8 @@ __pycache__/ # Distribution / packaging .Python env/ -venv/ +.venv*/ +venv*/ build/ develop-eggs/ dist/ diff --git a/.readthedocs.yml b/.readthedocs.yml index 6fe4009e4..a8c61d2de 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,7 +9,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.12" + python: "3.13" jobs: post_install: - pip install --group docs @@ -31,6 +31,3 @@ python: extra_requirements: - canalystii - gs-usb - - mf4 - - remote - - serial diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c00e9bd32..2f4194b31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1 @@ -Please read the [Development - Contributing](https://python-can.readthedocs.io/en/stable/development.html#contributing) guidelines in the documentation site. +Please read the [Development - Contributing](https://python-can.readthedocs.io/en/main/development.html#contributing) guidelines in the documentation site. diff --git a/can/bus.py b/can/bus.py index 0d031a18b..f053c4aa7 100644 --- a/can/bus.py +++ b/can/bus.py @@ -11,7 +11,6 @@ from time import time from types import TracebackType from typing import ( - Any, Callable, Optional, Union, @@ -69,7 +68,7 @@ class BusABC(metaclass=ABCMeta): @abstractmethod def __init__( self, - channel: Any, + channel: Optional[can.typechecking.Channel], can_filters: Optional[can.typechecking.CanFilters] = None, **kwargs: object, ): diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index aa858913e..6b62e5ceb 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -12,22 +12,18 @@ from copy import deepcopy from random import randint from threading import RLock -from typing import TYPE_CHECKING, Any, Optional +from typing import Any, Optional from can import CanOperationError from can.bus import BusABC, CanProtocol from can.message import Message -from can.typechecking import AutoDetectedConfig +from can.typechecking import AutoDetectedConfig, Channel logger = logging.getLogger(__name__) # Channels are lists of queues, one for each connection -if TYPE_CHECKING: - # https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime - channels: dict[Optional[Any], list[queue.Queue[Message]]] = {} -else: - channels = {} +channels: dict[Optional[Channel], list[queue.Queue[Message]]] = {} channels_lock = RLock() @@ -58,7 +54,7 @@ class VirtualBus(BusABC): def __init__( self, - channel: Any = None, + channel: Optional[Channel] = None, receive_own_messages: bool = False, rx_queue_size: int = 0, preserve_timestamps: bool = False, diff --git a/can/io/mf4.py b/can/io/mf4.py index 557d882e1..7007c3627 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -186,7 +186,8 @@ def file_size(self) -> int: """Return an estimate of the current file size in bytes.""" # TODO: find solution without accessing private attributes of asammdf return cast( - "int", self._mdf._tempfile.tell() # pylint: disable=protected-access + "int", + self._mdf._tempfile.tell(), # pylint: disable=protected-access,no-member ) def stop(self) -> None: diff --git a/doc/conf.py b/doc/conf.py index 7ae0480d5..5e413361c 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -138,7 +138,7 @@ ("py:class", "~P1"), # intersphinx fails to reference some builtins ("py:class", "asyncio.events.AbstractEventLoop"), - ("py:class", "_thread.allocate_lock"), + ("py:class", "_thread.lock"), ] # mock windows specific attributes diff --git a/doc/development.rst b/doc/development.rst index 074c1318d..97c175ada 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -1,133 +1,232 @@ Developer's Overview ==================== +Quick Start for Contributors +---------------------------- +* Fork the repository on GitHub and clone your fork. +* Create a new branch for your changes. +* Set up your development environment. +* Make your changes, add/update tests and documentation as needed. +* Run `tox` to check your changes. +* Push your branch and open a pull request. Contributing ------------ -Contribute to source code, documentation, examples and report issues: -https://github.com/hardbyte/python-can +Welcome! Thank you for your interest in python-can. Whether you want to fix a bug, +add a feature, improve documentation, write examples, help solve issues, +or simply report a problem, your contribution is valued. +Contributions are made via the `python-can GitHub repository `_. +If you have questions, feel free to open an issue or start a discussion on GitHub. -Note that the latest released version on PyPi may be significantly behind the -``main`` branch. Please open any feature requests against the ``main`` branch +If you're new to the codebase, see the next section for an overview of the code structure. +For more about the internals, see :ref:`internalapi` and information on extending the ``can.io`` module. -There is also a `python-can `__ -mailing list for development discussion. +Code Structure +^^^^^^^^^^^^^^ + +The modules in ``python-can`` are: + ++---------------------------------+------------------------------------------------------+ +|Module | Description | ++=================================+======================================================+ +|:doc:`interfaces ` | Contains interface dependent code. | ++---------------------------------+------------------------------------------------------+ +|:doc:`bus ` | Contains the interface independent Bus object. | ++---------------------------------+------------------------------------------------------+ +|:doc:`message ` | Contains the interface independent Message object. | ++---------------------------------+------------------------------------------------------+ +|:doc:`io ` | Contains a range of file readers and writers. | ++---------------------------------+------------------------------------------------------+ +|:doc:`broadcastmanager ` | Contains interface independent broadcast manager | +| | code. | ++---------------------------------+------------------------------------------------------+ -Some more information about the internals of this library can be found -in the chapter :ref:`internalapi`. -There is also additional information on extending the ``can.io`` module. +Step-by-Step Contribution Guide +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +1. **Fork and Clone the Repository** -Pre-releases ------------- + * Fork the python-can repository on GitHub to your own account. + * Clone your fork: + + .. code-block:: shell + + git clone https://github.com//python-can.git + cd python-can + + * Create a new branch for your work: + + .. code-block:: shell + + git checkout -b my-feature-branch + + * Ensure your branch is up to date with the latest changes from the main repository. + First, add the main repository as a remote (commonly named `upstream`) if you haven't already: + + .. code-block:: shell + + git remote add upstream https://github.com/hardbyte/python-can.git + + Then, regularly fetch and rebase from the main branch: + + .. code-block:: shell + + git fetch upstream + git rebase upstream/main + +2. **Set Up Your Development Environment** + + We recommend using `uv `__ to install development tools and run CLI utilities. + `uv` is a modern Python packaging tool that can quickly create virtual environments and manage dependencies, + including downloading required Python versions automatically. The `uvx` command allows you to run CLI tools + in isolated environments, separate from your global Python installation. This is useful for installing and + running Python applications (such as tox) without affecting your project's dependencies or environment. + + **Install tox (if not already available):** + + + .. code-block:: shell + + uv tool install tox --with tox-uv + + + **Quickly running your local python-can code** + + To run a local script (e.g., `snippet.py`) using your current python-can code, + you can use either the traditional `virtualenv` and `pip` workflow or the modern `uv` tool. + + **Traditional method (virtualenv + pip):** + + Create a virtual environment and install the package in editable mode. + This allows changes to your local code to be reflected immediately, without reinstalling. + + .. code-block:: shell + + # Create a new virtual environment + python -m venv .venv + + # Activate the environment + .venv\Scripts\activate # On Windows + source .venv/bin/activate # On Unix/macOS -The latest pre-release can be installed with:: + # Upgrade pip and install python-can in editable mode with development dependencies + python -m pip install --upgrade pip + pip install -e .[dev] - pip install --upgrade --pre python-can + # Run your script + python snippet.py + **Modern method (uv):** + With `uv`, you can run your script directly: -Building & Installing ---------------------- + .. code-block:: shell -The following assumes that the commands are executed from the root of the repository: + uv run snippet.py -The project can be built with:: + When ``uv run ...`` is called inside a project, + `uv` automatically sets up the environment and symlinks local packages. + No editable install is needed—changes to your code are reflected immediately. - pipx run build - pipx run twine check dist/* +3. **Make Your Changes** -The project can be installed in editable mode with:: + * Edit code, documentation, or tests as needed. + * If you fix a bug or add a feature, add or update tests in the ``test/`` directory. + * If your change affects users, update documentation in ``doc/`` and relevant docstrings. - pip install -e . +4. **Test Your Changes** -The unit tests can be run with:: + This project uses `tox `__ to automate all checks (tests, lint, type, docs). + Tox will set up isolated environments and run the right tools for you. - pipx run tox -e py + To run all checks: -The documentation can be built with:: + .. code-block:: shell - pipx run tox -e docs + tox -The linters can be run with:: + To run a specific check, use: - pip install --group lint -e . - black --check can - mypy can - ruff check can - pylint can/**.py can/io doc/conf.py examples/**.py can/interfaces/socketcan + .. code-block:: shell + tox -e lint # Run code style and lint checks (black, ruff, pylint) + tox -e type # Run type checks (mypy) + tox -e docs # Build and test documentation (sphinx) + tox -e py # Run tests (pytest) + + To run all checks in parallel (where supported), you can use: + + .. code-block:: shell + + tox p + + Some environments require specific Python versions. + If you use `uv`, it will automatically download and manage these for you. + +5. **(Optional) Build Source Distribution and Wheels** + + If you want to manually build the source distribution (sdist) and wheels for python-can, + you can use `uvx` to run the build and twine tools: + + .. code-block:: shell + + uv build + uvx twine check --strict dist/* + +6. **Push and Submit Your Contribution** + + * Push your branch: + + .. code-block:: shell + + git push origin my-feature-branch + + * Open a pull request from your branch to the ``main`` branch of the main python-can repository on GitHub. + + Please be patient — maintainers review contributions as time allows. Creating a new interface/backend -------------------------------- +.. attention:: + Please note: Pull requests that attempt to add new hardware interfaces directly to the + python-can codebase will not be accepted. Instead, we encourage contributors to create + plugins by publishing a Python package containing your :class:`can.BusABC` subclass and + using it within the python-can API. We will mention your package in this documentation + and add it as an optional dependency. For current best practices, please refer to + :ref:`plugin interface`. + + The following guideline is retained for informational purposes only and is not valid for new + contributions. + These steps are a guideline on how to add a new backend to python-can. -- Create a module (either a ``*.py`` or an entire subdirectory depending - on the complexity) inside ``can.interfaces`` -- Implement the central part of the backend: the bus class that extends +* Create a module (either a ``*.py`` or an entire subdirectory depending + on the complexity) inside ``can.interfaces``. See ``can/interfaces/socketcan`` for a reference implementation. +* Implement the central part of the backend: the bus class that extends :class:`can.BusABC`. See :ref:`businternals` for more info on this one! -- Register your backend bus class in ``BACKENDS`` in the file ``can.interfaces.__init__.py``. -- Add docs where appropriate. At a minimum add to ``doc/interfaces.rst`` and add +* Register your backend bus class in ``BACKENDS`` in the file ``can.interfaces.__init__.py``. +* Add docs where appropriate. At a minimum add to ``doc/interfaces.rst`` and add a new interface specific document in ``doc/interface/*``. It should document the supported platforms and also the hardware/software it requires. A small snippet of how to install the dependencies would also be useful to get people started without much friction. -- Also, don't forget to document your classes, methods and function with docstrings. -- Add tests in ``test/*`` where appropriate. - To get started, have a look at ``back2back_test.py``: - Simply add a test case like ``BasicTestSocketCan`` and some basic tests will be executed for the new interface. - -.. attention:: - We strongly recommend using the :ref:`plugin interface` to extend python-can. - Publish a python package that contains your :class:`can.BusABC` subclass and use - it within the python-can API. We will mention your package inside this documentation - and add it as an optional dependency. - -Code Structure --------------- - -The modules in ``python-can`` are: - -+---------------------------------+------------------------------------------------------+ -|Module | Description | -+=================================+======================================================+ -|:doc:`interfaces ` | Contains interface dependent code. | -+---------------------------------+------------------------------------------------------+ -|:doc:`bus ` | Contains the interface independent Bus object. | -+---------------------------------+------------------------------------------------------+ -|:doc:`message ` | Contains the interface independent Message object. | -+---------------------------------+------------------------------------------------------+ -|:doc:`io ` | Contains a range of file readers and writers. | -+---------------------------------+------------------------------------------------------+ -|:doc:`broadcastmanager ` | Contains interface independent broadcast manager | -| | code. | -+---------------------------------+------------------------------------------------------+ - +* Also, don't forget to document your classes, methods and function with docstrings. +* Add tests in ``test/*`` where appropriate. For example, see ``test/back2back_test.py`` and add a test case like ``BasicTestSocketCan`` for your new interface. Creating a new Release ---------------------- -- Release from the ``main`` branch (except for pre-releases). -- Check if any deprecations are pending. -- Run all tests and examples against available hardware. -- Update ``CONTRIBUTORS.txt`` with any new contributors. -- For larger changes update ``doc/history.rst``. -- Sanity check that documentation has stayed inline with code. -- In a new virtual env check that the package can be installed with pip: ``pip install python-can==X.Y.Z``. -- Create a new tag in the repository. -- Check the release on - `PyPi `__, - `Read the Docs `__ and - `GitHub `__. - +* Releases are automated via GitHub Actions. To create a new release: -Manual release steps (deprecated) ---------------------------------- + * Ensure all tests pass and documentation is up-to-date. + * Update ``CONTRIBUTORS.txt`` with any new contributors. + * For larger changes, update ``doc/history.rst``. + * Create a new tag and GitHub release (e.g., ``vX.Y.Z``) targeting the ``main`` branch. Add release notes and publish. + * The CI workflow will automatically build, check, and upload the release to PyPI and other platforms. -- Create a temporary virtual environment. -- Create a new tag in the repository. Use `semantic versioning `__. -- Build with ``pipx run build`` -- Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl``. -- Upload with twine ``twine upload dist/python-can-X.Y.Z*``. +* You can monitor the release status on: + `PyPi `__, + `Read the Docs `__ and + `GitHub Releases `__. diff --git a/doc/installation.rst b/doc/installation.rst index ff72ae21b..822de2ce0 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -21,6 +21,13 @@ Install the ``can`` package from PyPi with ``pip`` or similar:: $ pip install python-can[serial] +Pre-releases +------------ + +The latest pre-release can be installed with:: + + pip install --upgrade --pre python-can + GNU/Linux dependencies ---------------------- diff --git a/pyproject.toml b/pyproject.toml index 8fd77de21..9eb98e37b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 67.7", "setuptools_scm>=8"] +requires = ["setuptools >= 77.0", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] @@ -13,7 +13,7 @@ dependencies = [ "typing_extensions>=3.10.0.0", ] requires-python = ">=3.9" -license = { text = "LGPL v3" } +license = "LGPL-3.0-only" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -22,7 +22,6 @@ classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: Manufacturing", "Intended Audience :: Telecommunications Industry", - "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Natural Language :: English", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", @@ -100,9 +99,13 @@ test = [ "pytest-cov==4.0.0", "coverage==6.5.0", "hypothesis~=6.35.0", - "pyserial~=3.5", "parameterized~=0.8", ] +dev = [ + {include-group = "docs"}, + {include-group = "lint"}, + {include-group = "test"}, +] [tool.setuptools.dynamic] readme = { file = "README.rst" } @@ -195,8 +198,8 @@ ignore = [ known-first-party = ["can"] [tool.pylint] +extension-pkg-allow-list = ["curses"] disable = [ - "c-extension-no-member", "cyclic-import", "duplicate-code", "fixme", diff --git a/test/back2back_test.py b/test/back2back_test.py index a46597ef4..b52bae530 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -272,23 +272,21 @@ def test_sub_second_timestamp_resolution(self): self.bus2.recv(0) self.bus2.recv(0) - @unittest.skipIf(IS_CI, "fails randomly when run on CI server") def test_send_periodic_duration(self): """ Verify that send_periodic only transmits for the specified duration. Regression test for #1713. """ - for params in [(0.01, 0.003), (0.1, 0.011), (1, 0.4)]: - duration, period = params + for duration, period in [(0.01, 0.003), (0.1, 0.011), (1, 0.4)]: messages = [] self.bus2.send_periodic(can.Message(), period, duration) - while (msg := self.bus1.recv(period * 1.25)) is not None: + while (msg := self.bus1.recv(period + self.TIMEOUT)) is not None: messages.append(msg) - delta_t = round(messages[-1].timestamp - messages[0].timestamp, 2) - assert delta_t <= duration + delta_t = messages[-1].timestamp - messages[0].timestamp + assert delta_t < duration + 0.05 @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") diff --git a/test/test_socketcan.py b/test/test_socketcan.py index af06b8169..3df233f96 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -5,6 +5,7 @@ """ import ctypes import struct +import sys import unittest import warnings from unittest.mock import patch @@ -34,6 +35,7 @@ def setUp(self): self._ctypes_sizeof = ctypes.sizeof self._ctypes_alignment = ctypes.alignment + @unittest.skipIf(sys.version_info >= (3, 14), "Fails on Python 3.14 or newer") @patch("ctypes.sizeof") @patch("ctypes.alignment") def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_4( @@ -103,6 +105,7 @@ def side_effect_ctypes_alignment(value): ] self.assertEqual(expected_fields, BcmMsgHead._fields_) + @unittest.skipIf(sys.version_info >= (3, 14), "Fails on Python 3.14 or newer") @patch("ctypes.sizeof") @patch("ctypes.alignment") def test_bcm_header_factory_32_bit_sizeof_long_4_alignof_long_long_8( @@ -172,6 +175,7 @@ def side_effect_ctypes_alignment(value): ] self.assertEqual(expected_fields, BcmMsgHead._fields_) + @unittest.skipIf(sys.version_info >= (3, 14), "Fails on Python 3.14 or newer") @patch("ctypes.sizeof") @patch("ctypes.alignment") def test_bcm_header_factory_64_bit_sizeof_long_8_alignof_long_8( diff --git a/tox.ini b/tox.ini index c69f541f4..5f393cb93 100644 --- a/tox.ini +++ b/tox.ini @@ -1,46 +1,83 @@ +# https://tox.wiki/en/latest/config.html [tox] -min_version = 4.22 +min_version = 4.26 +env_list = py,lint,type,docs [testenv] +passenv = + CI + GITHUB_* + COVERALLS_* + PY_COLORS + TEST_SOCKETCAN dependency_groups = test -deps = - asammdf>=6.0; platform_python_implementation=="CPython" and python_version<"3.14" - msgpack~=1.1.0; python_version<"3.14" - pywin32>=305; platform_system=="Windows" and platform_python_implementation=="CPython" and python_version<"3.14" +extras = + canalystii + mf4 + multicast + pywin32 + serial + viewer commands = pytest {posargs} + +[testenv:py314] extras = canalystii + serial + pywin32 -[testenv:gh] -passenv = - CI - GITHUB_* - COVERALLS_* - PY_COLORS - TEST_SOCKETCAN +[testenv:{py313t,py314t,pypy310,pypy311}] +extras = + canalystii + serial [testenv:docs] description = Build and test the documentation -basepython = py312 +basepython = py313 dependency_groups = docs extras = canalystii gs-usb - mf4 - remote - serial commands = python -m sphinx -b html -Wan --keep-going doc build python -m sphinx -b doctest -W --keep-going doc build +[testenv:lint] +description = Run linters +basepython = py313 +dependency_groups = + lint +extras = + viewer +commands = + black --check . + ruff check can examples doc + pylint \ + can/**.py \ + can/io \ + doc/conf.py \ + examples/**.py \ + can/interfaces/socketcan + +[testenv:type] +description = Run type checker +basepython = py313 +dependency_groups = + lint +extras = +commands = + mypy --python-version 3.9 . + mypy --python-version 3.10 . + mypy --python-version 3.11 . + mypy --python-version 3.12 . + mypy --python-version 3.13 . [pytest] testpaths = test -addopts = -v --timeout=300 --cov=can --cov-config=tox.ini --cov-report=lcov --cov-report=term - +addopts = -v --timeout=300 --cov=can --cov-config=tox.ini --cov-report=lcov --cov-report=term --color=yes [coverage:run] # we could also use branch coverage From a4f4742df593b2de917ebbfb68a76f4210396a8d Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 27 Jul 2025 13:52:23 +0200 Subject: [PATCH 1203/1235] Minor Typing Improvements (#1962) --- can/bit_timing.py | 4 +- can/bus.py | 10 ++-- can/interface.py | 6 +-- can/interfaces/cantact.py | 21 +++++--- can/interfaces/ixxat/canlib.py | 3 +- can/interfaces/ixxat/canlib_vcinpl.py | 8 ++- can/interfaces/serial/serial_can.py | 33 ++++++------ can/interfaces/socketcan/utils.py | 3 +- can/interfaces/udp_multicast/bus.py | 12 +++-- can/interfaces/udp_multicast/utils.py | 4 +- can/interfaces/vector/canlib.py | 22 +++++--- can/interfaces/vector/exceptions.py | 8 ++- can/interfaces/virtual.py | 17 +++--- can/io/mf4.py | 2 +- can/listener.py | 4 +- can/logconvert.py | 10 ++-- can/message.py | 4 +- can/notifier.py | 2 +- can/thread_safe_bus.py | 74 +++++++++++++++++---------- can/typechecking.py | 8 ++- can/viewer.py | 2 +- pyproject.toml | 6 --- 22 files changed, 150 insertions(+), 113 deletions(-) diff --git a/can/bit_timing.py b/can/bit_timing.py index 4b0074472..2bb04bfbe 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -7,7 +7,7 @@ from can.typechecking import BitTimingDict, BitTimingFdDict -class BitTiming(Mapping): +class BitTiming(Mapping[str, int]): """Representation of a bit timing configuration for a CAN 2.0 bus. The class can be constructed in multiple ways, depending on the information @@ -477,7 +477,7 @@ def __hash__(self) -> int: return tuple(self._data.values()).__hash__() -class BitTimingFd(Mapping): +class BitTimingFd(Mapping[str, int]): """Representation of a bit timing configuration for a CAN FD bus. The class can be constructed in multiple ways, depending on the information diff --git a/can/bus.py b/can/bus.py index f053c4aa7..ec9eb09b7 100644 --- a/can/bus.py +++ b/can/bus.py @@ -5,7 +5,7 @@ import contextlib import logging import threading -from abc import ABC, ABCMeta, abstractmethod +from abc import ABC, abstractmethod from collections.abc import Iterator, Sequence from enum import Enum, auto from time import time @@ -19,7 +19,6 @@ from typing_extensions import Self -import can import can.typechecking from can.broadcastmanager import CyclicSendTaskABC, ThreadBasedCyclicSendTask from can.message import Message @@ -44,7 +43,7 @@ class CanProtocol(Enum): CAN_XL = auto() -class BusABC(metaclass=ABCMeta): +class BusABC(ABC): """The CAN Bus Abstract Base Class that serves as the basis for all concrete interfaces. @@ -68,7 +67,7 @@ class BusABC(metaclass=ABCMeta): @abstractmethod def __init__( self, - channel: Optional[can.typechecking.Channel], + channel: can.typechecking.Channel, can_filters: Optional[can.typechecking.CanFilters] = None, **kwargs: object, ): @@ -447,7 +446,6 @@ def _matches_filters(self, msg: Message) -> bool: for _filter in self._filters: # check if this filter even applies to the message if "extended" in _filter: - _filter = cast("can.typechecking.CanFilterExtended", _filter) if _filter["extended"] != msg.is_extended_id: continue @@ -524,7 +522,7 @@ def protocol(self) -> CanProtocol: return self._can_protocol @staticmethod - def _detect_available_configs() -> list[can.typechecking.AutoDetectedConfig]: + def _detect_available_configs() -> Sequence[can.typechecking.AutoDetectedConfig]: """Detect all configurations/channels that this interface could currently connect with. diff --git a/can/interface.py b/can/interface.py index eee58ff41..4aa010a36 100644 --- a/can/interface.py +++ b/can/interface.py @@ -7,7 +7,7 @@ import concurrent.futures.thread import importlib import logging -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Sequence from typing import Any, Optional, Union, cast from . import util @@ -142,7 +142,7 @@ def Bus( # noqa: N802 def detect_available_configs( interfaces: Union[None, str, Iterable[str]] = None, timeout: float = 5.0, -) -> list[AutoDetectedConfig]: +) -> Sequence[AutoDetectedConfig]: """Detect all configurations/channels that the interfaces could currently connect with. @@ -175,7 +175,7 @@ def detect_available_configs( # otherwise assume iterable of strings # Collect detection callbacks - callbacks: dict[str, Callable[[], list[AutoDetectedConfig]]] = {} + callbacks: dict[str, Callable[[], Sequence[AutoDetectedConfig]]] = {} for interface_keyword in interfaces: try: bus_class = _get_class_for_interface(interface_keyword) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 963a9ee3b..08fe15b72 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -4,6 +4,7 @@ import logging import time +from collections.abc import Sequence from typing import Any, Optional, Union from unittest.mock import Mock @@ -14,6 +15,7 @@ CanInterfaceNotImplementedError, error_check, ) +from ..typechecking import AutoDetectedConfig from ..util import check_or_adjust_timing_clock, deprecated_args_alias logger = logging.getLogger(__name__) @@ -31,7 +33,7 @@ class CantactBus(BusABC): """CANtact interface""" @staticmethod - def _detect_available_configs(): + def _detect_available_configs() -> Sequence[AutoDetectedConfig]: try: interface = cantact.Interface() except (NameError, SystemError, AttributeError): @@ -40,7 +42,7 @@ def _detect_available_configs(): ) return [] - channels = [] + channels: list[AutoDetectedConfig] = [] for i in range(0, interface.channel_count()): channels.append({"interface": "cantact", "channel": f"ch:{i}"}) return channels @@ -121,7 +123,14 @@ def __init__( **kwargs, ) - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> tuple[Optional[Message], bool]: + if timeout is None: + raise TypeError( + f"{self.__class__.__name__} expects a numeric `timeout` value." + ) + with error_check("Cannot receive message"): frame = self.interface.recv(int(timeout * 1000)) if frame is None: @@ -140,7 +149,7 @@ def _recv_internal(self, timeout): ) return msg, False - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None) -> None: with error_check("Cannot send message"): self.interface.send( self.channel, @@ -151,13 +160,13 @@ def send(self, msg, timeout=None): msg.data, ) - def shutdown(self): + def shutdown(self) -> None: super().shutdown() with error_check("Cannot shutdown interface"): self.interface.stop() -def mock_recv(timeout): +def mock_recv(timeout: int) -> Optional[dict[str, Any]]: if timeout > 0: return { "id": 0x123, diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index e6ad25d57..6192367c4 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -9,7 +9,6 @@ CyclicSendTaskABC, Message, ) -from can.typechecking import AutoDetectedConfig class IXXATBus(BusABC): @@ -175,5 +174,5 @@ def state(self) -> BusState: return self.bus.state @staticmethod - def _detect_available_configs() -> list[AutoDetectedConfig]: + def _detect_available_configs() -> Sequence[vcinpl.AutoDetectedIxxatConfig]: return vcinpl._detect_available_configs() diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 098b022bb..59f98417d 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -976,8 +976,8 @@ def get_ixxat_hwids(): return hwids -def _detect_available_configs() -> list[AutoDetectedConfig]: - config_list = [] # list in wich to store the resulting bus kwargs +def _detect_available_configs() -> Sequence["AutoDetectedIxxatConfig"]: + config_list = [] # list in which to store the resulting bus kwargs # used to detect HWID device_handle = HANDLE() @@ -1026,3 +1026,7 @@ def _detect_available_configs() -> list[AutoDetectedConfig]: pass # _canlib is None in the CI tests -> return a blank list return config_list + + +class AutoDetectedIxxatConfig(AutoDetectedConfig): + unique_hardware_id: int diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 12ce5aff1..680cf10f6 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -10,7 +10,8 @@ import io import logging import struct -from typing import Any, Optional +from collections.abc import Sequence +from typing import Any, Optional, cast from can import ( BusABC, @@ -26,21 +27,14 @@ logger = logging.getLogger("can.serial") try: - import serial + import serial.tools.list_ports except ImportError: logger.warning( "You won't be able to use the serial can backend without " - "the serial module installed!" + "the `pyserial` package installed!" ) serial = None -try: - from serial.tools.list_ports import comports as list_comports -except ImportError: - # If unavailable on some platform, just return nothing - def list_comports() -> list[Any]: - return [] - CAN_ERR_FLAG = 0x20000000 CAN_RTR_FLAG = 0x40000000 @@ -63,8 +57,7 @@ def __init__( baudrate: int = 115200, timeout: float = 0.1, rtscts: bool = False, - *args, - **kwargs, + **kwargs: Any, ) -> None: """ :param channel: @@ -107,7 +100,7 @@ def __init__( "could not create the serial device" ) from error - super().__init__(channel, *args, **kwargs) + super().__init__(channel, **kwargs) def shutdown(self) -> None: """ @@ -232,7 +225,7 @@ def _recv_internal( def fileno(self) -> int: try: - return self._ser.fileno() + return cast("int", self._ser.fileno()) except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" @@ -241,7 +234,11 @@ def fileno(self) -> int: raise CanOperationError("Cannot fetch fileno") from exception @staticmethod - def _detect_available_configs() -> list[AutoDetectedConfig]: - return [ - {"interface": "serial", "channel": port.device} for port in list_comports() - ] + def _detect_available_configs() -> Sequence[AutoDetectedConfig]: + configs: list[AutoDetectedConfig] = [] + if serial is None: + return configs + + for port in serial.tools.list_ports.comports(): + configs.append({"interface": "serial", "channel": port.device}) + return configs diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 80dcb203f..1c096f66e 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -9,7 +9,7 @@ import struct import subprocess import sys -from typing import Optional, cast +from typing import Optional from can import typechecking from can.interfaces.socketcan.constants import CAN_EFF_FLAG @@ -28,7 +28,6 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes can_id = can_filter["can_id"] can_mask = can_filter["can_mask"] if "extended" in can_filter: - can_filter = cast("typechecking.CanFilterExtended", can_filter) # Match on either 11-bit OR 29-bit messages instead of both can_mask |= CAN_EFF_FLAG if can_filter["extended"]: diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 45882ec07..9e0187ea2 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -6,10 +6,10 @@ import struct import time import warnings -from typing import Optional, Union +from typing import Any, Optional, Union import can -from can import BusABC, CanProtocol +from can import BusABC, CanProtocol, Message from can.typechecking import AutoDetectedConfig from .utils import is_msgpack_installed, pack_message, unpack_message @@ -98,7 +98,7 @@ def __init__( hop_limit: int = 1, receive_own_messages: bool = False, fd: bool = True, - **kwargs, + **kwargs: Any, ) -> None: is_msgpack_installed() @@ -126,7 +126,9 @@ def is_fd(self) -> bool: ) return self._can_protocol is CanProtocol.CAN_FD - def _recv_internal(self, timeout: Optional[float]): + def _recv_internal( + self, timeout: Optional[float] + ) -> tuple[Optional[Message], bool]: result = self._multicast.recv(timeout) if not result: return None, False @@ -204,7 +206,7 @@ def __init__( # Look up multicast group address in name server and find out IP version of the first suitable target # and then get the address family of it (socket.AF_INET or socket.AF_INET6) - connection_candidates = socket.getaddrinfo( # type: ignore + connection_candidates = socket.getaddrinfo( group, self.port, type=socket.SOCK_DGRAM ) sock = None diff --git a/can/interfaces/udp_multicast/utils.py b/can/interfaces/udp_multicast/utils.py index c6b2630a5..de39833a3 100644 --- a/can/interfaces/udp_multicast/utils.py +++ b/can/interfaces/udp_multicast/utils.py @@ -2,7 +2,7 @@ Defines common functions. """ -from typing import Any, Optional +from typing import Any, Optional, cast from can import CanInterfaceNotImplementedError, Message from can.typechecking import ReadableBytesLike @@ -51,7 +51,7 @@ def pack_message(message: Message) -> bytes: "bitrate_switch": message.bitrate_switch, "error_state_indicator": message.error_state_indicator, } - return msgpack.packb(as_dict, use_bin_type=True) + return cast("bytes", msgpack.packb(as_dict, use_bin_type=True)) def unpack_message( diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 986f52002..d15b89803 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -982,8 +982,8 @@ def reset(self) -> None: ) @staticmethod - def _detect_available_configs() -> list[AutoDetectedConfig]: - configs = [] + def _detect_available_configs() -> Sequence["AutoDetectedVectorConfig"]: + configs: list[AutoDetectedVectorConfig] = [] channel_configs = get_channel_configs() LOG.info("Found %d channels", len(channel_configs)) for channel_config in channel_configs: @@ -999,16 +999,13 @@ def _detect_available_configs() -> list[AutoDetectedConfig]: ) configs.append( { - # data for use in VectorBus.__init__(): "interface": "vector", "channel": channel_config.hw_channel, "serial": channel_config.serial_number, "channel_index": channel_config.channel_index, - # data for use in VectorBus.set_application_config(): "hw_type": channel_config.hw_type, "hw_index": channel_config.hw_index, "hw_channel": channel_config.hw_channel, - # additional information: "supports_fd": bool( channel_config.channel_capabilities & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT @@ -1016,7 +1013,7 @@ def _detect_available_configs() -> list[AutoDetectedConfig]: "vector_channel_config": channel_config, } ) - return configs # type: ignore + return configs @staticmethod def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: @@ -1188,6 +1185,19 @@ class VectorChannelConfig(NamedTuple): transceiver_name: str +class AutoDetectedVectorConfig(AutoDetectedConfig): + # data for use in VectorBus.__init__(): + serial: int + channel_index: int + # data for use in VectorBus.set_application_config(): + hw_type: int + hw_index: int + hw_channel: int + # additional information: + supports_fd: bool + vector_channel_config: VectorChannelConfig + + def _get_xl_driver_config() -> xlclass.XLdriverConfig: if xldriver is None: raise VectorError( diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 53c774e6f..b43df5e6c 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -1,10 +1,14 @@ """Exception/error declarations for the vector interface.""" +from typing import Any, Optional, Union + from can import CanError, CanInitializationError, CanOperationError class VectorError(CanError): - def __init__(self, error_code, error_string, function): + def __init__( + self, error_code: Optional[int], error_string: str, function: str + ) -> None: super().__init__( message=f"{function} failed ({error_string})", error_code=error_code ) @@ -12,7 +16,7 @@ def __init__(self, error_code, error_string, function): # keep reference to args for pickling self._args = error_code, error_string, function - def __reduce__(self): + def __reduce__(self) -> Union[str, tuple[Any, ...]]: return type(self), self._args, {} diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 6b62e5ceb..e4f68b0c4 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -12,7 +12,7 @@ from copy import deepcopy from random import randint from threading import RLock -from typing import Any, Optional +from typing import Any, Final, Optional from can import CanOperationError from can.bus import BusABC, CanProtocol @@ -21,10 +21,9 @@ logger = logging.getLogger(__name__) - # Channels are lists of queues, one for each connection -channels: dict[Optional[Channel], list[queue.Queue[Message]]] = {} -channels_lock = RLock() +channels: Final[dict[Channel, list[queue.Queue[Message]]]] = {} +channels_lock: Final = RLock() class VirtualBus(BusABC): @@ -54,7 +53,7 @@ class VirtualBus(BusABC): def __init__( self, - channel: Optional[Channel] = None, + channel: Channel = "channel-0", receive_own_messages: bool = False, rx_queue_size: int = 0, preserve_timestamps: bool = False, @@ -67,9 +66,9 @@ def __init__( bus by virtual instances constructed with the same channel identifier. :param channel: The channel identifier. This parameter can be an - arbitrary value. The bus instance will be able to see messages - from other virtual bus instances that were created with the same - value. + arbitrary hashable value. The bus instance will be able to see + messages from other virtual bus instances that were created with + the same value. :param receive_own_messages: If set to True, sent messages will be reflected back on the input queue. :param rx_queue_size: The size of the reception queue. The reception @@ -179,7 +178,7 @@ def _detect_available_configs() -> list[AutoDetectedConfig]: available_channels = list(channels.keys()) # find a currently unused channel - def get_extra(): + def get_extra() -> str: return f"channel-{randint(0, 9999)}" extra = get_extra() diff --git a/can/io/mf4.py b/can/io/mf4.py index 7007c3627..68aff87ae 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -274,7 +274,7 @@ def on_message_received(self, msg: Message) -> None: self._rtr_buffer = np.zeros(1, dtype=RTR_DTYPE) -class FrameIterator(metaclass=abc.ABCMeta): +class FrameIterator(abc.ABC): """ Iterator helper class for common handling among CAN DataFrames, ErrorFrames and RemoteFrames. """ diff --git a/can/listener.py b/can/listener.py index 6256d33b6..7f8f436a0 100644 --- a/can/listener.py +++ b/can/listener.py @@ -5,7 +5,7 @@ import asyncio import sys import warnings -from abc import ABCMeta, abstractmethod +from abc import ABC, abstractmethod from collections.abc import AsyncIterator from queue import Empty, SimpleQueue from typing import Any, Optional @@ -14,7 +14,7 @@ from can.message import Message -class Listener(metaclass=ABCMeta): +class Listener(ABC): """The basic listener that can be called directly to handle some CAN message:: diff --git a/can/logconvert.py b/can/logconvert.py index 49cdaf4bb..4527dc23e 100644 --- a/can/logconvert.py +++ b/can/logconvert.py @@ -5,17 +5,21 @@ import argparse import errno import sys +from typing import TYPE_CHECKING, NoReturn from can import Logger, LogReader, SizedRotatingLogger +if TYPE_CHECKING: + from can.io.generic import MessageWriter + class ArgumentParser(argparse.ArgumentParser): - def error(self, message): + def error(self, message: str) -> NoReturn: self.print_help(sys.stderr) self.exit(errno.EINVAL, f"{self.prog}: error: {message}\n") -def main(): +def main() -> None: parser = ArgumentParser( description="Convert a log file from one format to another.", ) @@ -47,7 +51,7 @@ def main(): with LogReader(args.input) as reader: if args.file_size: - logger = SizedRotatingLogger( + logger: MessageWriter = SizedRotatingLogger( base_filename=args.output, max_bytes=args.file_size ) else: diff --git a/can/message.py b/can/message.py index d8d94ea84..c1fbffd21 100644 --- a/can/message.py +++ b/can/message.py @@ -8,7 +8,7 @@ from copy import deepcopy from math import isinf, isnan -from typing import Optional +from typing import Any, Optional from . import typechecking @@ -210,7 +210,7 @@ def __copy__(self) -> "Message": error_state_indicator=self.error_state_indicator, ) - def __deepcopy__(self, memo: dict) -> "Message": + def __deepcopy__(self, memo: Optional[dict[int, Any]]) -> "Message": return Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, diff --git a/can/notifier.py b/can/notifier.py index 2b9944450..b2f550df7 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -103,7 +103,7 @@ def find_instances(self, bus: BusABC) -> tuple["Notifier", ...]: return tuple(instance_list) -class Notifier(AbstractContextManager): +class Notifier(AbstractContextManager["Notifier"]): _registry: Final = _NotifierRegistry() diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 9b008667f..35a4f400c 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -1,4 +1,12 @@ +from contextlib import nullcontext from threading import RLock +from typing import Any, Optional + +from can import typechecking +from can.bus import BusABC, BusState, CanProtocol +from can.message import Message + +from .interface import Bus try: # Only raise an exception on instantiation but allow module @@ -10,10 +18,6 @@ ObjectProxy = object import_exc = exc -from contextlib import nullcontext - -from .interface import Bus - class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method """ @@ -32,65 +36,81 @@ class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method instead of :meth:`~can.BusABC.recv` directly. """ - def __init__(self, *args, **kwargs): + __wrapped__: BusABC + + def __init__( + self, + channel: Optional[typechecking.Channel] = None, + interface: Optional[str] = None, + config_context: Optional[str] = None, + ignore_config: bool = False, + **kwargs: Any, + ) -> None: if import_exc is not None: raise import_exc - super().__init__(Bus(*args, **kwargs)) + super().__init__( + Bus( + channel=channel, + interface=interface, + config_context=config_context, + ignore_config=ignore_config, + **kwargs, + ) + ) # now, BusABC.send_periodic() does not need a lock anymore, but the # implementation still requires a context manager - self.__wrapped__._lock_send_periodic = nullcontext() + self.__wrapped__._lock_send_periodic = nullcontext() # type: ignore[assignment] # init locks for sending and receiving separately self._lock_send = RLock() self._lock_recv = RLock() - def recv( - self, timeout=None, *args, **kwargs - ): # pylint: disable=keyword-arg-before-vararg + def recv(self, timeout: Optional[float] = None) -> Optional[Message]: with self._lock_recv: - return self.__wrapped__.recv(timeout=timeout, *args, **kwargs) + return self.__wrapped__.recv(timeout=timeout) - def send( - self, msg, timeout=None, *args, **kwargs - ): # pylint: disable=keyword-arg-before-vararg + def send(self, msg: Message, timeout: Optional[float] = None) -> None: with self._lock_send: - return self.__wrapped__.send(msg, timeout=timeout, *args, **kwargs) + return self.__wrapped__.send(msg=msg, timeout=timeout) # send_periodic does not need a lock, since the underlying # `send` method is already synchronized @property - def filters(self): + def filters(self) -> Optional[typechecking.CanFilters]: with self._lock_recv: return self.__wrapped__.filters @filters.setter - def filters(self, filters): + def filters(self, filters: Optional[typechecking.CanFilters]) -> None: with self._lock_recv: self.__wrapped__.filters = filters - def set_filters( - self, filters=None, *args, **kwargs - ): # pylint: disable=keyword-arg-before-vararg + def set_filters(self, filters: Optional[typechecking.CanFilters] = None) -> None: with self._lock_recv: - return self.__wrapped__.set_filters(filters=filters, *args, **kwargs) + return self.__wrapped__.set_filters(filters=filters) - def flush_tx_buffer(self, *args, **kwargs): + def flush_tx_buffer(self) -> None: with self._lock_send: - return self.__wrapped__.flush_tx_buffer(*args, **kwargs) + return self.__wrapped__.flush_tx_buffer() - def shutdown(self, *args, **kwargs): + def shutdown(self) -> None: with self._lock_send, self._lock_recv: - return self.__wrapped__.shutdown(*args, **kwargs) + return self.__wrapped__.shutdown() @property - def state(self): + def state(self) -> BusState: with self._lock_send, self._lock_recv: return self.__wrapped__.state @state.setter - def state(self, new_state): + def state(self, new_state: BusState) -> None: with self._lock_send, self._lock_recv: self.__wrapped__.state = new_state + + @property + def protocol(self) -> CanProtocol: + with self._lock_send, self._lock_recv: + return self.__wrapped__.protocol diff --git a/can/typechecking.py b/can/typechecking.py index fc0c87c0d..8c25e8b57 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -21,18 +21,16 @@ import struct -class CanFilter(TypedDict): +class _CanFilterBase(TypedDict): can_id: int can_mask: int -class CanFilterExtended(TypedDict): - can_id: int - can_mask: int +class CanFilter(_CanFilterBase, total=False): extended: bool -CanFilters = Sequence[Union[CanFilter, CanFilterExtended]] +CanFilters = Sequence[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 diff --git a/can/viewer.py b/can/viewer.py index 81e8942a4..97bda1676 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -159,7 +159,7 @@ def run(self): # Unpack the data and then convert it into SI-units @staticmethod - def unpack_data(cmd: int, cmd_to_struct: dict, data: bytes) -> list[float]: + def unpack_data(cmd: int, cmd_to_struct: TDataStructs, data: bytes) -> list[float]: if not cmd_to_struct or not data: # These messages do not contain a data package return [] diff --git a/pyproject.toml b/pyproject.toml index 9eb98e37b..47d8e7a6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,11 +130,8 @@ disallow_incomplete_defs = true warn_redundant_casts = true warn_unused_ignores = true exclude = [ - "venv", "^doc/conf.py$", - "^build", "^test", - "^can/interfaces/__init__.py", "^can/interfaces/etas", "^can/interfaces/gs_usb", "^can/interfaces/ics_neovi", @@ -144,12 +141,9 @@ exclude = [ "^can/interfaces/nican", "^can/interfaces/neousys", "^can/interfaces/pcan", - "^can/interfaces/serial", "^can/interfaces/socketcan", "^can/interfaces/systec", - "^can/interfaces/udp_multicast", "^can/interfaces/usb2can", - "^can/interfaces/virtual", ] [tool.ruff] From 7c521af9dac83ee76c4ae9eeb092b51c5e21abc7 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sun, 27 Jul 2025 15:22:17 +0200 Subject: [PATCH 1204/1235] Update Test Dependencies (#1963) * update test dependencies * use pytest-modern for prettier pytest output * update ruff * fix coveralls flag-name * checkout to fix failing "git log" cmd --- .github/workflows/ci.yml | 3 ++- can/broadcastmanager.py | 4 ++-- can/interfaces/systec/structures.py | 5 +++++ pyproject.toml | 19 ++++++++++--------- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c3bb407f..b799b463e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} - flag-name: Unittests-${{ matrix.os }}-${{ matrix.python-version }} + flag-name: Unittests-${{ matrix.os }}-${{ matrix.env }} parallel: true path-to-lcov: ./coverage.lcov @@ -59,6 +59,7 @@ jobs: needs: test runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 - name: Coveralls Finished uses: coverallsapp/github-action@v2 with: diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index b2bc28e76..a71f6fd11 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -39,8 +39,8 @@ class _Pywin32Event: class _Pywin32: def __init__(self) -> None: - import pywintypes # pylint: disable=import-outside-toplevel,import-error - import win32event # pylint: disable=import-outside-toplevel,import-error + import pywintypes # noqa: PLC0415 # pylint: disable=import-outside-toplevel,import-error + import win32event # noqa: PLC0415 # pylint: disable=import-outside-toplevel,import-error self.pywintypes = pywintypes self.win32event = win32event diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index c80f21d44..a50ac4c26 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -51,6 +51,7 @@ class CanMsg(Structure): DWORD, ), # Receive time stamp in ms (for transmit messages no meaning) ] + __hash__ = Structure.__hash__ def __init__( self, id_=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None, dlc=None @@ -116,6 +117,7 @@ class Status(Structure): ("m_wCanStatus", WORD), # CAN error status (see enum :class:`CanStatus`) ("m_wUsbStatus", WORD), # USB error status (see enum :class:`UsbStatus`) ] + __hash__ = Structure.__hash__ def __eq__(self, other): if not isinstance(other, Status): @@ -171,6 +173,7 @@ class InitCanParam(Structure): WORD, ), # number of transmit buffer entries (default is 4096) ] + __hash__ = Structure.__hash__ def __init__( self, mode, BTR, OCR, AMR, ACR, baudrate, rx_buffer_entries, tx_buffer_entries @@ -277,6 +280,7 @@ class HardwareInfoEx(Structure): ("m_dwUniqueId3", DWORD), ("m_dwFlags", DWORD), # additional flags ] + __hash__ = Structure.__hash__ def __init__(self): super().__init__(sizeof(HardwareInfoEx)) @@ -389,6 +393,7 @@ class ChannelInfo(Structure): WORD, ), # CAN status (same as received by method :meth:`UcanServer.get_status`) ] + __hash__ = Structure.__hash__ def __init__(self): super().__init__(sizeof(ChannelInfo)) diff --git a/pyproject.toml b/pyproject.toml index 47d8e7a6d..51bcff693 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,18 +88,19 @@ docs = [ ] lint = [ "pylint==3.3.*", - "ruff==0.11.12", + "ruff==0.12.5", "black==25.1.*", - "mypy==1.16.*", + "mypy==1.17.*", ] test = [ - "pytest==8.3.*", - "pytest-timeout==2.1.*", - "coveralls==3.3.1", - "pytest-cov==4.0.0", - "coverage==6.5.0", - "hypothesis~=6.35.0", - "parameterized~=0.8", + "pytest==8.4.*", + "pytest-timeout==2.4.*", + "pytest-modern==0.7.*;platform_system!='Windows'", + "coveralls==4.0.*", + "pytest-cov==6.2.*", + "coverage==7.10.*", + "hypothesis==6.136.*", + "parameterized==0.9.*", ] dev = [ {include-group = "docs"}, From 51689bca00ce257a12d5b6b508f3da330f14a553 Mon Sep 17 00:00:00 2001 From: John Whittington Date: Tue, 5 Aug 2025 14:12:54 +0200 Subject: [PATCH 1205/1235] Mf4Reader: support files from ihedvall/mdflib (#1967) --- can/io/mf4.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/can/io/mf4.py b/can/io/mf4.py index 68aff87ae..bf594e3a5 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -352,7 +352,10 @@ def __iter__(self) -> Generator[Message, None, None]: if "CAN_DataFrame.BusChannel" in names: kv["channel"] = int(data["CAN_DataFrame.BusChannel"][i]) if "CAN_DataFrame.Dir" in names: - kv["is_rx"] = int(data["CAN_DataFrame.Dir"][i]) == 0 + if data["CAN_DataFrame.Dir"][i].dtype.kind == "S": + kv["is_rx"] = data["CAN_DataFrame.Dir"][i] == b"Rx" + else: + kv["is_rx"] = int(data["CAN_DataFrame.Dir"][i]) == 0 if "CAN_DataFrame.IDE" in names: kv["is_extended_id"] = bool(data["CAN_DataFrame.IDE"][i]) if "CAN_DataFrame.EDL" in names: @@ -387,7 +390,10 @@ def __iter__(self) -> Generator[Message, None, None]: if "CAN_ErrorFrame.BusChannel" in names: kv["channel"] = int(data["CAN_ErrorFrame.BusChannel"][i]) if "CAN_ErrorFrame.Dir" in names: - kv["is_rx"] = int(data["CAN_ErrorFrame.Dir"][i]) == 0 + if data["CAN_ErrorFrame.Dir"][i].dtype.kind == "S": + kv["is_rx"] = data["CAN_ErrorFrame.Dir"][i] == b"Rx" + else: + kv["is_rx"] = int(data["CAN_ErrorFrame.Dir"][i]) == 0 if "CAN_ErrorFrame.ID" in names: kv["arbitration_id"] = ( int(data["CAN_ErrorFrame.ID"][i]) & 0x1FFFFFFF @@ -441,7 +447,10 @@ def __iter__(self) -> Generator[Message, None, None]: if "CAN_RemoteFrame.BusChannel" in names: kv["channel"] = int(data["CAN_RemoteFrame.BusChannel"][i]) if "CAN_RemoteFrame.Dir" in names: - kv["is_rx"] = int(data["CAN_RemoteFrame.Dir"][i]) == 0 + if data["CAN_RemoteFrame.Dir"][i].dtype.kind == "S": + kv["is_rx"] = data["CAN_RemoteFrame.Dir"][i] == b"Rx" + else: + kv["is_rx"] = int(data["CAN_RemoteFrame.Dir"][i]) == 0 if "CAN_RemoteFrame.IDE" in names: kv["is_extended_id"] = bool(data["CAN_RemoteFrame.IDE"][i]) From 6160f76a214d1813cb1314371730586cb11416bb Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 5 Aug 2025 14:36:42 +0200 Subject: [PATCH 1206/1235] Use `towncrier` for Automated Changelog Generation, Add PR Template (#1965) * add towncrier configuration * add pull request template * add news fragments * add fragment for 1967 --- .github/pull_request_template.md | 37 ++++ CHANGELOG.md | 281 +++++++++++++------------------ doc/changelog.d/.gitignore | 11 ++ doc/changelog.d/1758.added.md | 1 + doc/changelog.d/1851.changed.md | 1 + doc/changelog.d/1890.added.md | 1 + doc/changelog.d/1904.fixed.md | 1 + doc/changelog.d/1906.fixed.md | 1 + doc/changelog.d/1908.fixed.md | 1 + doc/changelog.d/1914.added.md | 1 + doc/changelog.d/1920.added.md | 1 + doc/changelog.d/1921.fixed.md | 1 + doc/changelog.d/1927.fixed.md | 1 + doc/changelog.d/1931.removed.md | 1 + doc/changelog.d/1934.fixed.md | 1 + doc/changelog.d/1940.fixed.md | 1 + doc/changelog.d/1941.added.md | 1 + doc/changelog.d/1945.changed.md | 2 + doc/changelog.d/1946.changed.md | 1 + doc/changelog.d/1947.changed.md | 1 + doc/changelog.d/1948.added.md | 1 + doc/changelog.d/1949.added.md | 1 + doc/changelog.d/1951.removed.md | 1 + doc/changelog.d/1953.added.md | 1 + doc/changelog.d/1954.added.md | 1 + doc/changelog.d/1957.fixed.md | 1 + doc/changelog.d/1960.changed.md | 1 + doc/changelog.d/1961.added.md | 1 + doc/changelog.d/1967.fixed.md | 1 + doc/development.rst | 65 ++++++- pyproject.toml | 40 ++++- 31 files changed, 284 insertions(+), 177 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 doc/changelog.d/.gitignore create mode 100644 doc/changelog.d/1758.added.md create mode 100644 doc/changelog.d/1851.changed.md create mode 100644 doc/changelog.d/1890.added.md create mode 100644 doc/changelog.d/1904.fixed.md create mode 100644 doc/changelog.d/1906.fixed.md create mode 100644 doc/changelog.d/1908.fixed.md create mode 100644 doc/changelog.d/1914.added.md create mode 100644 doc/changelog.d/1920.added.md create mode 100644 doc/changelog.d/1921.fixed.md create mode 100644 doc/changelog.d/1927.fixed.md create mode 100644 doc/changelog.d/1931.removed.md create mode 100644 doc/changelog.d/1934.fixed.md create mode 100644 doc/changelog.d/1940.fixed.md create mode 100644 doc/changelog.d/1941.added.md create mode 100644 doc/changelog.d/1945.changed.md create mode 100644 doc/changelog.d/1946.changed.md create mode 100644 doc/changelog.d/1947.changed.md create mode 100644 doc/changelog.d/1948.added.md create mode 100644 doc/changelog.d/1949.added.md create mode 100644 doc/changelog.d/1951.removed.md create mode 100644 doc/changelog.d/1953.added.md create mode 100644 doc/changelog.d/1954.added.md create mode 100644 doc/changelog.d/1957.fixed.md create mode 100644 doc/changelog.d/1960.changed.md create mode 100644 doc/changelog.d/1961.added.md create mode 100644 doc/changelog.d/1967.fixed.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..e6ce365d1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,37 @@ + + +## Summary of Changes + + + +- + +## Related Issues / Pull Requests + + + +- Closes # +- Related to # + +## Type of Change + +- [ ] Bug fix +- [ ] New feature +- [ ] Documentation update +- [ ] Refactoring +- [ ] Other (please describe): + +## Checklist + +- [ ] I have followed the [contribution guide](https://python-can.readthedocs.io/en/main/development.html). +- [ ] I have added or updated tests as appropriate. +- [ ] I have added or updated documentation as appropriate. +- [ ] I have added a [news fragment](doc/changelog.d/) for towncrier. +- [ ] All checks and tests pass (`tox`). + +## Additional Notes + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 39cbaa716..a8ec28f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,17 @@ -Version 4.5.0 -============= +# Changelog -Features --------- + + + + + +## Version 4.5.0 + +### Features * gs_usb command-line support (and documentation updates and stability fixes) by @BenGardiner in https://github.com/hardbyte/python-can/pull/1790 * Faster and more general MF4 support by @cssedev in https://github.com/hardbyte/python-can/pull/1892 @@ -13,8 +22,7 @@ Features * Improve TestBusConfig by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1804 * Improve speed of TRCReader by @lebuni in https://github.com/hardbyte/python-can/pull/1893 -Bug Fixes ---------- +### Bug Fixes * Fix Kvaser timestamp by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1878 * Set end_time in ThreadBasedCyclicSendTask.start() by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1871 @@ -25,8 +33,7 @@ Bug Fixes * Resolve AttributeError within NicanError by @vijaysubbiah20 in https://github.com/hardbyte/python-can/pull/1806 -Miscellaneous -------------- +### Miscellaneous * Fix CI by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1889 * Update msgpack dependency by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1875 @@ -43,11 +50,10 @@ Miscellaneous * Add zlgcan to docs by @zariiii9003 in https://github.com/hardbyte/python-can/pull/1839 -Version 4.4.2 -============= +## Version 4.4.2 + +### Bug Fixes -Bug Fixes ---------- * Remove `abstractmethod` decorator from `Listener.stop()` (#1770, #1795) * Fix `SizedRotatingLogger` file suffix bug (#1792, #1793) * gs_usb: Use `BitTiming` class internally to configure bitrate (#1747, #1748) @@ -56,8 +62,7 @@ Bug Fixes * socketcan: Do not log exception on non-linux platforms (#1800) * vector, kvaser: Activate channels after CAN filters were applied (#1413, #1708, #1796) -Features --------- +### Features * kvaser: Add support for non-ISO CAN FD (#1752) * neovi: Return timestamps relative to epoch (#1789) @@ -66,11 +71,9 @@ Features * vector: Add support for `listen_only` mode (#1764) -Version 4.4.0 -============= +## Version 4.4.0 -Features --------- +### Features * TRC 1.3 Support: Added support for .trc log files as generated by PCAN Explorer v5 and other tools, expanding compatibility with common log file formats (#1753). * ASCReader refactor: improved the ASCReader code (#1717). @@ -80,17 +83,14 @@ Features * CAN FD Bus Connection for VectorBus: Enabled connecting to CAN FD buses without specifying bus timings, simplifying the connection process for users (#1716). * Neousys Configs Detection: Updated the detection mechanism for available Neousys configurations, ensuring more accurate and comprehensive configuration discovery (#1744). - -Bug Fixes ---------- +### Bug Fixes * Send Periodic Messages: Fixed an issue where fixed-duration periodic messages were sent one extra time beyond their intended count (#1713). * Vector Interface on Windows 11: Addressed compatibility issues with the Vector interface on Windows 11, ensuring stable operation across the latest OS version (#1731). * ASCWriter Millisecond Handling: Corrected the handling of milliseconds in ASCWriter, ensuring accurate time representation in log files (#1734). * Various minor bug fixes: Addressed several minor bugs to improve overall stability and performance. -Miscellaneous -------------- +### Miscellaneous * Invert default value logic for BusABC._is_shutdown. (#1774) * Implemented various logging enhancements to provide more detailed and useful operational insights (#1703). @@ -100,29 +100,27 @@ Miscellaneous The release also includes various other minor enhancements and bug fixes aimed at improving the reliability and performance of the software. -Version 4.3.1 -============= +## Version 4.3.1 + +### Bug Fixes -Bug Fixes ---------- * Fix socketcand erroneously discarding frames (#1700) * Fix initialization order in EtasBus (#1693, #1704) -Documentation -------------- +### Documentation + * Fix install instructions for neovi (#1694, #1697) -Version 4.3.0 -============= +## Version 4.3.0 + +### Breaking Changes -Breaking Changes ----------------- * Raise Minimum Python Version to 3.8 (#1597) * Do not stop notifier if exception was handled (#1645) -Bug Fixes ---------- +### Bug Fixes + * Vector: channel detection fails, if there is an active flexray channel (#1634) * ixxat: Fix exception in 'state' property on bus coupling errors (#1647) * NeoVi: Fixed serial number range (#1650) @@ -133,20 +131,22 @@ Bug Fixes * Vector: Skip the `can_op_mode check` if the device reports `can_op_mode=0` (#1678) * Vector: using the config from `detect_available_configs` might raise XL_ERR_INVALID_CHANNEL_MASK error (#1681) -Features --------- +### Features + +#### API -### API * Add `modifier_callback` parameter to `BusABC.send_periodic` for auto-modifying cyclic tasks (#703) * Add `protocol` property to BusABC to determine active CAN Protocol (#1532) * Change Bus constructor implementation and typing (#1557) * Add optional `strict` parameter to relax BitTiming & BitTimingFd Validation (#1618) * Add `BitTiming.iterate_from_sample_point` static methods (#1671) -### IO +#### IO + * Can Player compatibility with interfaces that use additional configuration (#1610) -### Interface Improvements +#### Interface Improvements + * Kvaser: Add BitTiming/BitTimingFd support to KvaserBus (#1510) * Ixxat: Implement `detect_available_configs` for the Ixxat bus. (#1607) * NeoVi: Enable send and receive on network ID above 255 (#1627) @@ -156,7 +156,8 @@ Features * Kvaser: add parameter exclusive and `override_exclusive` (#1660) * socketcand: Add parameter `tcp_tune` to reduce latency (#1683) -### Miscellaneous +#### Miscellaneous + * Distinguish Text/Binary-IO for Reader/Writer classes. (#1585) * Convert setup.py to pyproject.toml (#1592) * activate ruff pycodestyle checks (#1602) @@ -170,32 +171,29 @@ Features * Add Python 3.12 Support / Test Python 3.12 (#1673) -Version 4.2.2 -============= +## Version 4.2.2 + +### Bug Fixes -Bug Fixes ---------- * Fix socketcan KeyError (#1598, #1599). * Fix IXXAT not properly shutdown message (#1606). * Fix Mf4Reader and TRCReader incompatibility with extra CLI args (#1610). * Fix decoding error in Kvaser constructor for non-ASCII product name (#1613). -Version 4.2.1 -============= +## Version 4.2.1 + +### Bug Fixes -Bug Fixes ---------- * The ASCWriter now logs the correct channel for error frames (#1578, #1583). * Fix PCAN library detection (#1579, #1580). * On Windows, the first two periodic frames were sent without delay (#1590). -Version 4.2.0 -============= +## Version 4.2.0 + +### Breaking Changes -Breaking Changes ----------------- * The ``can.BitTiming`` class was replaced with the new ``can.BitTiming`` and `can.BitTimingFd` classes (#1468, #1515). Early adopters of ``can.BitTiming`` will need to update their code. Check the @@ -210,17 +208,16 @@ Breaking Changes There are open pull requests for kvaser (#1510), slcan (#1512) and usb2can (#1511). Testing and reviewing of these open PRs would be most appreciated. -Features --------- +### Features -### IO +#### IO * Add support for MF4 files (#1289). * Add support for version 2 TRC files and other TRC file enhancements (#1530). -### Type Annotations +#### Type Annotations * Export symbols to satisfy type checkers (#1547, #1551, #1558, #1568). -### Interface Improvements +#### Interface Improvements * Add ``__del__`` method to ``can.BusABC`` to automatically release resources (#1489, #1564). * pcan: Update PCAN Basic to 4.6.2.753 (#1481). * pcan: Use select instead of polling on Linux (#1410). @@ -232,21 +229,21 @@ Features * vector: Only check sample point instead of tseg & sjw (#1486). * vector: add VN5611 hwtype (#1501). -Documentation -------------- +### Documentation + * Add new section about related tools to documentation. Add a list of plugin interface packages (#1457). -Bug Fixes ---------- +### Bug Fixes + * Automatic type conversion for config values (#1498, #1499). * pcan: Fix ``Bus.__new__`` for CAN-FD interfaces (#1458, #1460). * pcan: Fix Detection of Library on Windows on ARM (#1463). * socketcand: extended ID bug fixes (#1504, #1508). * vector: improve robustness against unknown HardwareType values (#1500, #1502). -Deprecations ------------- +### Deprecations + * The ``bustype`` parameter of ``can.Bus`` is deprecated and will be removed in version 5.0, use ``interface`` instead. (#1462). * The ``context`` parameter of ``can.Bus`` is deprecated and will be @@ -258,8 +255,8 @@ Deprecations * The ``brs`` and ``log_errors`` parameters of `` NiXNETcanBus`` are deprecated and will be removed in version 5.0. (#1520). -Miscellaneous -------------- +### Miscellaneous + * Use high resolution timer on Windows to improve timing precision for BroadcastManager (#1449). * Improve ThreadBasedCyclicSendTask timing (#1539). @@ -271,11 +268,9 @@ Miscellaneous * Add deprecation period to utility function ``deprecated_args_alias`` (#1477). * Add `ruff` to the CI system (#1551) -Version 4.1.0 -============= +## Version 4.1.0 -Breaking Changes ----------------- +### Breaking Changes * ``windows-curses`` was moved to optional dependencies (#1395). Use ``pip install python-can[viewer]`` if you are using the ``can.viewer`` @@ -284,11 +279,9 @@ Breaking Changes from camelCase to snake_case (#1422). -Features --------- - -### IO +### Features +#### IO * The canutils logger preserves message direction (#1244) and uses common interface names (e.g. can0) instead of just channel numbers (#1271). @@ -299,11 +292,11 @@ Features and player initialisation (#1366). * Initial support for TRC files (#1217) -### Type Annotations +#### Type Annotations * python-can now includes the ``py.typed`` marker to support type checking according to PEP 561 (#1344). -### Interface Improvements +#### Interface Improvements * The gs_usb interface can be selected by device index instead of USB bus/address. Loopback frames are now correctly marked with the ``is_rx`` flag (#1270). @@ -317,8 +310,7 @@ Features be applied according to the arguments of ``VectorBus.__init__`` (#1426). * Ixxat bus now implements BusState api and detects errors (#1141) -Bug Fixes ---------- +### Bug Fixes * Improve robustness of USB2CAN serial number detection (#1129). * Fix channel2int conversion (#1268, #1269). @@ -340,8 +332,7 @@ Bug Fixes * Raise ValueError if gzip is used with incompatible log formats (#1429). * Allow restarting of transmission tasks for socketcan (#1440) -Miscellaneous -------------- +### Miscellaneous * Allow ICSApiError to be pickled and un-pickled (#1341) * Sort interface names in CLI API to make documentation reproducible (#1342) @@ -351,8 +342,7 @@ Miscellaneous * Migrate code coverage reporting from Codecov to Coveralls (#1430) * Migrate building docs and publishing releases to PyPi from Travis-CI to GitHub Actions (#1433) -Version 4.0.0 -==== +## Version 4.0.0 TL;DR: This release includes a ton of improvements from 2.5 years of development! 🎉 Test thoroughly after switching. @@ -366,8 +356,7 @@ Therefore, users are strongly advised to thoroughly test their programs against Re-reading the documentation for your interfaces might be helpful too as limitations and capabilities might have changed or are more explicit. While we did try to avoid breaking changes, in some cases it was not feasible and in particular, many implementation details have changed. -Major features --------------- +### Major features * Type hints for the core library and some interfaces (#652 and many others) * Support for Python 3.7-3.10+ only (dropped support for Python 2.* and 3.5-3.6) (#528 and many others) @@ -375,8 +364,7 @@ Major features * [Support for automatic configuration detection](https://python-can.readthedocs.io/en/develop/api.html#can.detect_available_configs) in most interfaces (#303, #640, #641, #811, #1077, #1085) * Better alignment of interfaces and IO to common conventions and semantics -New interfaces --------------- +### New interfaces * udp_multicast (#644) * robotell (#731) @@ -387,8 +375,7 @@ New interfaces * socketcand (#1140) * etas (#1144) -Improved interfaces -------------------- +### Improved interfaces * socketcan * Support for multiple Cyclic Messages in Tasks (#610) @@ -465,8 +452,7 @@ Improved interfaces * Fix transmitting onto a busy bus (#1114) * Replace binary library with python driver (#726, #1127) -Other API changes and improvements ----------------------------------- +### Other API changes and improvements * CAN FD frame support is pretty complete (#963) * ASCWriter (#604) and ASCReader (#741) @@ -497,8 +483,7 @@ Other API changes and improvements * Add changed byte highlighting to viewer.py (#1159) * Change DLC to DL in Message.\_\_str\_\_() (#1212) -Other Bugfixes --------------- +### Other Bugfixes * BLF PDU padding (#459) * stop_all_periodic_tasks skipping every other task (#634, #637, #645) @@ -524,8 +509,7 @@ Other Bugfixes * Some smaller bugfixes are not listed here since the problems were never part of a proper release * ASCReader & ASCWriter using DLC as data length (#1245, #1246) -Behind the scenes & Quality assurance -------------------------------------- +### Behind the scenes & Quality assurance * We publish both source distributions (`sdist`) and binary wheels (`bdist_wheel`) (#1059, #1071) * Many interfaces were partly rewritten to modernize the code or to better handle errors @@ -543,16 +527,13 @@ Behind the scenes & Quality assurance * [Good test coverage](https://app.codecov.io/gh/hardbyte/python-can/branch/develop) for all but the interfaces * Testing: Many of the new features directly added tests, and coverage of existing code was improved too (for example: #1031, #581, #585, #586, #942, #1196, #1198) -Version 3.3.4 -==== +## Version 3.3.4 Last call for Python2 support. * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. -Version 3.3.3 -==== - +## Version 3.3.3 Backported fixes from 4.x development branch which targets Python 3. * #798 Backport caching msg.data value in neovi interface. @@ -570,37 +551,30 @@ Backported fixes from 4.x development branch which targets Python 3. * #605 Socketcan BCM status fix. -Version 3.3.2 -==== +## Version 3.3.2 Minor bug fix release addressing issue in PCAN RTR. -Version 3.3.1 -==== +## Version 3.3.1 Minor fix to setup.py to only require pytest-runner when necessary. -Version 3.3.0 -==== +## Version 3.3.0 * Adding CAN FD 64 frame support to blf reader * Updates to installation instructions * Clean up bits generator in PCAN interface #588 * Minor fix to use latest tools when building wheels on travis. -Version 3.2.1 -==== +## Version 3.2.1 * CAN FD 64 frame support to blf reader * Minor fix to use latest tools when building wheels on travis. * Updates links in documentation. -Version 3.2.0 -==== +## Version 3.2.0 - -Major features --------------- +### Major features * FD support added for Pcan by @bmeisels with input from @markuspi, @christiansandberg & @felixdivo in PR #537 @@ -608,8 +582,7 @@ Major features and Python 3.5. Support has been removed for Python 3.4 in this release in PR #532 -Other notable changes ---------------------- +### Other notable changes * #533 BusState is now an enum. * #535 This release should automatically be published to PyPi by travis. @@ -624,26 +597,21 @@ https://github.com/hardbyte/python-can/milestone/7?closed=1 Pulls: #522, #526, #527, #536, #540, #546, #547, #548, #533, #559, #569, #571, #572, #575 -Backend Specific Changes ------------------------- +### Backend Specific Changes -pcan -~~~~ +#### pcan * FD -slcan -~~~~ +#### slcan * ability to set custom can speed instead of using predefined speed values. #553 -socketcan -~~~~ +#### socketcan * Bug fix to properly support 32bit systems. #573 -usb2can -~~~~ +#### usb2can * slightly better error handling * multiple serial devices can be found @@ -651,25 +619,20 @@ usb2can Pulls #511, #535 -vector -~~~~ +#### vector * handle `app_name`. #525 -Version 3.1.1 -==== +## Version 3.1.1 -Major features --------------- +### Major features Two new interfaces this release: - SYSTEC contributed by @idaniel86 in PR #466 - CANalyst-II contributed by @smeng9 in PR #476 - -Other notable changes ---------------------- +### Other notable changes * #477 The kvaser interface now supports bus statistics via a custom bus method. * #434 neovi now supports receiving own messages @@ -686,18 +649,15 @@ Other notable changes * #455 Fix to `Message` initializer * Small bugfixes and improvements -Version 3.1.0 -==== +## Version 3.1.0 Version 3.1.0 was built with old wheel and/or setuptools packages and was replaced with v3.1.1 after an installation but was discovered. -Version 3.0.0 -==== +## Version 3.0.0 -Major features --------------- +### Major features * Adds support for developing `asyncio` applications with `python-can` more easily. This can be useful when implementing protocols that handles simultaneous connections to many nodes since you can write @@ -710,8 +670,7 @@ Major features by calling the bus's new `stop_all_periodic_tasks` method. #412 -Breaking changes ----------------- +### Breaking changes * Interfaces should no longer override `send_periodic` and instead implement `_send_periodic_internal` to allow the Bus base class to manage tasks. #426 @@ -721,8 +680,7 @@ Breaking changes read/writer constructors from `filename` to `file`. -Other notable changes ---------------------- +### Other notable changes * can.Message class updated #413 - Addition of a `Message.equals` method. @@ -754,59 +712,48 @@ Other notable changes General fixes, cleanup and docs changes: (#347, #348, #367, #368, #370, #371, #373, #420, #417, #419, #432) -Backend Specific Changes ------------------------- +### Backend Specific Changes -3rd party interfaces -~~~~~~~~~~~~~~~~~~~~ +#### 3rd party interfaces * Deprecated `python_can.interface` entry point instead use `can.interface`. #389 -neovi -~~~~~ +#### neovi * Added support for CAN-FD #408 * Fix issues checking if bus is open. #381 * Adding multiple channels support. #415 -nican -~~~~~ +#### nican * implements reset instead of custom `flush_tx_buffer`. #364 -pcan -~~~~ +#### pcan * now supported on OSX. #365 - -serial -~~~~~~ +#### serial * Removed TextIOWrapper from serial. #383 * switch to `serial_for_url` enabling using remote ports via `loop://`, ``socket://` and `rfc2217://` URLs. #393 * hardware handshake using `rtscts` kwarg #402 -socketcan -~~~~~~~~~ +#### socketcan * socketcan tasks now reuse a bcm socket #404, #425, #426, * socketcan bugfix to receive error frames #384 -vector -~~~~~~ +#### vector * Vector interface now implements `_detect_available_configs`. #362 * Added support to select device by serial number. #387 -Version 2.2.1 (2018-07-12) -===== +## Version 2.2.1 (2018-07-12) * Fix errors and warnings when importing library on Windows * Fix Vector backend raising ValueError when hardware is not connected -Version 2.2.0 (2018-06-30) -===== +## Version 2.2.0 (2018-06-30) * Fallback message filtering implemented in Python for interfaces that don't offer better accelerated mechanism. * SocketCAN interfaces have been merged (Now use `socketcan` instead of either `socketcan_native` and `socketcan_ctypes`), @@ -817,8 +764,7 @@ Version 2.2.0 (2018-06-30) * Dropped support for Python 3.3 (officially reached end-of-life in Sept. 2017) * Deprecated the old `CAN` module, please use the newer `can` entry point (will be removed in an upcoming major version) -Version 2.1.0 (2018-02-17) -===== +## Version 2.1.0 (2018-02-17) * Support for out of tree can interfaces with pluggy. * Initial support for CAN-FD for socketcan_native and kvaser interfaces. @@ -830,8 +776,7 @@ Version 2.1.0 (2018-02-17) * Other misc improvements and bug fixes -Version 2.0.0 (2018-01-05 -===== +## Version 2.0.0 (2018-01-05) After an extended baking period we have finally tagged version 2.0.0! diff --git a/doc/changelog.d/.gitignore b/doc/changelog.d/.gitignore new file mode 100644 index 000000000..b56b00acb --- /dev/null +++ b/doc/changelog.d/.gitignore @@ -0,0 +1,11 @@ +# Ignore everything... +* +!.gitignore + +# ...except markdown news fragments +!*.security.md +!*.removed.md +!*.deprecated.md +!*.added.md +!*.changed.md +!*.fixed.md diff --git a/doc/changelog.d/1758.added.md b/doc/changelog.d/1758.added.md new file mode 100644 index 000000000..0b95b14e2 --- /dev/null +++ b/doc/changelog.d/1758.added.md @@ -0,0 +1 @@ +Support 11-bit identifiers in the `serial` interface. diff --git a/doc/changelog.d/1851.changed.md b/doc/changelog.d/1851.changed.md new file mode 100644 index 000000000..672f7bd7d --- /dev/null +++ b/doc/changelog.d/1851.changed.md @@ -0,0 +1 @@ +Allow sending Classic CAN frames with a DLC value larger than 8 using the `socketcan` interface. \ No newline at end of file diff --git a/doc/changelog.d/1890.added.md b/doc/changelog.d/1890.added.md new file mode 100644 index 000000000..802629ed3 --- /dev/null +++ b/doc/changelog.d/1890.added.md @@ -0,0 +1 @@ +Keep track of active Notifiers and make Notifier usable as a context manager. Add function `Notifier.find_instances(bus)` to find the active Notifier for a given bus instance. diff --git a/doc/changelog.d/1904.fixed.md b/doc/changelog.d/1904.fixed.md new file mode 100644 index 000000000..80b665a6b --- /dev/null +++ b/doc/changelog.d/1904.fixed.md @@ -0,0 +1 @@ +Fix a bug in `slcanBus.get_version()` and `slcanBus.get_serial_number()`: If any other data was received during the function call, then `None` was returned. \ No newline at end of file diff --git a/doc/changelog.d/1906.fixed.md b/doc/changelog.d/1906.fixed.md new file mode 100644 index 000000000..f8988ff48 --- /dev/null +++ b/doc/changelog.d/1906.fixed.md @@ -0,0 +1 @@ +Fix incorrect padding of CAN FD payload in `BlfReader`. \ No newline at end of file diff --git a/doc/changelog.d/1908.fixed.md b/doc/changelog.d/1908.fixed.md new file mode 100644 index 000000000..ce8947029 --- /dev/null +++ b/doc/changelog.d/1908.fixed.md @@ -0,0 +1 @@ +Set correct message direction for messages received with `kvaser` interface and `receive_own_messages=True`. \ No newline at end of file diff --git a/doc/changelog.d/1914.added.md b/doc/changelog.d/1914.added.md new file mode 100644 index 000000000..a1f838001 --- /dev/null +++ b/doc/changelog.d/1914.added.md @@ -0,0 +1 @@ +Add Windows support to `udp_multicast` interface. \ No newline at end of file diff --git a/doc/changelog.d/1920.added.md b/doc/changelog.d/1920.added.md new file mode 100644 index 000000000..c4f0e532e --- /dev/null +++ b/doc/changelog.d/1920.added.md @@ -0,0 +1 @@ +Add FD support to `slcan` according to CANable 2.0 implementation. diff --git a/doc/changelog.d/1921.fixed.md b/doc/changelog.d/1921.fixed.md new file mode 100644 index 000000000..139d2979f --- /dev/null +++ b/doc/changelog.d/1921.fixed.md @@ -0,0 +1 @@ +Fix timestamp rounding error in `BlfWriter`. diff --git a/doc/changelog.d/1927.fixed.md b/doc/changelog.d/1927.fixed.md new file mode 100644 index 000000000..5fb005d05 --- /dev/null +++ b/doc/changelog.d/1927.fixed.md @@ -0,0 +1 @@ +Fix timestamp rounding error in `BlfReader`. \ No newline at end of file diff --git a/doc/changelog.d/1931.removed.md b/doc/changelog.d/1931.removed.md new file mode 100644 index 000000000..416329a83 --- /dev/null +++ b/doc/changelog.d/1931.removed.md @@ -0,0 +1 @@ +Remove support for Python 3.8. diff --git a/doc/changelog.d/1934.fixed.md b/doc/changelog.d/1934.fixed.md new file mode 100644 index 000000000..a12e4ffb2 --- /dev/null +++ b/doc/changelog.d/1934.fixed.md @@ -0,0 +1 @@ +Handle timer overflow message and build timestamp according to the epoch in the `ixxat` interface. \ No newline at end of file diff --git a/doc/changelog.d/1940.fixed.md b/doc/changelog.d/1940.fixed.md new file mode 100644 index 000000000..9f4fc09ba --- /dev/null +++ b/doc/changelog.d/1940.fixed.md @@ -0,0 +1 @@ +Avoid unsupported `ioctl` function call to allow usage of the `udp_multicast` interface on MacOS. \ No newline at end of file diff --git a/doc/changelog.d/1941.added.md b/doc/changelog.d/1941.added.md new file mode 100644 index 000000000..a3d87cb6b --- /dev/null +++ b/doc/changelog.d/1941.added.md @@ -0,0 +1 @@ +Add support for error messages to the `socketcand` interface. \ No newline at end of file diff --git a/doc/changelog.d/1945.changed.md b/doc/changelog.d/1945.changed.md new file mode 100644 index 000000000..59a48774f --- /dev/null +++ b/doc/changelog.d/1945.changed.md @@ -0,0 +1,2 @@ +The `gs_usb` extra dependency was renamed to `gs-usb`. +The `lint` extra dependency was removed and replaced with new PEP 735 dependency groups `lint`, `docs` and `test`. \ No newline at end of file diff --git a/doc/changelog.d/1946.changed.md b/doc/changelog.d/1946.changed.md new file mode 100644 index 000000000..d5dad4225 --- /dev/null +++ b/doc/changelog.d/1946.changed.md @@ -0,0 +1 @@ +Update dependency name from `zlgcan-driver-py` to `zlgcan`. \ No newline at end of file diff --git a/doc/changelog.d/1947.changed.md b/doc/changelog.d/1947.changed.md new file mode 100644 index 000000000..db12a0318 --- /dev/null +++ b/doc/changelog.d/1947.changed.md @@ -0,0 +1 @@ +Use ThreadPoolExecutor in `detect_available_configs()` to reduce runtime and add `timeout` parameter. \ No newline at end of file diff --git a/doc/changelog.d/1948.added.md b/doc/changelog.d/1948.added.md new file mode 100644 index 000000000..132d49d98 --- /dev/null +++ b/doc/changelog.d/1948.added.md @@ -0,0 +1 @@ +Add support for remote and error frames in the `serial` interface. \ No newline at end of file diff --git a/doc/changelog.d/1949.added.md b/doc/changelog.d/1949.added.md new file mode 100644 index 000000000..6e8ac79b5 --- /dev/null +++ b/doc/changelog.d/1949.added.md @@ -0,0 +1 @@ +Add public functions `can.cli.add_bus_arguments` and `can.cli.create_bus_from_namespace` for creating bus command line options. Currently downstream packages need to implement their own logic to configure *python-can* buses. Now *python-can* can create and parse bus options for third party packages. \ No newline at end of file diff --git a/doc/changelog.d/1951.removed.md b/doc/changelog.d/1951.removed.md new file mode 100644 index 000000000..6d56c5d50 --- /dev/null +++ b/doc/changelog.d/1951.removed.md @@ -0,0 +1 @@ +Remove `can.io.generic.BaseIOHandler` class. Improve `can.io.*` type annotations by using `typing.Generic`. diff --git a/doc/changelog.d/1953.added.md b/doc/changelog.d/1953.added.md new file mode 100644 index 000000000..76ef5137a --- /dev/null +++ b/doc/changelog.d/1953.added.md @@ -0,0 +1 @@ +Add support for remote frames to `TRCReader`. diff --git a/doc/changelog.d/1954.added.md b/doc/changelog.d/1954.added.md new file mode 100644 index 000000000..d2d50669b --- /dev/null +++ b/doc/changelog.d/1954.added.md @@ -0,0 +1 @@ +Mention the `python-can-candle` package in the plugin interface section of the documentation. diff --git a/doc/changelog.d/1957.fixed.md b/doc/changelog.d/1957.fixed.md new file mode 100644 index 000000000..9d5dd6071 --- /dev/null +++ b/doc/changelog.d/1957.fixed.md @@ -0,0 +1 @@ +Fix configuration file parsing for the `state` bus parameter. diff --git a/doc/changelog.d/1960.changed.md b/doc/changelog.d/1960.changed.md new file mode 100644 index 000000000..f3977aedb --- /dev/null +++ b/doc/changelog.d/1960.changed.md @@ -0,0 +1 @@ +Update contribution guide. diff --git a/doc/changelog.d/1961.added.md b/doc/changelog.d/1961.added.md new file mode 100644 index 000000000..483427ec0 --- /dev/null +++ b/doc/changelog.d/1961.added.md @@ -0,0 +1 @@ +Add new CLI tool `python -m can.bridge` (or just `can_bridge`) to create a software bridge between two physical buses. diff --git a/doc/changelog.d/1967.fixed.md b/doc/changelog.d/1967.fixed.md new file mode 100644 index 000000000..fdd72b363 --- /dev/null +++ b/doc/changelog.d/1967.fixed.md @@ -0,0 +1 @@ +Mf4Reader: support non-standard `CAN_DataFrame.Dir` values in mf4 files created by [ihedvall/mdflib](https://github.com/ihedvall/mdflib). diff --git a/doc/development.rst b/doc/development.rst index 97c175ada..40604c346 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -164,7 +164,36 @@ Step-by-Step Contribution Guide Some environments require specific Python versions. If you use `uv`, it will automatically download and manage these for you. -5. **(Optional) Build Source Distribution and Wheels** + + +5. **Add a News Fragment for the Changelog** + + This project uses `towncrier `__ to manage the changelog in + ``CHANGELOG.md``. For every user-facing change (new feature, bugfix, deprecation, etc.), you + must add a news fragment: + + * News fragments are short files describing your change, stored in ``doc/changelog.d``. + * Name each fragment ``..md``, where ```` is one of: + ``added``, ``changed``, ``deprecated``, ``removed``, ``fixed``, or ``security``. + * Example (for a feature added in PR #1234): + + .. code-block:: shell + + echo "Added support for CAN FD." > doc/changelog.d/1234.added.md + + * Or use the towncrier CLI: + + .. code-block:: shell + + uvx towncrier create --dir doc/changelog.d -c "Added support for CAN FD." 1234.added.md + + * For changes not tied to an issue/PR, the fragment name must start with a plus symbol + (e.g., ``+mychange.added.md``). Towncrier calls these "orphan fragments". + + .. note:: You do not need to manually update ``CHANGELOG.md``—maintainers will build the + changelog at release time. + +6. **(Optional) Build Source Distribution and Wheels** If you want to manually build the source distribution (sdist) and wheels for python-can, you can use `uvx` to run the build and twine tools: @@ -174,7 +203,7 @@ Step-by-Step Contribution Guide uv build uvx twine check --strict dist/* -6. **Push and Submit Your Contribution** +7. **Push and Submit Your Contribution** * Push your branch: @@ -218,13 +247,33 @@ These steps are a guideline on how to add a new backend to python-can. Creating a new Release ---------------------- -* Releases are automated via GitHub Actions. To create a new release: +Releases are automated via GitHub Actions. To create a new release: + +* Build the changelog with towncrier: + + + * Collect all news fragments and update ``CHANGELOG.md`` by running: + + .. code-block:: shell + + uvx towncrier build --yes --version vX.Y.Z + + (Replace ``vX.Y.Z`` with the new version number. **The version must exactly match the tag you will create for the release.**) + This will add all news fragments to the changelog and remove the fragments by default. + + .. note:: You can generate the changelog for prereleases, but keep the news + fragments so they are included in the final release. To do this, replace ``--yes`` with ``--keep``. + This will update ``CHANGELOG.md`` but leave the fragments in place for future builds. + + * Review ``CHANGELOG.md`` for accuracy and completeness. - * Ensure all tests pass and documentation is up-to-date. - * Update ``CONTRIBUTORS.txt`` with any new contributors. - * For larger changes, update ``doc/history.rst``. - * Create a new tag and GitHub release (e.g., ``vX.Y.Z``) targeting the ``main`` branch. Add release notes and publish. - * The CI workflow will automatically build, check, and upload the release to PyPI and other platforms. +* Ensure all tests pass and documentation is up-to-date. +* Update ``CONTRIBUTORS.txt`` with any new contributors. +* For larger changes, update ``doc/history.rst``. +* Create a new tag and GitHub release (e.g., ``vX.Y.Z``) targeting the ``main`` + branch. Add release notes and publish. +* The CI workflow will automatically build, check, and upload the release to PyPI + and other platforms. * You can monitor the release status on: `PyPi `__, diff --git a/pyproject.toml b/pyproject.toml index 51bcff693..72e706740 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ docs = [ ] lint = [ "pylint==3.3.*", - "ruff==0.12.5", + "ruff==0.12.7", "black==25.1.*", "mypy==1.17.*", ] @@ -213,3 +213,41 @@ disable = [ "too-many-public-methods", "too-many-statements", ] + +[tool.towncrier] +directory = "doc/changelog.d" +filename = "CHANGELOG.md" +start_string = "\n" +underlines = ["", "", ""] +title_format = "## Version [{version}](https://github.com/hardbyte/python-can/tree/{version}) - {project_date}" +issue_format = "[#{issue}](https://github.com/hardbyte/python-can/issues/{issue})" + +[[tool.towncrier.type]] +directory = "security" +name = "Security" +showcontent = true + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true + +[[tool.towncrier.type]] +directory = "deprecated" +name = "Deprecated" +showcontent = true + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true From 963bbee9a5911f9286a4f6eeace86cd4b56b88ed Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:01:45 +0200 Subject: [PATCH 1207/1235] fix tests to avoid unclosed files and failed PyPy tests (#1968) --- can/io/logger.py | 3 +- test/test_rotating_loggers.py | 103 +++++++++++++++------------------- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/can/io/logger.py b/can/io/logger.py index 9febfe680..4d8ddc070 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -290,7 +290,8 @@ def __exit__( exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: - return self.writer.__exit__(exc_type, exc_val, exc_tb) + self.stop() + return False @abstractmethod def should_rollover(self, msg: Message) -> bool: diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index ab977e3ce..77a2c7f5d 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -29,7 +29,7 @@ def __init__(self, file: StringPathLike, **kwargs) -> None: suffix = Path(file).suffix.lower() if suffix not in self._supported_formats: raise ValueError(f"Unsupported file format: {suffix}") - self._writer = can.Printer(file=file) + self._writer = can.Logger(filename=file) @property def writer(self) -> FileIOMessageWriter: @@ -59,26 +59,20 @@ def test_attributes(self): assert hasattr(can.io.BaseRotatingLogger, "do_rollover") def test_get_new_writer(self, tmp_path): - with self._get_instance(tmp_path / "__unused.txt") as logger_instance: - writer = logger_instance._get_new_writer(tmp_path / "file.ASC") - assert isinstance(writer, can.ASCWriter) - writer.stop() + with self._get_instance(tmp_path / "file.ASC") as logger_instance: + assert isinstance(logger_instance.writer, can.ASCWriter) - writer = logger_instance._get_new_writer(tmp_path / "file.BLF") - assert isinstance(writer, can.BLFWriter) - writer.stop() + with self._get_instance(tmp_path / "file.BLF") as logger_instance: + assert isinstance(logger_instance.writer, can.BLFWriter) - writer = logger_instance._get_new_writer(tmp_path / "file.CSV") - assert isinstance(writer, can.CSVWriter) - writer.stop() + with self._get_instance(tmp_path / "file.CSV") as logger_instance: + assert isinstance(logger_instance.writer, can.CSVWriter) - writer = logger_instance._get_new_writer(tmp_path / "file.LOG") - assert isinstance(writer, can.CanutilsLogWriter) - writer.stop() + with self._get_instance(tmp_path / "file.LOG") as logger_instance: + assert isinstance(logger_instance.writer, can.CanutilsLogWriter) - writer = logger_instance._get_new_writer(tmp_path / "file.TXT") - assert isinstance(writer, can.Printer) - writer.stop() + with self._get_instance(tmp_path / "file.TXT") as logger_instance: + assert isinstance(logger_instance.writer, can.Printer) def test_rotation_filename(self, tmp_path): with self._get_instance(tmp_path / "__unused.txt") as logger_instance: @@ -89,63 +83,61 @@ def test_rotation_filename(self, tmp_path): assert logger_instance.rotation_filename(default_name) == "default_by_namer" def test_rotate_without_rotator(self, tmp_path): - with self._get_instance(tmp_path / "__unused.txt") as logger_instance: - source = str(tmp_path / "source.txt") - dest = str(tmp_path / "dest.txt") + source = str(tmp_path / "source.txt") + dest = str(tmp_path / "dest.txt") - assert os.path.exists(source) is False - assert os.path.exists(dest) is False + assert os.path.exists(source) is False + assert os.path.exists(dest) is False - logger_instance._writer = logger_instance._get_new_writer(source) - logger_instance.stop() + with self._get_instance(source) as logger_instance: + # use context manager to create `source` file and close it + pass - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + assert os.path.exists(source) is True + assert os.path.exists(dest) is False - logger_instance.rotate(source, dest) + logger_instance.rotate(source, dest) - assert os.path.exists(source) is False - assert os.path.exists(dest) is True + assert os.path.exists(source) is False + assert os.path.exists(dest) is True def test_rotate_with_rotator(self, tmp_path): - with self._get_instance(tmp_path / "__unused.txt") as logger_instance: - rotator_func = Mock() - logger_instance.rotator = rotator_func + source = str(tmp_path / "source.txt") + dest = str(tmp_path / "dest.txt") - source = str(tmp_path / "source.txt") - dest = str(tmp_path / "dest.txt") + assert os.path.exists(source) is False + assert os.path.exists(dest) is False - assert os.path.exists(source) is False - assert os.path.exists(dest) is False + with self._get_instance(source) as logger_instance: + # use context manager to create `source` file and close it + pass - logger_instance._writer = logger_instance._get_new_writer(source) - logger_instance.stop() + rotator_func = Mock() + logger_instance.rotator = rotator_func + logger_instance._writer = logger_instance._get_new_writer(source) + logger_instance.stop() - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + assert os.path.exists(source) is True + assert os.path.exists(dest) is False - logger_instance.rotate(source, dest) - rotator_func.assert_called_with(source, dest) + logger_instance.rotate(source, dest) + rotator_func.assert_called_with(source, dest) - # assert that no rotation was performed since rotator_func - # does not do anything - assert os.path.exists(source) is True - assert os.path.exists(dest) is False + # assert that no rotation was performed since rotator_func + # does not do anything + assert os.path.exists(source) is True + assert os.path.exists(dest) is False def test_stop(self, tmp_path): """Test if stop() method of writer is called.""" with self._get_instance(tmp_path / "file.ASC") as logger_instance: # replace stop method of writer with Mock - original_stop = logger_instance.writer.stop - mock_stop = Mock() + mock_stop = Mock(side_effect=logger_instance.writer.stop) logger_instance.writer.stop = mock_stop logger_instance.stop() mock_stop.assert_called() - # close file.ASC to enable cleanup of temp_dir - original_stop() - def test_on_message_received(self, tmp_path): with self._get_instance(tmp_path / "file.ASC") as logger_instance: # Test without rollover @@ -181,12 +173,9 @@ def test_on_message_received(self, tmp_path): writers_on_message_received.assert_called_with(msg) def test_issue_1792(self, tmp_path): - with self._get_instance(tmp_path / "__unused.log") as logger_instance: - writer = logger_instance._get_new_writer( - tmp_path / "2017_Jeep_Grand_Cherokee_3.6L_V6.log" - ) - assert isinstance(writer, can.CanutilsLogWriter) - writer.stop() + filepath = tmp_path / "2017_Jeep_Grand_Cherokee_3.6L_V6.log" + with self._get_instance(filepath) as logger_instance: + assert isinstance(logger_instance.writer, can.CanutilsLogWriter) class TestSizedRotatingLogger: From 54d271844e8841d7e9ef36c91e41cfd3065f21a8 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:55:10 +0200 Subject: [PATCH 1208/1235] set Message.channel in PcanBus.recv (#1969) --- can/interfaces/pcan/pcan.py | 1 + doc/changelog.d/1969.fixed.md | 1 + test/test_pcan.py | 2 ++ 3 files changed, 4 insertions(+) create mode 100644 doc/changelog.d/1969.fixed.md diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index ef3b23e3b..d63981580 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -590,6 +590,7 @@ def _recv_internal( ) rx_msg = Message( + channel=self.channel_info, timestamp=timestamp, arbitration_id=pcan_msg.ID, is_extended_id=is_extended_id, diff --git a/doc/changelog.d/1969.fixed.md b/doc/changelog.d/1969.fixed.md new file mode 100644 index 000000000..3ee6f6c50 --- /dev/null +++ b/doc/changelog.d/1969.fixed.md @@ -0,0 +1 @@ +PcanBus: Set `Message.channel` attribute in `PcanBus.recv()`. diff --git a/test/test_pcan.py b/test/test_pcan.py index 31c541f0a..a9c6ea922 100644 --- a/test/test_pcan.py +++ b/test/test_pcan.py @@ -232,6 +232,7 @@ def test_recv(self): self.assertEqual(recv_msg.is_fd, False) self.assertSequenceEqual(recv_msg.data, msg.DATA) self.assertEqual(recv_msg.timestamp, 0) + self.assertEqual(recv_msg.channel, "PCAN_USBBUS1") def test_recv_fd(self): data = (ctypes.c_ubyte * 64)(*[x for x in range(64)]) @@ -255,6 +256,7 @@ def test_recv_fd(self): self.assertEqual(recv_msg.is_fd, True) self.assertSequenceEqual(recv_msg.data, msg.DATA) self.assertEqual(recv_msg.timestamp, 0) + self.assertEqual(recv_msg.channel, "PCAN_USBBUS1") @pytest.mark.timeout(3.0) @patch("select.select", return_value=([], [], [])) From 380a4239a03649df9b179b2c7cac0238bd0578db Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 9 Aug 2025 01:40:59 +1200 Subject: [PATCH 1209/1235] Create dependabot.yml (#1972) --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..34c1a3a8c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" From df8819f40c20210af6145f51dbae6719fb40640b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:55:16 +0200 Subject: [PATCH 1210/1235] Fix zizmor warnings and update dependabot.yml (#1971) * fix broken url * fix zizmor warnings * update dependabot.yml --- .github/dependabot.yml | 17 ++++++++++++- .github/workflows/ci.yml | 55 +++++++++++++++++++++++++++------------- test/test_socketcan.py | 4 +-- 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 34c1a3a8c..e2781e2ef 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,21 @@ version: 2 updates: - package-ecosystem: "uv" + # Enable version updates for development dependencies directory: "/" schedule: - interval: "weekly" + interval: "monthly" + groups: + dev-deps: + patterns: + - "*" + + - package-ecosystem: "github-actions" + # Enable version updates for GitHub Actions + directory: "/" + schedule: + interval: "monthly" + groups: + github-actions: + patterns: + - "*" \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b799b463e..f85b08d20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,9 @@ on: env: PY_COLORS: "1" +permissions: + contents: read + jobs: test: runs-on: ${{ matrix.os }} @@ -29,9 +32,12 @@ jobs: ] fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # 6.4.3 - name: Install tox run: uv tool install tox --with tox-uv - name: Setup SocketCAN @@ -45,10 +51,10 @@ jobs: tox -e ${{ matrix.env }} env: # SocketCAN tests currently fail with PyPy because it does not support raw CAN sockets - # See: https://foss.heptapod.net/pypy/pypy/-/issues/3809 + # See: https://github.com/pypy/pypy/issues/3808 TEST_SOCKETCAN: "${{ matrix.os == 'ubuntu-latest' && ! startsWith(matrix.env, 'pypy' ) }}" - name: Coveralls Parallel - uses: coverallsapp/github-action@v2 + uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # 2.3.6 with: github-token: ${{ secrets.github_token }} flag-name: Unittests-${{ matrix.os }}-${{ matrix.env }} @@ -59,9 +65,12 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + fetch-depth: 0 + persist-credentials: false - name: Coveralls Finished - uses: coverallsapp/github-action@v2 + uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # 2.3.6 with: github-token: ${{ secrets.github_token }} parallel-finished: true @@ -69,9 +78,12 @@ jobs: static-code-analysis: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # 6.4.3 - name: Install tox run: uv tool install tox --with tox-uv - name: Run linters @@ -84,9 +96,12 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # 6.4.3 - name: Install tox run: uv tool install tox --with tox-uv - name: Build documentation @@ -97,17 +112,18 @@ jobs: name: Packaging runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 with: - fetch-depth: 0 # fetch tags for setuptools-scm + fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # 6.4.3 - name: Build wheel and sdist - run: uvx --from build pyproject-build --installer uv + run: uv build - name: Check build artifacts run: uvx twine check --strict dist/* - name: Save artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2 with: name: release path: ./dist @@ -123,10 +139,15 @@ jobs: # upload to PyPI only on release if: github.event.release && github.event.action == 'published' steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # 4.3.0 with: path: dist merge-multiple: true + - name: Generate artifact attestation + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # 2.4.0 + with: + subject-path: 'dist/*' + - name: Publish release distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # 1.12.4 diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 3df233f96..534ee2a61 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -377,7 +377,7 @@ def test_pypy_socketcan_support(self): This test shall document raw CAN socket support under PyPy. Once this test fails, it is likely that PyPy either implemented raw CAN socket support or at least changed the error that is thrown. - https://foss.heptapod.net/pypy/pypy/-/issues/3809 + https://github.com/pypy/pypy/issues/3808 https://github.com/hardbyte/python-can/issues/1479 """ try: @@ -386,7 +386,7 @@ def test_pypy_socketcan_support(self): if "unknown address family" not in str(e): warnings.warn( "Please check if PyPy has implemented raw CAN socket support! " - "See: https://foss.heptapod.net/pypy/pypy/-/issues/3809" + "See: https://github.com/pypy/pypy/issues/3808" ) From 8ebb9e2d920706fbfe6b52187b6892c7b1533b0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:22:15 +0200 Subject: [PATCH 1211/1235] Bump the dev-deps group with 2 updates (#1976) Updates the requirements on [ruff](https://github.com/astral-sh/ruff) and [hypothesis](https://github.com/HypothesisWorks/hypothesis) to permit the latest version. Updates `ruff` from 0.12.7 to 0.12.8 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.7...0.12.8) Updates `hypothesis` to 6.137.1 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.136.0...hypothesis-python-6.137.1) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: hypothesis dependency-version: 6.137.1 dependency-type: direct:production dependency-group: dev-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 72e706740..52a910067 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ docs = [ ] lint = [ "pylint==3.3.*", - "ruff==0.12.7", + "ruff==0.12.8", "black==25.1.*", "mypy==1.17.*", ] @@ -99,7 +99,7 @@ test = [ "coveralls==4.0.*", "pytest-cov==6.2.*", "coverage==7.10.*", - "hypothesis==6.136.*", + "hypothesis>=6.136,<6.138", "parameterized==0.9.*", ] dev = [ From 9dc14896783d8ee30e49c29785927fb3e8693e2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:22:31 +0200 Subject: [PATCH 1212/1235] Bump actions/download-artifact in the github-actions group (#1975) Bumps the github-actions group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 4.3.0 to 5.0.0 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/d3f86a106a0bac45b974a628896c90dbdf5c8093...634f93cb2916e3fdff6788551b99b062d0335ce0) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f85b08d20..77f1cc1f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,7 +139,7 @@ jobs: # upload to PyPI only on release if: github.event.release && github.event.action == 'published' steps: - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # 4.3.0 + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # 5.0.0 with: path: dist merge-multiple: true From 9c7115146c60b2ae3dc9d23674f61c0802e18f7c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:17:31 +0200 Subject: [PATCH 1213/1235] Generate Changelog for v4.6.0 (#1970) * add python 3.14 classifier * add another fragmet for 1949 * generate changelog for v4.6.0 * dependabot PRs should not trigger workflow twice --- .github/workflows/ci.yml | 2 ++ CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++ doc/changelog.d/1758.added.md | 1 - doc/changelog.d/1851.changed.md | 1 - doc/changelog.d/1890.added.md | 1 - doc/changelog.d/1904.fixed.md | 1 - doc/changelog.d/1906.fixed.md | 1 - doc/changelog.d/1908.fixed.md | 1 - doc/changelog.d/1914.added.md | 1 - doc/changelog.d/1920.added.md | 1 - doc/changelog.d/1921.fixed.md | 1 - doc/changelog.d/1927.fixed.md | 1 - doc/changelog.d/1931.removed.md | 1 - doc/changelog.d/1934.fixed.md | 1 - doc/changelog.d/1940.fixed.md | 1 - doc/changelog.d/1941.added.md | 1 - doc/changelog.d/1945.changed.md | 2 -- doc/changelog.d/1946.changed.md | 1 - doc/changelog.d/1947.changed.md | 1 - doc/changelog.d/1948.added.md | 1 - doc/changelog.d/1949.added.md | 1 - doc/changelog.d/1951.removed.md | 1 - doc/changelog.d/1953.added.md | 1 - doc/changelog.d/1954.added.md | 1 - doc/changelog.d/1957.fixed.md | 1 - doc/changelog.d/1960.changed.md | 1 - doc/changelog.d/1961.added.md | 1 - doc/changelog.d/1967.fixed.md | 1 - doc/changelog.d/1969.fixed.md | 1 - pyproject.toml | 1 + 30 files changed, 46 insertions(+), 28 deletions(-) delete mode 100644 doc/changelog.d/1758.added.md delete mode 100644 doc/changelog.d/1851.changed.md delete mode 100644 doc/changelog.d/1890.added.md delete mode 100644 doc/changelog.d/1904.fixed.md delete mode 100644 doc/changelog.d/1906.fixed.md delete mode 100644 doc/changelog.d/1908.fixed.md delete mode 100644 doc/changelog.d/1914.added.md delete mode 100644 doc/changelog.d/1920.added.md delete mode 100644 doc/changelog.d/1921.fixed.md delete mode 100644 doc/changelog.d/1927.fixed.md delete mode 100644 doc/changelog.d/1931.removed.md delete mode 100644 doc/changelog.d/1934.fixed.md delete mode 100644 doc/changelog.d/1940.fixed.md delete mode 100644 doc/changelog.d/1941.added.md delete mode 100644 doc/changelog.d/1945.changed.md delete mode 100644 doc/changelog.d/1946.changed.md delete mode 100644 doc/changelog.d/1947.changed.md delete mode 100644 doc/changelog.d/1948.added.md delete mode 100644 doc/changelog.d/1949.added.md delete mode 100644 doc/changelog.d/1951.removed.md delete mode 100644 doc/changelog.d/1953.added.md delete mode 100644 doc/changelog.d/1954.added.md delete mode 100644 doc/changelog.d/1957.fixed.md delete mode 100644 doc/changelog.d/1960.changed.md delete mode 100644 doc/changelog.d/1961.added.md delete mode 100644 doc/changelog.d/1967.fixed.md delete mode 100644 doc/changelog.d/1969.fixed.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77f1cc1f9..a80f1e247 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,8 @@ on: types: [ published ] pull_request: push: + branches-ignore: + - 'dependabot/**' env: PY_COLORS: "1" diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ec28f12..8fdbcecf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,49 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang +## Version [v4.6.0](https://github.com/hardbyte/python-can/tree/v4.6.0) - 2025-08-05 + +### Removed + +- Remove support for Python 3.8. ([#1931](https://github.com/hardbyte/python-can/issues/1931)) +- Unknown command line arguments ("extra args") are no longer passed down to `can.Bus()` instantiation. Use the `--bus-kwargs` argument instead. ([#1949](https://github.com/hardbyte/python-can/issues/1949)) +- Remove `can.io.generic.BaseIOHandler` class. Improve `can.io.*` type annotations by using `typing.Generic`. ([#1951](https://github.com/hardbyte/python-can/issues/1951)) + +### Added + +- Support 11-bit identifiers in the `serial` interface. ([#1758](https://github.com/hardbyte/python-can/issues/1758)) +- Keep track of active Notifiers and make Notifier usable as a context manager. Add function `Notifier.find_instances(bus)` to find the active Notifier for a given bus instance. ([#1890](https://github.com/hardbyte/python-can/issues/1890)) +- Add Windows support to `udp_multicast` interface. ([#1914](https://github.com/hardbyte/python-can/issues/1914)) +- Add FD support to `slcan` according to CANable 2.0 implementation. ([#1920](https://github.com/hardbyte/python-can/issues/1920)) +- Add support for error messages to the `socketcand` interface. ([#1941](https://github.com/hardbyte/python-can/issues/1941)) +- Add support for remote and error frames in the `serial` interface. ([#1948](https://github.com/hardbyte/python-can/issues/1948)) +- Add public functions `can.cli.add_bus_arguments` and `can.cli.create_bus_from_namespace` for creating bus command line options. Currently downstream packages need to implement their own logic to configure *python-can* buses. Now *python-can* can create and parse bus options for third party packages. ([#1949](https://github.com/hardbyte/python-can/issues/1949)) +- Add support for remote frames to `TRCReader`. ([#1953](https://github.com/hardbyte/python-can/issues/1953)) +- Mention the `python-can-candle` package in the plugin interface section of the documentation. ([#1954](https://github.com/hardbyte/python-can/issues/1954)) +- Add new CLI tool `python -m can.bridge` (or just `can_bridge`) to create a software bridge between two physical buses. ([#1961](https://github.com/hardbyte/python-can/issues/1961)) + +### Changed + +- Allow sending Classic CAN frames with a DLC value larger than 8 using the `socketcan` interface. ([#1851](https://github.com/hardbyte/python-can/issues/1851)) +- The `gs_usb` extra dependency was renamed to `gs-usb`. + The `lint` extra dependency was removed and replaced with new PEP 735 dependency groups `lint`, `docs` and `test`. ([#1945](https://github.com/hardbyte/python-can/issues/1945)) +- Update dependency name from `zlgcan-driver-py` to `zlgcan`. ([#1946](https://github.com/hardbyte/python-can/issues/1946)) +- Use ThreadPoolExecutor in `detect_available_configs()` to reduce runtime and add `timeout` parameter. ([#1947](https://github.com/hardbyte/python-can/issues/1947)) +- Update contribution guide. ([#1960](https://github.com/hardbyte/python-can/issues/1960)) + +### Fixed + +- Fix a bug in `slcanBus.get_version()` and `slcanBus.get_serial_number()`: If any other data was received during the function call, then `None` was returned. ([#1904](https://github.com/hardbyte/python-can/issues/1904)) +- Fix incorrect padding of CAN FD payload in `BlfReader`. ([#1906](https://github.com/hardbyte/python-can/issues/1906)) +- Set correct message direction for messages received with `kvaser` interface and `receive_own_messages=True`. ([#1908](https://github.com/hardbyte/python-can/issues/1908)) +- Fix timestamp rounding error in `BlfWriter`. ([#1921](https://github.com/hardbyte/python-can/issues/1921)) +- Fix timestamp rounding error in `BlfReader`. ([#1927](https://github.com/hardbyte/python-can/issues/1927)) +- Handle timer overflow message and build timestamp according to the epoch in the `ixxat` interface. ([#1934](https://github.com/hardbyte/python-can/issues/1934)) +- Avoid unsupported `ioctl` function call to allow usage of the `udp_multicast` interface on MacOS. ([#1940](https://github.com/hardbyte/python-can/issues/1940)) +- Fix configuration file parsing for the `state` bus parameter. ([#1957](https://github.com/hardbyte/python-can/issues/1957)) +- Mf4Reader: support non-standard `CAN_DataFrame.Dir` values in mf4 files created by [ihedvall/mdflib](https://github.com/ihedvall/mdflib). ([#1967](https://github.com/hardbyte/python-can/issues/1967)) +- PcanBus: Set `Message.channel` attribute in `PcanBus.recv()`. ([#1969](https://github.com/hardbyte/python-can/issues/1969)) + ## Version 4.5.0 diff --git a/doc/changelog.d/1758.added.md b/doc/changelog.d/1758.added.md deleted file mode 100644 index 0b95b14e2..000000000 --- a/doc/changelog.d/1758.added.md +++ /dev/null @@ -1 +0,0 @@ -Support 11-bit identifiers in the `serial` interface. diff --git a/doc/changelog.d/1851.changed.md b/doc/changelog.d/1851.changed.md deleted file mode 100644 index 672f7bd7d..000000000 --- a/doc/changelog.d/1851.changed.md +++ /dev/null @@ -1 +0,0 @@ -Allow sending Classic CAN frames with a DLC value larger than 8 using the `socketcan` interface. \ No newline at end of file diff --git a/doc/changelog.d/1890.added.md b/doc/changelog.d/1890.added.md deleted file mode 100644 index 802629ed3..000000000 --- a/doc/changelog.d/1890.added.md +++ /dev/null @@ -1 +0,0 @@ -Keep track of active Notifiers and make Notifier usable as a context manager. Add function `Notifier.find_instances(bus)` to find the active Notifier for a given bus instance. diff --git a/doc/changelog.d/1904.fixed.md b/doc/changelog.d/1904.fixed.md deleted file mode 100644 index 80b665a6b..000000000 --- a/doc/changelog.d/1904.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix a bug in `slcanBus.get_version()` and `slcanBus.get_serial_number()`: If any other data was received during the function call, then `None` was returned. \ No newline at end of file diff --git a/doc/changelog.d/1906.fixed.md b/doc/changelog.d/1906.fixed.md deleted file mode 100644 index f8988ff48..000000000 --- a/doc/changelog.d/1906.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix incorrect padding of CAN FD payload in `BlfReader`. \ No newline at end of file diff --git a/doc/changelog.d/1908.fixed.md b/doc/changelog.d/1908.fixed.md deleted file mode 100644 index ce8947029..000000000 --- a/doc/changelog.d/1908.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Set correct message direction for messages received with `kvaser` interface and `receive_own_messages=True`. \ No newline at end of file diff --git a/doc/changelog.d/1914.added.md b/doc/changelog.d/1914.added.md deleted file mode 100644 index a1f838001..000000000 --- a/doc/changelog.d/1914.added.md +++ /dev/null @@ -1 +0,0 @@ -Add Windows support to `udp_multicast` interface. \ No newline at end of file diff --git a/doc/changelog.d/1920.added.md b/doc/changelog.d/1920.added.md deleted file mode 100644 index c4f0e532e..000000000 --- a/doc/changelog.d/1920.added.md +++ /dev/null @@ -1 +0,0 @@ -Add FD support to `slcan` according to CANable 2.0 implementation. diff --git a/doc/changelog.d/1921.fixed.md b/doc/changelog.d/1921.fixed.md deleted file mode 100644 index 139d2979f..000000000 --- a/doc/changelog.d/1921.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix timestamp rounding error in `BlfWriter`. diff --git a/doc/changelog.d/1927.fixed.md b/doc/changelog.d/1927.fixed.md deleted file mode 100644 index 5fb005d05..000000000 --- a/doc/changelog.d/1927.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix timestamp rounding error in `BlfReader`. \ No newline at end of file diff --git a/doc/changelog.d/1931.removed.md b/doc/changelog.d/1931.removed.md deleted file mode 100644 index 416329a83..000000000 --- a/doc/changelog.d/1931.removed.md +++ /dev/null @@ -1 +0,0 @@ -Remove support for Python 3.8. diff --git a/doc/changelog.d/1934.fixed.md b/doc/changelog.d/1934.fixed.md deleted file mode 100644 index a12e4ffb2..000000000 --- a/doc/changelog.d/1934.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Handle timer overflow message and build timestamp according to the epoch in the `ixxat` interface. \ No newline at end of file diff --git a/doc/changelog.d/1940.fixed.md b/doc/changelog.d/1940.fixed.md deleted file mode 100644 index 9f4fc09ba..000000000 --- a/doc/changelog.d/1940.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Avoid unsupported `ioctl` function call to allow usage of the `udp_multicast` interface on MacOS. \ No newline at end of file diff --git a/doc/changelog.d/1941.added.md b/doc/changelog.d/1941.added.md deleted file mode 100644 index a3d87cb6b..000000000 --- a/doc/changelog.d/1941.added.md +++ /dev/null @@ -1 +0,0 @@ -Add support for error messages to the `socketcand` interface. \ No newline at end of file diff --git a/doc/changelog.d/1945.changed.md b/doc/changelog.d/1945.changed.md deleted file mode 100644 index 59a48774f..000000000 --- a/doc/changelog.d/1945.changed.md +++ /dev/null @@ -1,2 +0,0 @@ -The `gs_usb` extra dependency was renamed to `gs-usb`. -The `lint` extra dependency was removed and replaced with new PEP 735 dependency groups `lint`, `docs` and `test`. \ No newline at end of file diff --git a/doc/changelog.d/1946.changed.md b/doc/changelog.d/1946.changed.md deleted file mode 100644 index d5dad4225..000000000 --- a/doc/changelog.d/1946.changed.md +++ /dev/null @@ -1 +0,0 @@ -Update dependency name from `zlgcan-driver-py` to `zlgcan`. \ No newline at end of file diff --git a/doc/changelog.d/1947.changed.md b/doc/changelog.d/1947.changed.md deleted file mode 100644 index db12a0318..000000000 --- a/doc/changelog.d/1947.changed.md +++ /dev/null @@ -1 +0,0 @@ -Use ThreadPoolExecutor in `detect_available_configs()` to reduce runtime and add `timeout` parameter. \ No newline at end of file diff --git a/doc/changelog.d/1948.added.md b/doc/changelog.d/1948.added.md deleted file mode 100644 index 132d49d98..000000000 --- a/doc/changelog.d/1948.added.md +++ /dev/null @@ -1 +0,0 @@ -Add support for remote and error frames in the `serial` interface. \ No newline at end of file diff --git a/doc/changelog.d/1949.added.md b/doc/changelog.d/1949.added.md deleted file mode 100644 index 6e8ac79b5..000000000 --- a/doc/changelog.d/1949.added.md +++ /dev/null @@ -1 +0,0 @@ -Add public functions `can.cli.add_bus_arguments` and `can.cli.create_bus_from_namespace` for creating bus command line options. Currently downstream packages need to implement their own logic to configure *python-can* buses. Now *python-can* can create and parse bus options for third party packages. \ No newline at end of file diff --git a/doc/changelog.d/1951.removed.md b/doc/changelog.d/1951.removed.md deleted file mode 100644 index 6d56c5d50..000000000 --- a/doc/changelog.d/1951.removed.md +++ /dev/null @@ -1 +0,0 @@ -Remove `can.io.generic.BaseIOHandler` class. Improve `can.io.*` type annotations by using `typing.Generic`. diff --git a/doc/changelog.d/1953.added.md b/doc/changelog.d/1953.added.md deleted file mode 100644 index 76ef5137a..000000000 --- a/doc/changelog.d/1953.added.md +++ /dev/null @@ -1 +0,0 @@ -Add support for remote frames to `TRCReader`. diff --git a/doc/changelog.d/1954.added.md b/doc/changelog.d/1954.added.md deleted file mode 100644 index d2d50669b..000000000 --- a/doc/changelog.d/1954.added.md +++ /dev/null @@ -1 +0,0 @@ -Mention the `python-can-candle` package in the plugin interface section of the documentation. diff --git a/doc/changelog.d/1957.fixed.md b/doc/changelog.d/1957.fixed.md deleted file mode 100644 index 9d5dd6071..000000000 --- a/doc/changelog.d/1957.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix configuration file parsing for the `state` bus parameter. diff --git a/doc/changelog.d/1960.changed.md b/doc/changelog.d/1960.changed.md deleted file mode 100644 index f3977aedb..000000000 --- a/doc/changelog.d/1960.changed.md +++ /dev/null @@ -1 +0,0 @@ -Update contribution guide. diff --git a/doc/changelog.d/1961.added.md b/doc/changelog.d/1961.added.md deleted file mode 100644 index 483427ec0..000000000 --- a/doc/changelog.d/1961.added.md +++ /dev/null @@ -1 +0,0 @@ -Add new CLI tool `python -m can.bridge` (or just `can_bridge`) to create a software bridge between two physical buses. diff --git a/doc/changelog.d/1967.fixed.md b/doc/changelog.d/1967.fixed.md deleted file mode 100644 index fdd72b363..000000000 --- a/doc/changelog.d/1967.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Mf4Reader: support non-standard `CAN_DataFrame.Dir` values in mf4 files created by [ihedvall/mdflib](https://github.com/ihedvall/mdflib). diff --git a/doc/changelog.d/1969.fixed.md b/doc/changelog.d/1969.fixed.md deleted file mode 100644 index 3ee6f6c50..000000000 --- a/doc/changelog.d/1969.fixed.md +++ /dev/null @@ -1 +0,0 @@ -PcanBus: Set `Message.channel` attribute in `PcanBus.recv()`. diff --git a/pyproject.toml b/pyproject.toml index 52a910067..e36b50ad0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Embedded Systems", From c0e5a8ed9f92eff10415d8c4d59d8d38fb2a0b99 Mon Sep 17 00:00:00 2001 From: David Charles Ambler Snowdon <3860429+dcasnowdon@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:52:28 +1000 Subject: [PATCH 1214/1235] Resolve an unset data_bitrate to no operation in slcan. (#1978) * Change the default to 0, which is a no-op for data_bitrate. * Added a changelog fragment. * Fix black formatting for test_slcan.py * Revert the function signature, and explicitly check for `None` in data_bitrate. * Update the town crier news fragment. --- can/interfaces/slcan.py | 5 +++++ doc/changelog.d/1978.fixed.md | 1 + test/test_slcan.py | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 doc/changelog.d/1978.fixed.md diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 01ba9c995..d9eab5edf 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -180,6 +180,11 @@ def set_bitrate(self, bitrate: int, data_bitrate: Optional[int] = None) -> None: else: bitrates = ", ".join(str(k) for k in self._BITRATES.keys()) raise ValueError(f"Invalid bitrate, choose one of {bitrates}.") + + # If data_bitrate is None, we set it to 0 which means no data bitrate + if data_bitrate is None: + data_bitrate = 0 + if data_bitrate in self._DATA_BITRATES: dbitrate_code = self._DATA_BITRATES[data_bitrate] else: diff --git a/doc/changelog.d/1978.fixed.md b/doc/changelog.d/1978.fixed.md new file mode 100644 index 000000000..1de50bb60 --- /dev/null +++ b/doc/changelog.d/1978.fixed.md @@ -0,0 +1 @@ +Fix initialisation of an slcan bus, when setting a bitrate. When using CAN 2.0 (not FD), the default setting for `data_bitrate` was invalid, causing an exception. \ No newline at end of file diff --git a/test/test_slcan.py b/test/test_slcan.py index 491800e24..b757ad04d 100644 --- a/test/test_slcan.py +++ b/test/test_slcan.py @@ -74,7 +74,13 @@ class slcanTestCase(unittest.TestCase): def setUp(self): self.bus = cast( can.interfaces.slcan.slcanBus, - can.Bus("loop://", interface="slcan", sleep_after_open=0, timeout=TIMEOUT), + can.Bus( + "loop://", + interface="slcan", + sleep_after_open=0, + timeout=TIMEOUT, + bitrate=500000, + ), ) self.serial = cast(SerialMock, self.bus.serialPortOrig) self.serial.reset_input_buffer() From 702ec3eb79530b9242a1167c049cfe2cfec235e5 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:32:01 +0200 Subject: [PATCH 1215/1235] prepare CHANGELOG.md for v4.6.1 (#1979) --- CHANGELOG.md | 7 +++++++ README.rst | 4 ++-- doc/changelog.d/1978.fixed.md | 1 - 3 files changed, 9 insertions(+), 3 deletions(-) delete mode 100644 doc/changelog.d/1978.fixed.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fdbcecf1..75ecab49e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang +## Version [v4.6.1](https://github.com/hardbyte/python-can/tree/v4.6.1) - 2025-08-12 + +### Fixed + +- Fix initialisation of an slcan bus, when setting a bitrate. When using CAN 2.0 (not FD), the default setting for `data_bitrate` was invalid, causing an exception. ([#1978](https://github.com/hardbyte/python-can/issues/1978)) + + ## Version [v4.6.0](https://github.com/hardbyte/python-can/tree/v4.6.0) - 2025-08-05 ### Removed diff --git a/README.rst b/README.rst index 3c185f6cb..2579871b9 100644 --- a/README.rst +++ b/README.rst @@ -37,8 +37,8 @@ python-can :target: https://github.com/hardbyte/python-can/actions/workflows/ci.yml :alt: Github Actions workflow status -.. |coverage| image:: https://coveralls.io/repos/github/hardbyte/python-can/badge.svg?branch=develop - :target: https://coveralls.io/github/hardbyte/python-can?branch=develop +.. |coverage| image:: https://coveralls.io/repos/github/hardbyte/python-can/badge.svg?branch=main + :target: https://coveralls.io/github/hardbyte/python-can?branch=main :alt: Test coverage reports on Coveralls.io The **C**\ ontroller **A**\ rea **N**\ etwork is a bus standard designed diff --git a/doc/changelog.d/1978.fixed.md b/doc/changelog.d/1978.fixed.md deleted file mode 100644 index 1de50bb60..000000000 --- a/doc/changelog.d/1978.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix initialisation of an slcan bus, when setting a bitrate. When using CAN 2.0 (not FD), the default setting for `data_bitrate` was invalid, causing an exception. \ No newline at end of file From bc248e8aaf96280a574c06e8e7d2778a67f091e3 Mon Sep 17 00:00:00 2001 From: Ben Beasley Date: Tue, 12 Aug 2025 19:10:35 -0400 Subject: [PATCH 1216/1235] Allow wrapt 2.x (#1980) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e36b50ad0..e125ea84f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["readme", "version"] description = "Controller Area Network interface module for Python" authors = [{ name = "python-can contributors" }] dependencies = [ - "wrapt~=1.10", + "wrapt >= 1.10, < 3", "packaging >= 23.1", "typing_extensions>=3.10.0.0", ] From 921b47ecb97f07f1012032fe6740941a68116a70 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:57:40 +0200 Subject: [PATCH 1217/1235] keep a reference to asyncio tasks (#1982) --- can/notifier.py | 7 +++++-- doc/changelog.d/1938.fixed.md | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 doc/changelog.d/1938.fixed.md diff --git a/can/notifier.py b/can/notifier.py index b2f550df7..a2ee512fc 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -148,6 +148,7 @@ def __init__( self._lock = threading.Lock() self._readers: list[Union[int, threading.Thread]] = [] + self._tasks: set[asyncio.Task] = set() _bus_list: list[BusABC] = bus if isinstance(bus, list) else [bus] for each_bus in _bus_list: self.add_bus(each_bus) @@ -256,8 +257,10 @@ def _on_message_received(self, msg: Message) -> None: for callback in self.listeners: res = callback(msg) if res and self._loop and asyncio.iscoroutine(res): - # Schedule coroutine - self._loop.create_task(res) + # Schedule coroutine and keep a reference to the task + task = self._loop.create_task(res) + self._tasks.add(task) + task.add_done_callback(self._tasks.discard) def _on_error(self, exc: Exception) -> bool: """Calls ``on_error()`` for all listeners if they implement it. diff --git a/doc/changelog.d/1938.fixed.md b/doc/changelog.d/1938.fixed.md new file mode 100644 index 000000000..f9aad1089 --- /dev/null +++ b/doc/changelog.d/1938.fixed.md @@ -0,0 +1 @@ +Keep a reference to asyncio tasks in `can.Notifier` as recommended by [python documentation](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task). From efa9f0d401ae0f800429dd58385431deda604609 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:27:47 +0200 Subject: [PATCH 1218/1235] Ensure that all virtual channels are closed after tests (#1984) --- test/back2back_test.py | 14 +-- test/conftest.py | 24 ++++ test/notifier_test.py | 36 +++--- test/simplecyclic_test.py | 224 +++++++++++++++++++------------------- test/test_bus.py | 8 +- test/zero_dlc_test.py | 45 ++++---- 6 files changed, 187 insertions(+), 164 deletions(-) create mode 100644 test/conftest.py diff --git a/test/back2back_test.py b/test/back2back_test.py index b52bae530..738c9d16b 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -164,37 +164,33 @@ def test_message_is_rx(self): ) def test_message_is_rx_receive_own_messages(self): """The same as `test_message_direction` but testing with `receive_own_messages=True`.""" - bus3 = can.Bus( + with can.Bus( channel=self.CHANNEL_2, interface=self.INTERFACE_2, bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True, receive_own_messages=True, - ) - try: + ) as bus3: msg = can.Message( is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3], is_rx=False ) bus3.send(msg) self_recv_msg_bus3 = bus3.recv(self.TIMEOUT) self.assertTrue(self_recv_msg_bus3.is_rx) - finally: - bus3.shutdown() def test_unique_message_instances(self): """Verify that we have a different instances of message for each bus even with `receive_own_messages=True`. """ - bus3 = can.Bus( + with can.Bus( channel=self.CHANNEL_2, interface=self.INTERFACE_2, bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True, receive_own_messages=True, - ) - try: + ) as bus3: msg = can.Message( is_extended_id=False, arbitration_id=0x300, data=[2, 1, 3] ) @@ -209,8 +205,6 @@ def test_unique_message_instances(self): recv_msg_bus1.data[0] = 4 self.assertNotEqual(recv_msg_bus1.data, recv_msg_bus2.data) self.assertEqual(recv_msg_bus2.data, self_recv_msg_bus3.data) - finally: - bus3.shutdown() def test_fd_message(self): msg = can.Message( diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 000000000..c54238be1 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,24 @@ +import pytest + +from can.interfaces import virtual + + +@pytest.fixture(autouse=True) +def check_unclosed_virtual_channel(): + """ + Pytest fixture for detecting leaked virtual CAN channels. + + - The fixture yields control to the test. + - After the test completes, it acquires `virtual.channels_lock` and asserts + that `virtual.channels` is empty. + - If a test leaves behind any unclosed virtual CAN channels, the assertion + will fail, surfacing resource leaks early. + + This helps maintain test isolation and prevents subtle bugs caused by + leftover state between tests. + """ + + yield + + with virtual.channels_lock: + assert len(virtual.channels) == 0 diff --git a/test/notifier_test.py b/test/notifier_test.py index c21d51f04..d8512a00b 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -20,23 +20,25 @@ def test_single_bus(self): self.assertTrue(notifier.stopped) def test_multiple_bus(self): - with can.Bus(0, interface="virtual", receive_own_messages=True) as bus1: - with can.Bus(1, interface="virtual", receive_own_messages=True) as bus2: - reader = can.BufferedReader() - notifier = can.Notifier([bus1, bus2], [reader], 0.1) - self.assertFalse(notifier.stopped) - msg = can.Message() - bus1.send(msg) - time.sleep(0.1) - bus2.send(msg) - recv_msg = reader.get_message(1) - self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 0) - recv_msg = reader.get_message(1) - self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 1) - notifier.stop() - self.assertTrue(notifier.stopped) + with ( + can.Bus(0, interface="virtual", receive_own_messages=True) as bus1, + can.Bus(1, interface="virtual", receive_own_messages=True) as bus2, + ): + reader = can.BufferedReader() + notifier = can.Notifier([bus1, bus2], [reader], 0.1) + self.assertFalse(notifier.stopped) + msg = can.Message() + bus1.send(msg) + time.sleep(0.1) + bus2.send(msg) + recv_msg = reader.get_message(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 0) + recv_msg = reader.get_message(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 1) + notifier.stop() + self.assertTrue(notifier.stopped) def test_context_manager(self): with can.Bus("test", interface="virtual", receive_own_messages=True) as bus: diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index c4c1a2340..91bbf3bbe 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -36,123 +36,120 @@ def test_cycle_time(self): is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] ) - with can.interface.Bus(interface="virtual") as bus1: - with can.interface.Bus(interface="virtual") as bus2: - # disabling the garbage collector makes the time readings more reliable - gc.disable() - - task = bus1.send_periodic(msg, 0.01, 1) - self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + with ( + can.interface.Bus(interface="virtual") as bus1, + can.interface.Bus(interface="virtual") as bus2, + ): + # disabling the garbage collector makes the time readings more reliable + gc.disable() + + task = bus1.send_periodic(msg, 0.01, 1) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) - sleep(2) - size = bus2.queue.qsize() - # About 100 messages should have been transmitted - self.assertTrue( - 80 <= size <= 120, - "100 +/- 20 messages should have been transmitted. But queue contained {}".format( - size - ), - ) - last_msg = bus2.recv() - next_last_msg = bus2.recv() + sleep(2) + size = bus2.queue.qsize() + # About 100 messages should have been transmitted + self.assertTrue( + 80 <= size <= 120, + "100 +/- 20 messages should have been transmitted. But queue contained {}".format( + size + ), + ) + last_msg = bus2.recv() + next_last_msg = bus2.recv() - # we need to reenable the garbage collector again - gc.enable() + # we need to reenable the garbage collector again + gc.enable() - # Check consecutive messages are spaced properly in time and have - # the same id/data - self.assertMessageEqual(last_msg, next_last_msg) + # Check consecutive messages are spaced properly in time and have + # the same id/data + self.assertMessageEqual(last_msg, next_last_msg) - # Check the message id/data sent is the same as message received - # Set timestamp and channel to match recv'd because we don't care - # and they are not initialized by the can.Message constructor. - msg.timestamp = last_msg.timestamp - msg.channel = last_msg.channel - self.assertMessageEqual(msg, last_msg) + # Check the message id/data sent is the same as message received + # Set timestamp and channel to match recv'd because we don't care + # and they are not initialized by the can.Message constructor. + msg.timestamp = last_msg.timestamp + msg.channel = last_msg.channel + self.assertMessageEqual(msg, last_msg) def test_removing_bus_tasks(self): - bus = can.interface.Bus(interface="virtual") - tasks = [] - for task_i in range(10): - msg = can.Message( - is_extended_id=False, - arbitration_id=0x123, - data=[0, 1, 2, 3, 4, 5, 6, 7], - ) - msg.arbitration_id = task_i - task = bus.send_periodic(msg, 0.1, 1) - tasks.append(task) - self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + with can.interface.Bus(interface="virtual") as bus: + tasks = [] + for task_i in range(10): + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) + msg.arbitration_id = task_i + task = bus.send_periodic(msg, 0.1, 1) + tasks.append(task) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) - assert len(bus._periodic_tasks) == 10 + assert len(bus._periodic_tasks) == 10 - for task in tasks: - # Note calling task.stop will remove the task from the Bus's internal task management list - task.stop() + for task in tasks: + # Note calling task.stop will remove the task from the Bus's internal task management list + task.stop() - self.join_threads([task.thread for task in tasks], 5.0) + self.join_threads([task.thread for task in tasks], 5.0) - assert len(bus._periodic_tasks) == 0 - bus.shutdown() + assert len(bus._periodic_tasks) == 0 def test_managed_tasks(self): - bus = can.interface.Bus(interface="virtual", receive_own_messages=True) - tasks = [] - for task_i in range(3): - msg = can.Message( - is_extended_id=False, - arbitration_id=0x123, - data=[0, 1, 2, 3, 4, 5, 6, 7], - ) - msg.arbitration_id = task_i - task = bus.send_periodic(msg, 0.1, 10, store_task=False) - tasks.append(task) - self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) - - assert len(bus._periodic_tasks) == 0 + with can.interface.Bus(interface="virtual", receive_own_messages=True) as bus: + tasks = [] + for task_i in range(3): + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) + msg.arbitration_id = task_i + task = bus.send_periodic(msg, 0.1, 10, store_task=False) + tasks.append(task) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) - # Self managed tasks should still be sending messages - for _ in range(50): - received_msg = bus.recv(timeout=5.0) - assert received_msg is not None - assert received_msg.arbitration_id in {0, 1, 2} + assert len(bus._periodic_tasks) == 0 - for task in tasks: - task.stop() + # Self managed tasks should still be sending messages + for _ in range(50): + received_msg = bus.recv(timeout=5.0) + assert received_msg is not None + assert received_msg.arbitration_id in {0, 1, 2} - self.join_threads([task.thread for task in tasks], 5.0) + for task in tasks: + task.stop() - bus.shutdown() + self.join_threads([task.thread for task in tasks], 5.0) def test_stopping_perodic_tasks(self): - bus = can.interface.Bus(interface="virtual") - tasks = [] - for task_i in range(10): - msg = can.Message( - is_extended_id=False, - arbitration_id=0x123, - data=[0, 1, 2, 3, 4, 5, 6, 7], - ) - msg.arbitration_id = task_i - task = bus.send_periodic(msg, 0.1, 1) - tasks.append(task) - - assert len(bus._periodic_tasks) == 10 - # stop half the tasks using the task object - for task in tasks[::2]: - task.stop() + with can.interface.Bus(interface="virtual") as bus: + tasks = [] + for task_i in range(10): + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) + msg.arbitration_id = task_i + task = bus.send_periodic(msg, 0.1, 1) + tasks.append(task) - assert len(bus._periodic_tasks) == 5 + assert len(bus._periodic_tasks) == 10 + # stop half the tasks using the task object + for task in tasks[::2]: + task.stop() - # stop the other half using the bus api - bus.stop_all_periodic_tasks(remove_tasks=False) - self.join_threads([task.thread for task in tasks], 5.0) + assert len(bus._periodic_tasks) == 5 - # Tasks stopped via `stop_all_periodic_tasks` with remove_tasks=False should - # still be associated with the bus (e.g. for restarting) - assert len(bus._periodic_tasks) == 5 + # stop the other half using the bus api + bus.stop_all_periodic_tasks(remove_tasks=False) + self.join_threads([task.thread for task in tasks], 5.0) - bus.shutdown() + # Tasks stopped via `stop_all_periodic_tasks` with remove_tasks=False should + # still be associated with the bus (e.g. for restarting) + assert len(bus._periodic_tasks) == 5 def test_restart_perodic_tasks(self): period = 0.01 @@ -214,25 +211,26 @@ def _read_all_messages(_bus: "can.interfaces.virtual.VirtualBus") -> None: @unittest.skipIf(IS_CI, "fails randomly when run on CI server") def test_thread_based_cyclic_send_task(self): - bus = can.ThreadSafeBus(interface="virtual") - msg = can.Message( - is_extended_id=False, arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7] - ) + with can.ThreadSafeBus(interface="virtual") as bus: + msg = can.Message( + is_extended_id=False, + arbitration_id=0x123, + data=[0, 1, 2, 3, 4, 5, 6, 7], + ) - # good case, bus is up - on_error_mock = MagicMock(return_value=False) - task = can.broadcastmanager.ThreadBasedCyclicSendTask( - bus=bus, - lock=bus._lock_send_periodic, - messages=msg, - period=0.1, - duration=3, - on_error=on_error_mock, - ) - sleep(1) - on_error_mock.assert_not_called() - task.stop() - bus.shutdown() + # good case, bus is up + on_error_mock = MagicMock(return_value=False) + task = can.broadcastmanager.ThreadBasedCyclicSendTask( + bus=bus, + lock=bus._lock_send_periodic, + messages=msg, + period=0.1, + duration=3, + on_error=on_error_mock, + ) + sleep(1) + on_error_mock.assert_not_called() + task.stop() # bus has been shut down on_error_mock = MagicMock(return_value=False) diff --git a/test/test_bus.py b/test/test_bus.py index 24421b2fd..6a09a6deb 100644 --- a/test/test_bus.py +++ b/test/test_bus.py @@ -8,11 +8,11 @@ def test_bus_ignore_config(): with patch.object( target=can.util, attribute="load_config", side_effect=can.util.load_config ): - _ = can.Bus(interface="virtual", ignore_config=True) - assert not can.util.load_config.called + with can.Bus(interface="virtual", ignore_config=True): + assert not can.util.load_config.called - _ = can.Bus(interface="virtual") - assert can.util.load_config.called + with can.Bus(interface="virtual"): + assert can.util.load_config.called @patch.object(can.bus.BusABC, "shutdown") diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py index d6693e294..e8ae8c293 100644 --- a/test/zero_dlc_test.py +++ b/test/zero_dlc_test.py @@ -12,35 +12,40 @@ class ZeroDLCTest(unittest.TestCase): def test_recv_non_zero_dlc(self): - bus_send = can.interface.Bus(interface="virtual") - bus_recv = can.interface.Bus(interface="virtual") - data = [0, 1, 2, 3, 4, 5, 6, 7] - msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=data) + with ( + can.interface.Bus(interface="virtual") as bus_send, + can.interface.Bus(interface="virtual") as bus_recv, + ): + data = [0, 1, 2, 3, 4, 5, 6, 7] + msg_send = can.Message( + is_extended_id=False, arbitration_id=0x100, data=data + ) - bus_send.send(msg_send) - msg_recv = bus_recv.recv() + bus_send.send(msg_send) + msg_recv = bus_recv.recv() - # Receiving a frame with data should evaluate msg_recv to True - self.assertTrue(msg_recv) + # Receiving a frame with data should evaluate msg_recv to True + self.assertTrue(msg_recv) def test_recv_none(self): - bus_recv = can.interface.Bus(interface="virtual") + with can.interface.Bus(interface="virtual") as bus_recv: + msg_recv = bus_recv.recv(timeout=0) - msg_recv = bus_recv.recv(timeout=0) - - # Receiving nothing should evaluate msg_recv to False - self.assertFalse(msg_recv) + # Receiving nothing should evaluate msg_recv to False + self.assertFalse(msg_recv) def test_recv_zero_dlc(self): - bus_send = can.interface.Bus(interface="virtual") - bus_recv = can.interface.Bus(interface="virtual") - msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=[]) + with ( + can.interface.Bus(interface="virtual") as bus_send, + can.interface.Bus(interface="virtual") as bus_recv, + ): + msg_send = can.Message(is_extended_id=False, arbitration_id=0x100, data=[]) - bus_send.send(msg_send) - msg_recv = bus_recv.recv() + bus_send.send(msg_send) + msg_recv = bus_recv.recv() - # Receiving a frame without data (dlc == 0) should evaluate msg_recv to True - self.assertTrue(msg_recv) + # Receiving a frame without data (dlc == 0) should evaluate msg_recv to True + self.assertTrue(msg_recv) if __name__ == "__main__": From b6280b1d8a5fc3c17e3a6f888b41092733d563d7 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:09:10 +0200 Subject: [PATCH 1219/1235] Improve PyPy test robustness (#1985) * increase timeout for PyPy * remove duration arg --- test/back2back_test.py | 2 +- test/simplecyclic_test.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 738c9d16b..ce7c39e2a 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -33,7 +33,7 @@ class Back2BackTestCase(unittest.TestCase): """ BITRATE = 500000 - TIMEOUT = 0.1 + TIMEOUT = 1.0 if IS_PYPY else 0.1 INTERFACE_1 = "virtual" CHANNEL_1 = "virtual_channel_0" diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 91bbf3bbe..22a11e643 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -5,18 +5,18 @@ """ import gc +import platform import sys import time import traceback import unittest from threading import Thread from time import sleep -from typing import List from unittest.mock import MagicMock import can -from .config import * +from .config import IS_CI, IS_PYPY from .message_helper import ComparingMessagesTestCase @@ -133,7 +133,7 @@ def test_stopping_perodic_tasks(self): data=[0, 1, 2, 3, 4, 5, 6, 7], ) msg.arbitration_id = task_i - task = bus.send_periodic(msg, 0.1, 1) + task = bus.send_periodic(msg, period=0.1) tasks.append(task) assert len(bus._periodic_tasks) == 10 @@ -261,7 +261,7 @@ def test_thread_based_cyclic_send_task(self): task.stop() def test_modifier_callback(self) -> None: - msg_list: List[can.Message] = [] + msg_list: list[can.Message] = [] def increment_first_byte(msg: can.Message) -> None: msg.data[0] = (msg.data[0] + 1) % 256 @@ -288,8 +288,8 @@ def increment_first_byte(msg: can.Message) -> None: self.assertEqual(b"\x07\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[6].data)) @staticmethod - def join_threads(threads: List[Thread], timeout: float) -> None: - stuck_threads: List[Thread] = [] + def join_threads(threads: list[Thread], timeout: float) -> None: + stuck_threads: list[Thread] = [] t0 = time.perf_counter() for thread in threads: time_left = timeout - (time.perf_counter() - t0) From e142868ee494414e6079b1a5e4fec56196aacc94 Mon Sep 17 00:00:00 2001 From: Gerrit Beine Date: Sun, 31 Aug 2025 15:32:41 +0200 Subject: [PATCH 1220/1235] Add CAN-over-Ethernet interface plugin to documentation (#1987) * Add COE interface plugin to the list. * Add news fragment for documentation change --------- Co-authored-by: Gerrit Beine --- doc/changelog.d/1987.added.md | 1 + doc/plugin-interface.rst | 37 +++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 doc/changelog.d/1987.added.md diff --git a/doc/changelog.d/1987.added.md b/doc/changelog.d/1987.added.md new file mode 100644 index 000000000..398add3e3 --- /dev/null +++ b/doc/changelog.d/1987.added.md @@ -0,0 +1 @@ +Add [python-can-coe](https://c0d3.sh/smarthome/python-can-coe) interface plugin to the documentation. diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index d841281e8..2f295b678 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -62,23 +62,25 @@ The table below lists interface drivers that can be added by installing addition .. note:: The packages listed below are maintained by other authors. Any issues should be reported in their corresponding repository and **not** in the python-can repository. -+----------------------------+-------------------------------------------------------+ -| Name | Description | -+============================+=======================================================+ -| `python-can-canine`_ | CAN Driver for the CANine CAN interface | -+----------------------------+-------------------------------------------------------+ -| `python-can-cvector`_ | Cython based version of the 'VectorBus' | -+----------------------------+-------------------------------------------------------+ -| `python-can-remote`_ | CAN over network bridge | -+----------------------------+-------------------------------------------------------+ -| `python-can-sontheim`_ | CAN Driver for Sontheim CAN interfaces (e.g. CANfox) | -+----------------------------+-------------------------------------------------------+ -| `zlgcan`_ | Python wrapper for zlgcan-driver-rs | -+----------------------------+-------------------------------------------------------+ -| `python-can-cando`_ | Python wrapper for Netronics' CANdo and CANdoISO | -+----------------------------+-------------------------------------------------------+ -| `python-can-candle`_ | A full-featured driver for candleLight | -+----------------------------+-------------------------------------------------------+ ++----------------------------+----------------------------------------------------------+ +| Name | Description | ++============================+==========================================================+ +| `python-can-canine`_ | CAN Driver for the CANine CAN interface | ++----------------------------+----------------------------------------------------------+ +| `python-can-cvector`_ | Cython based version of the 'VectorBus' | ++----------------------------+----------------------------------------------------------+ +| `python-can-remote`_ | CAN over network bridge | ++----------------------------+----------------------------------------------------------+ +| `python-can-sontheim`_ | CAN Driver for Sontheim CAN interfaces (e.g. CANfox) | ++----------------------------+----------------------------------------------------------+ +| `zlgcan`_ | Python wrapper for zlgcan-driver-rs | ++----------------------------+----------------------------------------------------------+ +| `python-can-cando`_ | Python wrapper for Netronics' CANdo and CANdoISO | ++----------------------------+----------------------------------------------------------+ +| `python-can-candle`_ | A full-featured driver for candleLight | ++----------------------------+----------------------------------------------------------+ +| `python-can-coe`_ | A CAN-over-Ethernet interface for Technische Alternative | ++----------------------------+----------------------------------------------------------+ .. _python-can-canine: https://github.com/tinymovr/python-can-canine .. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector @@ -87,4 +89,5 @@ The table below lists interface drivers that can be added by installing addition .. _zlgcan: https://github.com/jesses2025smith/zlgcan-driver .. _python-can-cando: https://github.com/belliriccardo/python-can-cando .. _python-can-candle: https://github.com/BIRLab/python-can-candle +.. _python-can-coe: https://c0d3.sh/smarthome/python-can-coe From ef30d088740b2c448cf3db29de66413e926f0589 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 06:12:38 +0000 Subject: [PATCH 1221/1235] Bump the github-actions group with 3 updates Bumps the github-actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/checkout` from 4.2.2 to 5.0.0 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/11bd71901bbe5b1630ceea73d27597364c9af683...08c6903cd8c0fde910a37f88322edcfb5dd907a8) Updates `astral-sh/setup-uv` from 6.4.3 to 6.6.1 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/e92bafb6253dcd438e0484186d7669ea7a8ca1cc...557e51de59eb14aaaba2ed9621916900a91d50c6) Updates `actions/attest-build-provenance` from 2.4.0 to 3.0.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/e8998f949152b193b063cb0ec769d69d929409be...977bb373ede98d70efdf65b84cb5f73e068dcc2a) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: astral-sh/setup-uv dependency-version: 6.6.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: actions/attest-build-provenance dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a80f1e247..5078a33ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,12 +34,12 @@ jobs: ] fail-fast: false steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 with: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # 6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # 6.6.1 - name: Install tox run: uv tool install tox --with tox-uv - name: Setup SocketCAN @@ -67,7 +67,7 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 with: fetch-depth: 0 persist-credentials: false @@ -80,12 +80,12 @@ jobs: static-code-analysis: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 with: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # 6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # 6.6.1 - name: Install tox run: uv tool install tox --with tox-uv - name: Run linters @@ -98,12 +98,12 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 with: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # 6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # 6.6.1 - name: Install tox run: uv tool install tox --with tox-uv - name: Build documentation @@ -114,12 +114,12 @@ jobs: name: Packaging runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 with: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # 6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # 6.6.1 - name: Build wheel and sdist run: uv build - name: Check build artifacts @@ -147,7 +147,7 @@ jobs: merge-multiple: true - name: Generate artifact attestation - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # 2.4.0 + uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # 3.0.0 with: subject-path: 'dist/*' From 4ac05e56e85536b120af42f28af6611979b7e404 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 04:26:23 +0000 Subject: [PATCH 1222/1235] Bump ruff from 0.12.8 to 0.12.11 in the dev-deps group Bumps the dev-deps group with 1 update: [ruff](https://github.com/astral-sh/ruff). Updates `ruff` from 0.12.8 to 0.12.11 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.12.8...0.12.11) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.12.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dev-deps ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e125ea84f..bc526ce73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ docs = [ ] lint = [ "pylint==3.3.*", - "ruff==0.12.8", + "ruff==0.12.11", "black==25.1.*", "mypy==1.17.*", ] From f17c37622c143ec4990fb57d86654e9c25a62e91 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:00:41 +0200 Subject: [PATCH 1223/1235] remove support for Python 3.9 (#1996) Co-authored-by: zariiii9003 --- .github/dependabot.yml | 5 +- .github/workflows/ci.yml | 1 - README.rst | 1 + can/_entry_points.py | 21 ++------ can/broadcastmanager.py | 37 ++++++------- can/bus.py | 43 +++++++-------- can/cli.py | 26 ++++----- can/ctypesutil.py | 7 +-- can/exceptions.py | 5 +- can/interface.py | 10 ++-- can/interfaces/canalystii.py | 20 +++---- can/interfaces/cantact.py | 12 ++--- can/interfaces/etas/__init__.py | 14 +++-- can/interfaces/gs_usb.py | 7 +-- can/interfaces/iscan.py | 9 ++-- can/interfaces/ixxat/canlib.py | 31 ++++++----- can/interfaces/ixxat/canlib_vcinpl.py | 13 +++-- can/interfaces/ixxat/canlib_vcinpl2.py | 27 +++++----- can/interfaces/kvaser/canlib.py | 5 +- can/interfaces/nican.py | 11 ++-- can/interfaces/nixnet.py | 16 +++--- can/interfaces/pcan/pcan.py | 14 +++-- can/interfaces/robotell.py | 3 +- can/interfaces/serial/serial_can.py | 8 ++- can/interfaces/slcan.py | 26 ++++----- can/interfaces/socketcan/socketcan.py | 37 ++++++------- can/interfaces/socketcan/utils.py | 5 +- can/interfaces/udp_multicast/bus.py | 18 +++---- can/interfaces/udp_multicast/utils.py | 4 +- can/interfaces/usb2can/usb2canInterface.py | 9 ++-- can/interfaces/vector/canlib.py | 61 ++++++++++------------ can/interfaces/vector/exceptions.py | 6 +-- can/interfaces/virtual.py | 8 ++- can/io/asc.py | 14 ++--- can/io/blf.py | 18 +++---- can/io/canutils.py | 10 ++-- can/io/csv.py | 6 +-- can/io/generic.py | 42 +++++++-------- can/io/logger.py | 15 +++--- can/io/mf4.py | 10 ++-- can/io/printer.py | 4 +- can/io/sqlite.py | 4 +- can/io/trc.py | 32 ++++++------ can/listener.py | 11 +--- can/logger.py | 3 +- can/message.py | 14 ++--- can/notifier.py | 23 ++++---- can/thread_safe_bus.py | 18 +++---- can/typechecking.py | 25 ++++----- can/util.py | 31 +++++------ can/viewer.py | 4 +- doc/changelog.d/1996.removed.md | 1 + pyproject.toml | 12 ++--- tox.ini | 2 +- 54 files changed, 363 insertions(+), 456 deletions(-) create mode 100644 doc/changelog.d/1996.removed.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e2781e2ef..dbe907783 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,11 +5,12 @@ version: 2 updates: - - package-ecosystem: "uv" + - package-ecosystem: "pip" # Enable version updates for development dependencies directory: "/" schedule: interval: "monthly" + versioning-strategy: "increase-if-necessary" groups: dev-deps: patterns: @@ -23,4 +24,4 @@ updates: groups: github-actions: patterns: - - "*" \ No newline at end of file + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5078a33ee..12022ba35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,6 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] env: [ - "py39", "py310", "py311", "py312", diff --git a/README.rst b/README.rst index 2579871b9..6e75d8d7d 100644 --- a/README.rst +++ b/README.rst @@ -61,6 +61,7 @@ Library Version Python 4.0+ 3.7+ 4.3+ 3.8+ 4.6+ 3.9+ + main branch 3.10+ ============================== =========== diff --git a/can/_entry_points.py b/can/_entry_points.py index 6320b797b..fd1a62d24 100644 --- a/can/_entry_points.py +++ b/can/_entry_points.py @@ -1,5 +1,4 @@ import importlib -import sys from dataclasses import dataclass from importlib.metadata import entry_points from typing import Any @@ -16,19 +15,7 @@ def load(self) -> Any: return getattr(module, self.class_name) -# See https://docs.python.org/3/library/importlib.metadata.html#entry-points, -# "Compatibility Note". -if sys.version_info >= (3, 10): - - def read_entry_points(group: str) -> list[_EntryPoint]: - return [ - _EntryPoint(ep.name, ep.module, ep.attr) for ep in entry_points(group=group) - ] - -else: - - def read_entry_points(group: str) -> list[_EntryPoint]: - return [ - _EntryPoint(ep.name, *ep.value.split(":", maxsplit=1)) - for ep in entry_points().get(group, []) # pylint: disable=no-member - ] +def read_entry_points(group: str) -> list[_EntryPoint]: + return [ + _EntryPoint(ep.name, ep.module, ep.attr) for ep in entry_points(group=group) + ] diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index a71f6fd11..1fea9ac50 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -12,13 +12,10 @@ import threading import time import warnings -from collections.abc import Sequence +from collections.abc import Callable, Sequence from typing import ( TYPE_CHECKING, - Callable, Final, - Optional, - Union, cast, ) @@ -78,7 +75,7 @@ def wait_inf(self, event: _Pywin32Event) -> None: ) -PYWIN32: Optional[_Pywin32] = None +PYWIN32: _Pywin32 | None = None if sys.platform == "win32" and sys.version_info < (3, 11): try: PYWIN32 = _Pywin32() @@ -105,9 +102,7 @@ class CyclicSendTaskABC(CyclicTask, abc.ABC): Message send task with defined period """ - def __init__( - self, messages: Union[Sequence[Message], Message], period: float - ) -> None: + def __init__(self, messages: Sequence[Message] | Message, period: float) -> None: """ :param messages: The messages to be sent periodically. @@ -125,7 +120,7 @@ def __init__( @staticmethod def _check_and_convert_messages( - messages: Union[Sequence[Message], Message], + messages: 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. @@ -164,9 +159,9 @@ def _check_and_convert_messages( class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC, abc.ABC): def __init__( self, - messages: Union[Sequence[Message], Message], + messages: Sequence[Message] | Message, period: float, - duration: Optional[float], + duration: float | None, ) -> None: """Message send task with a defined duration and period. @@ -181,7 +176,7 @@ def __init__( """ super().__init__(messages, period) self.duration = duration - self.end_time: Optional[float] = None + self.end_time: float | None = None class RestartableCyclicTaskABC(CyclicSendTaskABC, abc.ABC): @@ -215,7 +210,7 @@ def _check_modified_messages(self, messages: tuple[Message, ...]) -> None: "from when the task was created" ) - def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: + def modify_data(self, messages: Sequence[Message] | Message) -> None: """Update the contents of the periodically sent messages, without altering the timing. @@ -242,7 +237,7 @@ class MultiRateCyclicSendTaskABC(CyclicSendTaskABC, abc.ABC): def __init__( self, channel: typechecking.Channel, - messages: Union[Sequence[Message], Message], + messages: Sequence[Message] | Message, count: int, # pylint: disable=unused-argument initial_period: float, # pylint: disable=unused-argument subsequent_period: float, @@ -272,12 +267,12 @@ def __init__( self, bus: "BusABC", lock: threading.Lock, - messages: Union[Sequence[Message], Message], + messages: Sequence[Message] | Message, period: float, - duration: Optional[float] = None, - on_error: Optional[Callable[[Exception], bool]] = None, + duration: float | None = None, + on_error: Callable[[Exception], bool] | None = None, autostart: bool = True, - modifier_callback: Optional[Callable[[Message], None]] = None, + modifier_callback: Callable[[Message], None] | None = None, ) -> None: """Transmits `messages` with a `period` seconds for `duration` seconds on a `bus`. @@ -298,13 +293,13 @@ def __init__( self.bus = bus self.send_lock = lock self.stopped = True - self.thread: Optional[threading.Thread] = None + self.thread: threading.Thread | None = None self.on_error = on_error self.modifier_callback = modifier_callback self.period_ms = int(round(period * 1000, 0)) - self.event: Optional[_Pywin32Event] = None + self.event: _Pywin32Event | None = None if PYWIN32: if self.period_ms == 0: # A period of 0 would mean that the timer is signaled only once @@ -338,7 +333,7 @@ def start(self) -> None: self.thread = threading.Thread(target=self._run, name=name) self.thread.daemon = True - self.end_time: Optional[float] = ( + self.end_time: float | None = ( time.perf_counter() + self.duration if self.duration else None ) diff --git a/can/bus.py b/can/bus.py index ec9eb09b7..03425caaa 100644 --- a/can/bus.py +++ b/can/bus.py @@ -6,14 +6,11 @@ import logging import threading from abc import ABC, abstractmethod -from collections.abc import Iterator, Sequence +from collections.abc import Callable, Iterator, Sequence from enum import Enum, auto from time import time from types import TracebackType from typing import ( - Callable, - Optional, - Union, cast, ) @@ -68,7 +65,7 @@ class BusABC(ABC): def __init__( self, channel: can.typechecking.Channel, - can_filters: Optional[can.typechecking.CanFilters] = None, + can_filters: can.typechecking.CanFilters | None = None, **kwargs: object, ): """Construct and open a CAN bus instance of the specified type. @@ -101,7 +98,7 @@ def __init__( def __str__(self) -> str: return self.channel_info - def recv(self, timeout: Optional[float] = None) -> Optional[Message]: + def recv(self, timeout: float | None = None) -> Message | None: """Block waiting for a message from the Bus. :param timeout: @@ -139,9 +136,7 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: return None - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: """ Read a message from the bus and tell whether it was filtered. This methods may be called by :meth:`~can.BusABC.recv` @@ -184,7 +179,7 @@ def _recv_internal( raise NotImplementedError("Trying to read from a write only bus?") @abstractmethod - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: """Transmit a message to the CAN bus. Override this method to enable the transmit path. @@ -205,12 +200,12 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: def send_periodic( self, - msgs: Union[Message, Sequence[Message]], + msgs: Message | Sequence[Message], period: float, - duration: Optional[float] = None, + duration: float | None = None, store_task: bool = True, autostart: bool = True, - modifier_callback: Optional[Callable[[Message], None]] = None, + modifier_callback: Callable[[Message], None] | None = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -297,11 +292,11 @@ def wrapped_stop_method(remove_task: bool = True) -> None: def _send_periodic_internal( self, - msgs: Union[Sequence[Message], Message], + msgs: Sequence[Message] | Message, period: float, - duration: Optional[float] = None, + duration: float | None = None, autostart: bool = True, - modifier_callback: Optional[Callable[[Message], None]] = None, + modifier_callback: Callable[[Message], None] | None = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. @@ -378,7 +373,7 @@ def __iter__(self) -> Iterator[Message]: yield msg @property - def filters(self) -> Optional[can.typechecking.CanFilters]: + def filters(self) -> can.typechecking.CanFilters | None: """ Modify the filters of this bus. See :meth:`~can.BusABC.set_filters` for details. @@ -386,12 +381,10 @@ def filters(self) -> Optional[can.typechecking.CanFilters]: return self._filters @filters.setter - def filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: + def filters(self, filters: can.typechecking.CanFilters | None) -> None: self.set_filters(filters) - def set_filters( - self, filters: Optional[can.typechecking.CanFilters] = None - ) -> None: + def set_filters(self, filters: can.typechecking.CanFilters | None = None) -> None: """Apply filtering to all messages received by this Bus. All messages that match at least one filter are returned. @@ -417,7 +410,7 @@ def set_filters( with contextlib.suppress(NotImplementedError): self._apply_filters(self._filters) - def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: + def _apply_filters(self, filters: can.typechecking.CanFilters | None) -> None: """ Hook for applying the filters to the underlying kernel or hardware if supported/implemented by the interface. @@ -484,9 +477,9 @@ def __enter__(self) -> Self: def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: self.shutdown() diff --git a/can/cli.py b/can/cli.py index 6e3850354..d0ff70126 100644 --- a/can/cli.py +++ b/can/cli.py @@ -1,7 +1,7 @@ import argparse import re from collections.abc import Sequence -from typing import Any, Optional, Union +from typing import Any import can from can.typechecking import CanFilter, TAdditionalCliArgs @@ -12,8 +12,8 @@ def add_bus_arguments( parser: argparse.ArgumentParser, *, filter_arg: bool = False, - prefix: Optional[str] = None, - group_title: Optional[str] = None, + prefix: str | None = None, + group_title: str | None = None, ) -> None: """Adds CAN bus configuration options to an argument parser. @@ -144,7 +144,7 @@ def add_bus_arguments( def create_bus_from_namespace( namespace: argparse.Namespace, *, - prefix: Optional[str] = None, + prefix: str | None = None, **kwargs: Any, ) -> can.BusABC: """Creates and returns a CAN bus instance based on the provided namespace and arguments. @@ -192,8 +192,8 @@ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[Any], None], - option_string: Optional[str] = None, + values: str | Sequence[Any] | None, + option_string: str | None = None, ) -> None: if not isinstance(values, list): raise argparse.ArgumentError(self, "Invalid filter argument") @@ -222,8 +222,8 @@ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[Any], None], - option_string: Optional[str] = None, + values: str | Sequence[Any] | None, + option_string: str | None = None, ) -> None: if not isinstance(values, list): raise argparse.ArgumentError(self, "Invalid --timing argument") @@ -252,13 +252,13 @@ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[Any], None], - option_string: Optional[str] = None, + values: str | Sequence[Any] | None, + option_string: str | None = None, ) -> None: if not isinstance(values, list): raise argparse.ArgumentError(self, "Invalid --bus-kwargs argument") - bus_kwargs: dict[str, Union[str, int, float, bool]] = {} + bus_kwargs: dict[str, str | int | float | bool] = {} for arg in values: try: @@ -281,7 +281,7 @@ def __call__( def _add_extra_args( - parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup], + parser: argparse.ArgumentParser | argparse._ArgumentGroup, ) -> None: parser.add_argument( "extra_args", @@ -301,7 +301,7 @@ def _split_arg(_arg: str) -> tuple[str, str]: left, right = _arg.split("=", 1) return left.lstrip("-").replace("-", "_"), right - args: dict[str, Union[str, int, float, bool]] = {} + args: dict[str, str | int | float | bool] = {} for key, string_val in map(_split_arg, unknown_args): args[key] = cast_from_string(string_val) return args diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 8336941be..0798de910 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -5,7 +5,8 @@ import ctypes import logging import sys -from typing import Any, Callable, Optional, Union +from collections.abc import Callable +from typing import Any log = logging.getLogger("can.ctypesutil") @@ -20,7 +21,7 @@ class CLibrary(_LibBase): - def __init__(self, library_or_path: Union[str, ctypes.CDLL]) -> None: + def __init__(self, library_or_path: str | ctypes.CDLL) -> None: self.func_name: Any if isinstance(library_or_path, str): @@ -33,7 +34,7 @@ def map_symbol( func_name: str, restype: Any = None, argtypes: tuple[Any, ...] = (), - errcheck: Optional[Callable[..., Any]] = None, + errcheck: Callable[..., Any] | None = None, ) -> Any: """ Map and return a symbol (function) from a C library. A reference to the diff --git a/can/exceptions.py b/can/exceptions.py index 8abc75147..696701399 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -17,7 +17,6 @@ from collections.abc import Generator from contextlib import contextmanager -from typing import Optional class CanError(Exception): @@ -51,7 +50,7 @@ class CanError(Exception): def __init__( self, message: str = "", - error_code: Optional[int] = None, + error_code: int | None = None, ) -> None: self.error_code = error_code super().__init__( @@ -108,7 +107,7 @@ class CanTimeoutError(CanError, TimeoutError): @contextmanager def error_check( - error_message: Optional[str] = None, + error_message: str | None = None, exception_type: type[CanError] = CanOperationError, ) -> Generator[None, None, None]: """Catches any exceptions and turns them into the new type while preserving the stack trace.""" diff --git a/can/interface.py b/can/interface.py index 4aa010a36..efde5b214 100644 --- a/can/interface.py +++ b/can/interface.py @@ -8,7 +8,7 @@ import importlib import logging from collections.abc import Callable, Iterable, Sequence -from typing import Any, Optional, Union, cast +from typing import Any, cast from . import util from .bus import BusABC @@ -64,9 +64,9 @@ def _get_class_for_interface(interface: str) -> type[BusABC]: context="config_context", ) def Bus( # noqa: N802 - channel: Optional[Channel] = None, - interface: Optional[str] = None, - config_context: Optional[str] = None, + channel: Channel | None = None, + interface: str | None = None, + config_context: str | None = None, ignore_config: bool = False, **kwargs: Any, ) -> BusABC: @@ -140,7 +140,7 @@ def Bus( # noqa: N802 def detect_available_configs( - interfaces: Union[None, str, Iterable[str]] = None, + interfaces: None | str | Iterable[str] = None, timeout: float = 5.0, ) -> Sequence[AutoDetectedConfig]: """Detect all configurations/channels that the interfaces could diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index d85211130..e2bf7555e 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -3,7 +3,7 @@ from collections import deque from collections.abc import Sequence from ctypes import c_ubyte -from typing import Any, Optional, Union +from typing import Any import canalystii as driver @@ -21,12 +21,12 @@ class CANalystIIBus(BusABC): ) def __init__( self, - channel: Union[int, Sequence[int], str] = (0, 1), + channel: int | Sequence[int] | str = (0, 1), device: int = 0, - bitrate: Optional[int] = None, - timing: Optional[Union[BitTiming, BitTimingFd]] = None, - can_filters: Optional[CanFilters] = None, - rx_queue_size: Optional[int] = None, + bitrate: int | None = None, + timing: BitTiming | BitTimingFd | None = None, + can_filters: CanFilters | None = None, + rx_queue_size: int | None = None, **kwargs: dict[str, Any], ): """ @@ -94,7 +94,7 @@ def __init__( # system. RX_POLL_DELAY = 0.020 - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: """Send a CAN message to the bus :param msg: message to send @@ -166,8 +166,8 @@ def poll_received_messages(self) -> None: ) def _recv_internal( - self, timeout: Optional[float] = None - ) -> tuple[Optional[Message], bool]: + self, timeout: float | None = None + ) -> tuple[Message | None, bool]: """ :param timeout: float in seconds @@ -194,7 +194,7 @@ def _recv_internal( return (None, False) - def flush_tx_buffer(self, channel: Optional[int] = None) -> None: + def flush_tx_buffer(self, channel: int | None = None) -> None: """Flush the TX buffer of the device. :param channel: diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 08fe15b72..ee01fbf94 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -5,7 +5,7 @@ import logging import time from collections.abc import Sequence -from typing import Any, Optional, Union +from typing import Any from unittest.mock import Mock from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message @@ -56,7 +56,7 @@ def __init__( bitrate: int = 500_000, poll_interval: float = 0.01, monitor: bool = False, - timing: Optional[Union[BitTiming, BitTimingFd]] = None, + timing: BitTiming | BitTimingFd | None = None, **kwargs: Any, ) -> None: """ @@ -123,9 +123,7 @@ def __init__( **kwargs, ) - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: if timeout is None: raise TypeError( f"{self.__class__.__name__} expects a numeric `timeout` value." @@ -149,7 +147,7 @@ def _recv_internal( ) return msg, False - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: with error_check("Cannot send message"): self.interface.send( self.channel, @@ -166,7 +164,7 @@ def shutdown(self) -> None: self.interface.stop() -def mock_recv(timeout: int) -> Optional[dict[str, Any]]: +def mock_recv(timeout: int) -> dict[str, Any] | None: if timeout > 0: return { "id": 0x123, diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 9d4d0bd2a..f8364a3fd 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -1,5 +1,5 @@ import time -from typing import Optional +from typing import Any import can from can.exceptions import CanInitializationError @@ -11,12 +11,12 @@ class EtasBus(can.BusABC): def __init__( self, channel: str, - can_filters: Optional[can.typechecking.CanFilters] = None, + can_filters: can.typechecking.CanFilters | None = None, receive_own_messages: bool = False, bitrate: int = 1000000, fd: bool = True, data_bitrate: int = 2000000, - **kwargs: dict[str, any], + **kwargs: dict[str, Any], ): self.receive_own_messages = receive_own_messages self._can_protocol = can.CanProtocol.CAN_FD if fd else can.CanProtocol.CAN_20 @@ -120,9 +120,7 @@ def __init__( # Super call must be after child init since super calls set_filters super().__init__(channel=channel, **kwargs) - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[can.Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[can.Message | None, bool]: ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)() ociMsg = OCI_CANMessageEx() ociMsgs[0] = ctypes.pointer(ociMsg) @@ -189,7 +187,7 @@ def _recv_internal( return (msg, True) - def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: + def send(self, msg: can.Message, timeout: float | None = None) -> None: ociMsgs = (ctypes.POINTER(OCI_CANMessageEx) * 1)() ociMsg = OCI_CANMessageEx() ociMsgs[0] = ctypes.pointer(ociMsg) @@ -219,7 +217,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: OCI_WriteCANDataEx(self.txQueue, OCI_NO_TIME, ociMsgs, 1, None) - def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: + def _apply_filters(self, filters: can.typechecking.CanFilters | None) -> None: if self._oci_filters: OCI_RemoveCANFrameFilterEx(self.rxQueue, self._oci_filters, 1) diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 4ab541f43..6297fc1f5 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -1,5 +1,4 @@ import logging -from typing import Optional import usb from gs_usb.constants import CAN_EFF_FLAG, CAN_ERR_FLAG, CAN_MAX_DLC, CAN_RTR_FLAG @@ -82,7 +81,7 @@ def __init__( **kwargs, ) - def send(self, msg: can.Message, timeout: Optional[float] = None): + def send(self, msg: can.Message, timeout: float | None = None): """Transmit a message to the CAN bus. :param Message msg: A message object. @@ -117,9 +116,7 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): except usb.core.USBError as exc: raise CanOperationError("The message could not be sent") from exc - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[can.Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[can.Message | None, bool]: """ Read a message from the bus and tell whether it was filtered. This methods may be called by :meth:`~can.BusABC.recv` diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index 79b4f754d..2fa19942a 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -5,7 +5,6 @@ import ctypes import logging import time -from typing import Optional, Union from can import ( BusABC, @@ -82,7 +81,7 @@ class IscanBus(BusABC): def __init__( self, - channel: Union[str, int], + channel: str | int, bitrate: int = 500000, poll_interval: float = 0.01, **kwargs, @@ -115,9 +114,7 @@ def __init__( **kwargs, ) - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: raw_msg = MessageExStruct() end_time = time.time() + timeout if timeout is not None else None while True: @@ -147,7 +144,7 @@ def _recv_internal( ) return msg, False - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: raw_msg = MessageExStruct( msg.arbitration_id, bool(msg.is_extended_id), diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 6192367c4..528e86d5e 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,5 +1,4 @@ -from collections.abc import Sequence -from typing import Callable, Optional, Union +from collections.abc import Callable, Sequence import can.interfaces.ixxat.canlib_vcinpl as vcinpl import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 @@ -26,20 +25,20 @@ def __init__( channel: int, can_filters=None, receive_own_messages: bool = False, - unique_hardware_id: Optional[int] = None, + unique_hardware_id: int | None = None, extended: bool = True, fd: bool = False, - rx_fifo_size: Optional[int] = None, - tx_fifo_size: Optional[int] = None, + rx_fifo_size: int | None = None, + tx_fifo_size: int | None = None, bitrate: int = 500000, data_bitrate: int = 2000000, - sjw_abr: Optional[int] = None, - tseg1_abr: Optional[int] = None, - tseg2_abr: Optional[int] = None, - sjw_dbr: Optional[int] = None, - tseg1_dbr: Optional[int] = None, - tseg2_dbr: Optional[int] = None, - ssp_dbr: Optional[int] = None, + sjw_abr: int | None = None, + tseg1_abr: int | None = None, + tseg2_abr: int | None = None, + sjw_dbr: int | None = None, + tseg1_dbr: int | None = None, + tseg2_dbr: int | None = None, + ssp_dbr: int | None = None, **kwargs, ): """ @@ -147,16 +146,16 @@ def _recv_internal(self, timeout): """Read a message from IXXAT device.""" return self.bus._recv_internal(timeout) - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: return self.bus.send(msg, timeout) def _send_periodic_internal( self, - msgs: Union[Sequence[Message], Message], + msgs: Sequence[Message] | Message, period: float, - duration: Optional[float] = None, + duration: float | None = None, autostart: bool = True, - modifier_callback: Optional[Callable[[Message], None]] = None, + modifier_callback: Callable[[Message], None] | None = None, ) -> CyclicSendTaskABC: return self.bus._send_periodic_internal( msgs, period, duration, autostart, modifier_callback diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 59f98417d..7c4becafd 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -15,8 +15,7 @@ import sys import time import warnings -from collections.abc import Sequence -from typing import Callable, Optional, Union +from collections.abc import Callable, Sequence from can import ( BusABC, @@ -436,7 +435,7 @@ def __init__( channel: int, can_filters=None, receive_own_messages: bool = False, - unique_hardware_id: Optional[int] = None, + unique_hardware_id: int | None = None, extended: bool = True, rx_fifo_size: int = 16, tx_fifo_size: int = 16, @@ -770,7 +769,7 @@ def _recv_internal(self, timeout): return rx_msg, True - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: """ Sends a message on the bus. The interface may buffer the message. @@ -805,11 +804,11 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: def _send_periodic_internal( self, - msgs: Union[Sequence[Message], Message], + msgs: Sequence[Message] | Message, period: float, - duration: Optional[float] = None, + duration: float | None = None, autostart: bool = True, - modifier_callback: Optional[Callable[[Message], None]] = None, + modifier_callback: Callable[[Message], None] | None = None, ) -> CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" if modifier_callback is None: diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index b7698277f..b6789885a 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -15,8 +15,7 @@ import sys import time import warnings -from collections.abc import Sequence -from typing import Callable, Optional, Union +from collections.abc import Callable, Sequence from can import ( BusABC, @@ -431,19 +430,19 @@ def __init__( channel: int, can_filters=None, receive_own_messages: int = False, - unique_hardware_id: Optional[int] = None, + unique_hardware_id: int | None = None, extended: bool = True, rx_fifo_size: int = 1024, tx_fifo_size: int = 128, bitrate: int = 500000, data_bitrate: int = 2000000, - sjw_abr: Optional[int] = None, - tseg1_abr: Optional[int] = None, - tseg2_abr: Optional[int] = None, - sjw_dbr: Optional[int] = None, - tseg1_dbr: Optional[int] = None, - tseg2_dbr: Optional[int] = None, - ssp_dbr: Optional[int] = None, + sjw_abr: int | None = None, + tseg1_abr: int | None = None, + tseg2_abr: int | None = None, + sjw_dbr: int | None = None, + tseg1_dbr: int | None = None, + tseg2_dbr: int | None = None, + ssp_dbr: int | None = None, **kwargs, ): """ @@ -902,7 +901,7 @@ def _recv_internal(self, timeout): return rx_msg, True - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: """ Sends a message on the bus. The interface may buffer the message. @@ -947,11 +946,11 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: def _send_periodic_internal( self, - msgs: Union[Sequence[Message], Message], + msgs: Sequence[Message] | Message, period: float, - duration: Optional[float] = None, + duration: float | None = None, autostart: bool = True, - modifier_callback: Optional[Callable[[Message], None]] = None, + modifier_callback: Callable[[Message], None] | None = None, ) -> CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" if modifier_callback is None: diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index a1dd03e58..4403b60ca 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -10,7 +10,6 @@ import logging import sys import time -from typing import Optional, Union from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message from can.exceptions import CanError, CanInitializationError, CanOperationError @@ -375,8 +374,8 @@ class KvaserBus(BusABC): def __init__( self, channel: int, - can_filters: Optional[CanFilters] = None, - timing: Optional[Union[BitTiming, BitTimingFd]] = None, + can_filters: CanFilters | None = None, + timing: BitTiming | BitTimingFd | None = None, **kwargs, ): """ diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 1abf0b35f..ba5b991c9 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -16,7 +16,6 @@ import ctypes import logging import sys -from typing import Optional import can.typechecking from can import ( @@ -187,8 +186,8 @@ class NicanBus(BusABC): def __init__( self, channel: str, - can_filters: Optional[can.typechecking.CanFilters] = None, - bitrate: Optional[int] = None, + can_filters: can.typechecking.CanFilters | None = None, + bitrate: int | None = None, log_errors: bool = True, **kwargs, ) -> None: @@ -279,9 +278,7 @@ def __init__( **kwargs, ) - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: """ Read a message from a NI-CAN bus. @@ -330,7 +327,7 @@ def _recv_internal( ) return msg, True - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: """ Send a message to NI-CAN. diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index c723d1f52..ec303a364 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -14,7 +14,7 @@ import warnings from queue import SimpleQueue from types import ModuleType -from typing import Any, Optional, Union +from typing import Any import can.typechecking from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message @@ -27,7 +27,7 @@ logger = logging.getLogger(__name__) -nixnet: Optional[ModuleType] = None +nixnet: ModuleType | None = None try: import nixnet # type: ignore import nixnet.constants # type: ignore @@ -52,12 +52,12 @@ def __init__( self, channel: str = "CAN1", bitrate: int = 500_000, - timing: Optional[Union[BitTiming, BitTimingFd]] = None, - can_filters: Optional[can.typechecking.CanFilters] = None, + timing: BitTiming | BitTimingFd | None = None, + can_filters: can.typechecking.CanFilters | None = None, receive_own_messages: bool = False, can_termination: bool = False, fd: bool = False, - fd_bitrate: Optional[int] = None, + fd_bitrate: int | None = None, poll_interval: float = 0.001, **kwargs: Any, ) -> None: @@ -201,9 +201,7 @@ def fd(self) -> bool: ) return self._can_protocol is CanProtocol.CAN_FD - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: end_time = time.perf_counter() + timeout if timeout is not None else None while True: @@ -256,7 +254,7 @@ def _recv_internal( ) return msg, False - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: """ Send a message using NI-XNET. diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index d63981580..a2f5f361f 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -6,7 +6,7 @@ import platform import time import warnings -from typing import Any, Optional, Union +from typing import Any from packaging import version @@ -120,9 +120,9 @@ class PcanBus(BusABC): def __init__( self, channel: str = "PCAN_USBBUS1", - device_id: Optional[int] = None, + device_id: int | None = None, state: BusState = BusState.ACTIVE, - timing: Optional[Union[BitTiming, BitTimingFd]] = None, + timing: BitTiming | BitTimingFd | None = None, bitrate: int = 500000, receive_own_messages: bool = False, **kwargs: Any, @@ -500,9 +500,7 @@ def set_device_number(self, device_number): return False return True - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: end_time = time.time() + timeout if timeout is not None else None while True: @@ -523,7 +521,7 @@ def _recv_internal( # receive queue is empty, wait or return on timeout if end_time is None: - time_left: Optional[float] = None + time_left: float | None = None timed_out = False else: time_left = max(0.0, end_time - time.time()) @@ -793,7 +791,7 @@ def _detect_available_configs(): pass return channels - def status_string(self) -> Optional[str]: + def status_string(self) -> str | None: """ Query the PCAN bus status. diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 16668bdda..b24543856 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -5,7 +5,6 @@ import io import logging import time -from typing import Optional from can import BusABC, CanProtocol, Message @@ -380,7 +379,7 @@ def fileno(self): except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception - def get_serial_number(self, timeout: Optional[int]) -> Optional[str]: + def get_serial_number(self, timeout: int | None) -> str | None: """Get serial number of the slcan interface. :param timeout: diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 680cf10f6..efef6cf59 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -11,7 +11,7 @@ import logging import struct from collections.abc import Sequence -from typing import Any, Optional, cast +from typing import Any, cast from can import ( BusABC, @@ -109,7 +109,7 @@ def shutdown(self) -> None: super().shutdown() self._ser.close() - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: """ Send a message over the serial device. @@ -161,9 +161,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: except serial.SerialTimeoutException as error: raise CanTimeoutError() from error - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: """ Read a message from the serial device. diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index d9eab5edf..086d9ed32 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -7,7 +7,7 @@ import time import warnings from queue import SimpleQueue -from typing import Any, Optional, Union, cast +from typing import Any, cast from can import BitTiming, BitTimingFd, BusABC, CanProtocol, Message, typechecking from can.exceptions import ( @@ -75,8 +75,8 @@ def __init__( self, channel: typechecking.ChannelStr, tty_baudrate: int = 115200, - bitrate: Optional[int] = None, - timing: Optional[Union[BitTiming, BitTimingFd]] = None, + bitrate: int | None = None, + timing: BitTiming | BitTimingFd | None = None, sleep_after_open: float = _SLEEP_AFTER_SERIAL_OPEN, rtscts: bool = False, listen_only: bool = False, @@ -119,7 +119,7 @@ def __init__( if serial is None: raise CanInterfaceNotImplementedError("The serial module is not installed") - btr: Optional[str] = kwargs.get("btr", None) + btr: str | None = kwargs.get("btr", None) if btr is not None: warnings.warn( "The 'btr' argument is deprecated since python-can v4.5.0 " @@ -166,7 +166,7 @@ def __init__( super().__init__(channel, **kwargs) - def set_bitrate(self, bitrate: int, data_bitrate: Optional[int] = None) -> None: + def set_bitrate(self, bitrate: int, data_bitrate: int | None = None) -> None: """ :param bitrate: Bitrate in bit/s @@ -211,7 +211,7 @@ def _write(self, string: str) -> None: self.serialPortOrig.write(string.encode() + self.LINE_TERMINATOR) self.serialPortOrig.flush() - def _read(self, timeout: Optional[float]) -> Optional[str]: + def _read(self, timeout: float | None) -> str | None: _timeout = serial.Timeout(timeout) with error_check("Could not read from serial device"): @@ -250,9 +250,7 @@ def open(self) -> None: def close(self) -> None: self._write("C") - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: canId = None remote = False extended = False @@ -261,7 +259,7 @@ def _recv_internal( fdBrs = False if self._queue.qsize(): - string: Optional[str] = self._queue.get_nowait() + string: str | None = self._queue.get_nowait() else: string = self._read(timeout) @@ -335,7 +333,7 @@ def _recv_internal( return msg, False return None, False - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: if timeout != self.serialPortOrig.write_timeout: self.serialPortOrig.write_timeout = timeout if msg.is_remote_frame: @@ -381,9 +379,7 @@ def fileno(self) -> int: except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception - def get_version( - self, timeout: Optional[float] - ) -> tuple[Optional[int], Optional[int]]: + def get_version(self, timeout: float | None) -> tuple[int | None, int | None]: """Get HW and SW version of the slcan interface. :param timeout: @@ -411,7 +407,7 @@ def get_version( break return None, None - def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: + def get_serial_number(self, timeout: float | None) -> str | None: """Get serial number of the slcan interface. :param timeout: diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 30b75108a..6dc856cbf 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -15,8 +15,7 @@ import threading import time import warnings -from collections.abc import Sequence -from typing import Callable, Optional, Union +from collections.abc import Callable, Sequence import can from can import BusABC, CanProtocol, Message @@ -51,14 +50,12 @@ # Setup BCM struct def bcm_header_factory( - fields: list[tuple[str, Union[type[ctypes.c_uint32], type[ctypes.c_long]]]], + fields: list[tuple[str, type[ctypes.c_uint32] | type[ctypes.c_long]]], alignment: int = 8, ): curr_stride = 0 results: list[ - tuple[ - str, Union[type[ctypes.c_uint8], type[ctypes.c_uint32], type[ctypes.c_long]] - ] + tuple[str, type[ctypes.c_uint8] | type[ctypes.c_uint32] | type[ctypes.c_long]] ] = [] pad_index = 0 for field in fields: @@ -405,9 +402,9 @@ def __init__( self, bcm_socket: socket.socket, task_id: int, - messages: Union[Sequence[Message], Message], + messages: Sequence[Message] | Message, period: float, - duration: Optional[float] = None, + duration: float | None = None, autostart: bool = True, ) -> None: """Construct and :meth:`~start` a task. @@ -507,7 +504,7 @@ def stop(self) -> None: stopframe = build_bcm_tx_delete_header(self.task_id, self.flags) send_bcm(self.bcm_socket, stopframe) - def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: + def modify_data(self, messages: Sequence[Message] | Message) -> None: """Update the contents of the periodically sent CAN messages by sending TX_SETUP message to Linux kernel. @@ -605,9 +602,7 @@ def bind_socket(sock: socket.socket, channel: str = "can0") -> None: log.debug("Bound socket.") -def capture_message( - sock: socket.socket, get_channel: bool = False -) -> Optional[Message]: +def capture_message(sock: socket.socket, get_channel: bool = False) -> Message | None: """ Captures a message from given socket. @@ -702,7 +697,7 @@ def __init__( receive_own_messages: bool = False, local_loopback: bool = True, fd: bool = False, - can_filters: Optional[CanFilters] = None, + can_filters: CanFilters | None = None, ignore_rx_error_frames=False, **kwargs, ) -> None: @@ -818,9 +813,7 @@ def shutdown(self) -> None: log.debug("Closing raw can socket") self.socket.close() - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: try: # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) @@ -842,7 +835,7 @@ def _recv_internal( # socket wasn't readable or timeout occurred return None, self._is_filtered - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: """Transmit a message to the CAN bus. :param msg: A message object. @@ -880,7 +873,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: raise can.CanOperationError("Transmit buffer full") - def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: + def _send_once(self, data: bytes, channel: str | None = None) -> int: try: if self.channel == "" and channel: # Message must be addressed to a specific channel @@ -895,11 +888,11 @@ def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: def _send_periodic_internal( self, - msgs: Union[Sequence[Message], Message], + msgs: Sequence[Message] | Message, period: float, - duration: Optional[float] = None, + duration: float | None = None, autostart: bool = True, - modifier_callback: Optional[Callable[[Message], None]] = None, + modifier_callback: Callable[[Message], None] | None = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -974,7 +967,7 @@ def _get_bcm_socket(self, channel: str) -> socket.socket: self._bcm_sockets[channel] = create_bcm_socket(self.channel) return self._bcm_sockets[channel] - def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: + def _apply_filters(self, filters: can.typechecking.CanFilters | None) -> None: try: self.socket.setsockopt( constants.SOL_CAN_RAW, constants.CAN_RAW_FILTER, pack_filters(filters) diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 1c096f66e..0740f769d 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -9,7 +9,6 @@ import struct import subprocess import sys -from typing import Optional from can import typechecking from can.interfaces.socketcan.constants import CAN_EFF_FLAG @@ -17,7 +16,7 @@ log = logging.getLogger(__name__) -def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes: +def pack_filters(can_filters: typechecking.CanFilters | None = None) -> bytes: if can_filters is None: # Pass all messages can_filters = [{"can_id": 0, "can_mask": 0}] @@ -72,7 +71,7 @@ def find_available_interfaces() -> list[str]: return interfaces -def error_code_to_str(code: Optional[int]) -> str: +def error_code_to_str(code: int | None) -> str: """ Converts a given error code (errno) to a useful and human readable string. diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 9e0187ea2..87a0800fa 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -6,7 +6,7 @@ import struct import time import warnings -from typing import Any, Optional, Union +from typing import Any import can from can import BusABC, CanProtocol, Message @@ -24,7 +24,7 @@ # see socket.getaddrinfo() IPv4_ADDRESS_INFO = tuple[str, int] # address, port IPv6_ADDRESS_INFO = tuple[str, int, int, int] # address, port, flowinfo, scope_id -IP_ADDRESS_INFO = Union[IPv4_ADDRESS_INFO, IPv6_ADDRESS_INFO] +IP_ADDRESS_INFO = IPv4_ADDRESS_INFO | IPv6_ADDRESS_INFO # Additional constants for the interaction with Unix kernels SO_TIMESTAMPNS = 35 @@ -126,9 +126,7 @@ def is_fd(self) -> bool: ) return self._can_protocol is CanProtocol.CAN_FD - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: result = self._multicast.recv(timeout) if not result: return None, False @@ -148,7 +146,7 @@ def _recv_internal( return can_message, False - def send(self, msg: can.Message, timeout: Optional[float] = None) -> None: + def send(self, msg: can.Message, timeout: float | None = None) -> None: if self._can_protocol is not CanProtocol.CAN_FD and msg.is_fd: raise can.CanOperationError( "cannot send FD message over bus with CAN FD disabled" @@ -242,7 +240,7 @@ def __init__( # used by send() self._send_destination = (self.group, self.port) - self._last_send_timeout: Optional[float] = None + self._last_send_timeout: float | None = None def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: """Creates a new socket. This might fail and raise an exception! @@ -319,7 +317,7 @@ def _create_socket(self, address_family: socket.AddressFamily) -> socket.socket: "could not create or configure socket" ) from error - def send(self, data: bytes, timeout: Optional[float] = None) -> None: + def send(self, data: bytes, timeout: float | None = None) -> None: """Send data to all group members. This call blocks. :param timeout: the timeout in seconds after which an Exception is raised is sending has failed @@ -342,8 +340,8 @@ def send(self, data: bytes, timeout: Optional[float] = None) -> None: raise can.CanOperationError("failed to send via socket") from error def recv( - self, timeout: Optional[float] = None - ) -> Optional[tuple[bytes, IP_ADDRESS_INFO, float]]: + self, timeout: float | None = None + ) -> tuple[bytes, IP_ADDRESS_INFO, float] | None: """ Receive up to **max_buffer** bytes. diff --git a/can/interfaces/udp_multicast/utils.py b/can/interfaces/udp_multicast/utils.py index de39833a3..1e1d62c23 100644 --- a/can/interfaces/udp_multicast/utils.py +++ b/can/interfaces/udp_multicast/utils.py @@ -2,7 +2,7 @@ Defines common functions. """ -from typing import Any, Optional, cast +from typing import Any, cast from can import CanInterfaceNotImplementedError, Message from can.typechecking import ReadableBytesLike @@ -56,7 +56,7 @@ def pack_message(message: Message) -> bytes: def unpack_message( data: ReadableBytesLike, - replace: Optional[dict[str, Any]] = None, + replace: dict[str, Any] | None = None, check: bool = False, ) -> Message: """Unpack a can.Message from a msgpack byte blob. diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index adc16e8b3..66c171f4d 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -4,7 +4,6 @@ import logging from ctypes import byref -from typing import Optional, Union from can import ( BitTiming, @@ -110,12 +109,12 @@ class Usb2canBus(BusABC): def __init__( self, - channel: Optional[str] = None, + channel: str | None = None, dll: str = "usb2can.dll", flags: int = 0x00000008, bitrate: int = 500000, - timing: Optional[Union[BitTiming, BitTimingFd]] = None, - serial: Optional[str] = None, + timing: BitTiming | BitTimingFd | None = None, + serial: str | None = None, **kwargs, ): self.can = Usb2CanAbstractionLayer(dll) @@ -207,7 +206,7 @@ def _detect_available_configs(): return Usb2canBus.detect_available_configs() @staticmethod - def detect_available_configs(serial_matcher: Optional[str] = None): + def detect_available_configs(serial_matcher: str | None = None): """ Uses the *Windows Management Instrumentation* to identify serial devices. diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index d15b89803..8bdd77b83 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -10,14 +10,11 @@ import os import time import warnings -from collections.abc import Iterator, Sequence +from collections.abc import Callable, Iterator, Sequence from types import ModuleType from typing import ( Any, - Callable, NamedTuple, - Optional, - Union, cast, ) @@ -45,14 +42,14 @@ LOG = logging.getLogger(__name__) # Import safely Vector API module for Travis tests -xldriver: Optional[ModuleType] = None +xldriver: ModuleType | None = None try: from . import xldriver except FileNotFoundError as exc: LOG.warning("Could not import vxlapi: %s", exc) -WaitForSingleObject: Optional[Callable[[int, int], int]] -INFINITE: Optional[int] +WaitForSingleObject: Callable[[int, int], int] | None +INFINITE: int | None try: # Try builtin Python 3 Windows API from _winapi import ( # type: ignore[attr-defined,no-redef,unused-ignore] @@ -83,24 +80,24 @@ class VectorBus(BusABC): ) def __init__( self, - channel: Union[int, Sequence[int], str], - can_filters: Optional[CanFilters] = None, + channel: int | Sequence[int] | str, + can_filters: CanFilters | None = None, poll_interval: float = 0.01, receive_own_messages: bool = False, - timing: Optional[Union[BitTiming, BitTimingFd]] = None, - bitrate: Optional[int] = None, + timing: BitTiming | BitTimingFd | None = None, + bitrate: int | None = None, rx_queue_size: int = 2**14, - app_name: Optional[str] = "CANalyzer", - serial: Optional[int] = None, + app_name: str | None = "CANalyzer", + serial: int | None = None, fd: bool = False, - data_bitrate: Optional[int] = None, + data_bitrate: int | None = None, sjw_abr: int = 2, tseg1_abr: int = 6, tseg2_abr: int = 3, sjw_dbr: int = 2, tseg1_dbr: int = 6, tseg2_dbr: int = 3, - listen_only: Optional[bool] = False, + listen_only: bool | None = False, **kwargs: Any, ) -> None: """ @@ -377,8 +374,8 @@ def fd(self) -> bool: def _find_global_channel_idx( self, channel: int, - serial: Optional[int], - app_name: Optional[str], + serial: int | None, + app_name: str | None, channel_configs: list["VectorChannelConfig"], ) -> int: if serial is not None: @@ -565,10 +562,10 @@ def _check_can_settings( self, channel_mask: int, bitrate: int, - sample_point: Optional[float] = None, + sample_point: float | None = None, fd: bool = False, - data_bitrate: Optional[int] = None, - data_sample_point: Optional[float] = None, + data_bitrate: int | None = None, + data_sample_point: float | None = None, ) -> None: """Compare requested CAN settings to active settings in driver.""" vcc_list = get_channel_configs() @@ -656,7 +653,7 @@ def _check_can_settings( f"These are the currently active settings: {settings_string}." ) - def _apply_filters(self, filters: Optional[CanFilters]) -> None: + def _apply_filters(self, filters: CanFilters | None) -> None: if filters: # Only up to one filter per ID type allowed if len(filters) == 1 or ( @@ -706,9 +703,7 @@ def _apply_filters(self, filters: Optional[CanFilters]) -> None: except VectorOperationError as exc: LOG.warning("Could not reset filters: %s", exc) - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: end_time = time.time() + timeout if timeout is not None else None while True: @@ -741,7 +736,7 @@ def _recv_internal( # Wait a short time until we try again time.sleep(self.poll_interval) - def _recv_canfd(self) -> Optional[Message]: + def _recv_canfd(self) -> Message | None: xl_can_rx_event = xlclass.XLcanRxEvent() self.xldriver.xlCanReceive(self.port_handle, xl_can_rx_event) @@ -786,7 +781,7 @@ def _recv_canfd(self) -> Optional[Message]: data=data_struct.data[:dlc], ) - def _recv_can(self) -> Optional[Message]: + def _recv_can(self) -> Message | None: xl_event = xlclass.XLevent() event_count = ctypes.c_uint(1) self.xldriver.xlReceive(self.port_handle, event_count, xl_event) @@ -842,7 +837,7 @@ def handle_canfd_event(self, event: xlclass.XLcanRxEvent) -> None: `XL_CAN_EV_TAG_TX_ERROR`, `XL_TIMER` or `XL_CAN_EV_TAG_CHIP_STATE` tag. """ - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: self._send_sequence([msg]) def _send_sequence(self, msgs: Sequence[Message]) -> int: @@ -1030,7 +1025,7 @@ def popup_vector_hw_configuration(wait_for_finish: int = 0) -> None: @staticmethod def get_application_config( app_name: str, app_channel: int - ) -> tuple[Union[int, xldefine.XL_HardwareType], int, int]: + ) -> tuple[int | xldefine.XL_HardwareType, int, int]: """Retrieve information for an application in Vector Hardware Configuration. :param app_name: @@ -1076,7 +1071,7 @@ def get_application_config( def set_application_config( app_name: str, app_channel: int, - hw_type: Union[int, xldefine.XL_HardwareType], + hw_type: int | xldefine.XL_HardwareType, hw_index: int, hw_channel: int, **kwargs: Any, @@ -1170,7 +1165,7 @@ class VectorChannelConfig(NamedTuple): """NamedTuple which contains the channel properties from Vector XL API.""" name: str - hw_type: Union[int, xldefine.XL_HardwareType] + hw_type: int | xldefine.XL_HardwareType hw_index: int hw_channel: int channel_index: int @@ -1179,7 +1174,7 @@ class VectorChannelConfig(NamedTuple): channel_bus_capabilities: xldefine.XL_BusCapabilities is_on_bus: bool connected_bus_type: xldefine.XL_BusTypes - bus_params: Optional[VectorBusParams] + bus_params: VectorBusParams | None serial_number: int article_number: int transceiver_name: str @@ -1214,7 +1209,7 @@ def _get_xl_driver_config() -> xlclass.XLdriverConfig: def _read_bus_params_from_c_struct( bus_params: xlclass.XLbusParams, -) -> Optional[VectorBusParams]: +) -> VectorBusParams | None: bus_type = xldefine.XL_BusTypes(bus_params.busType) if bus_type is not xldefine.XL_BusTypes.XL_BUS_TYPE_CAN: return None @@ -1283,7 +1278,7 @@ def get_channel_configs() -> list[VectorChannelConfig]: return channel_list -def _hw_type(hw_type: int) -> Union[int, xldefine.XL_HardwareType]: +def _hw_type(hw_type: int) -> int | xldefine.XL_HardwareType: try: return xldefine.XL_HardwareType(hw_type) except ValueError: diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index b43df5e6c..779365893 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -1,13 +1,13 @@ """Exception/error declarations for the vector interface.""" -from typing import Any, Optional, Union +from typing import Any from can import CanError, CanInitializationError, CanOperationError class VectorError(CanError): def __init__( - self, error_code: Optional[int], error_string: str, function: str + self, error_code: int | None, error_string: str, function: str ) -> None: super().__init__( message=f"{function} failed ({error_string})", error_code=error_code @@ -16,7 +16,7 @@ def __init__( # keep reference to args for pickling self._args = error_code, error_string, function - def __reduce__(self) -> Union[str, tuple[Any, ...]]: + def __reduce__(self) -> str | tuple[Any, ...]: return type(self), self._args, {} diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index e4f68b0c4..ba33a6ea8 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -12,7 +12,7 @@ from copy import deepcopy from random import randint from threading import RLock -from typing import Any, Final, Optional +from typing import Any, Final from can import CanOperationError from can.bus import BusABC, CanProtocol @@ -118,9 +118,7 @@ def _check_if_open(self) -> None: if not self._open: raise CanOperationError("Cannot operate on a closed bus") - def _recv_internal( - self, timeout: Optional[float] - ) -> tuple[Optional[Message], bool]: + def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]: self._check_if_open() try: msg = self.queue.get(block=True, timeout=timeout) @@ -129,7 +127,7 @@ def _recv_internal( else: return msg, False - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: self._check_if_open() timestamp = msg.timestamp if self.preserve_timestamps else time.time() diff --git a/can/io/asc.py b/can/io/asc.py index e917953ff..2c80458c4 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -10,7 +10,7 @@ import re from collections.abc import Generator from datetime import datetime -from typing import Any, Final, Optional, TextIO, Union +from typing import Any, Final, TextIO from ..message import Message from ..typechecking import StringPathLike @@ -41,7 +41,7 @@ class ASCReader(TextIOMessageReader): def __init__( self, - file: Union[StringPathLike, TextIO], + file: StringPathLike | TextIO, base: str = "hex", relative_timestamp: bool = True, **kwargs: Any, @@ -64,10 +64,10 @@ def __init__( self.base = base self._converted_base = self._check_base(base) self.relative_timestamp = relative_timestamp - self.date: Optional[str] = None + self.date: str | None = None self.start_time = 0.0 # TODO - what is this used for? The ASC Writer only prints `absolute` - self.timestamps_format: Optional[str] = None + self.timestamps_format: str | None = None self.internal_events_logged = False def _extract_header(self) -> None: @@ -284,7 +284,7 @@ def __iter__(self) -> Generator[Message, None, None]: # J1939 message or some other unsupported event continue - msg_kwargs: dict[str, Union[float, bool, int]] = {} + msg_kwargs: dict[str, float | bool | int] = {} try: _timestamp, channel, rest_of_message = line.split(None, 2) timestamp = float(_timestamp) + self.start_time @@ -347,7 +347,7 @@ class ASCWriter(TextIOMessageWriter): def __init__( self, - file: Union[StringPathLike, TextIO], + file: StringPathLike | TextIO, channel: int = 1, **kwargs: Any, ) -> None: @@ -393,7 +393,7 @@ def stop(self) -> None: self.file.write("End TriggerBlock\n") super().stop() - def log_event(self, message: str, timestamp: Optional[float] = None) -> None: + def log_event(self, message: str, timestamp: float | None = None) -> None: """Add a message to the log file. :param message: an arbitrary message diff --git a/can/io/blf.py b/can/io/blf.py index 2c9050d54..77bd02fae 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -19,7 +19,7 @@ import zlib from collections.abc import Generator, Iterator from decimal import Decimal -from typing import Any, BinaryIO, Optional, Union, cast +from typing import Any, BinaryIO, cast from ..message import Message from ..typechecking import StringPathLike @@ -104,7 +104,7 @@ class BLFParseError(Exception): TIME_ONE_NANS_FACTOR = Decimal("1e-9") -def timestamp_to_systemtime(timestamp: Optional[float]) -> TSystemTime: +def timestamp_to_systemtime(timestamp: float | None) -> TSystemTime: if timestamp is None or timestamp < 631152000: # Probably not a Unix timestamp return 0, 0, 0, 0, 0, 0, 0, 0 @@ -148,7 +148,7 @@ class BLFReader(BinaryIOMessageReader): def __init__( self, - file: Union[StringPathLike, BinaryIO], + file: StringPathLike | BinaryIO, **kwargs: Any, ) -> None: """ @@ -386,7 +386,7 @@ class BLFWriter(BinaryIOMessageWriter): def __init__( self, - file: Union[StringPathLike, BinaryIO], + file: StringPathLike | BinaryIO, append: bool = False, channel: int = 1, compression_level: int = -1, @@ -430,10 +430,10 @@ def __init__( raise BLFParseError("Unexpected file format") self.uncompressed_size = header[11] self.object_count = header[12] - self.start_timestamp: Optional[float] = systemtime_to_timestamp( + self.start_timestamp: float | None = systemtime_to_timestamp( cast("TSystemTime", header[14:22]) ) - self.stop_timestamp: Optional[float] = systemtime_to_timestamp( + self.stop_timestamp: float | None = systemtime_to_timestamp( cast("TSystemTime", header[22:30]) ) # Jump to the end of the file @@ -508,7 +508,7 @@ def on_message_received(self, msg: Message) -> None: data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, can_data) self._add_object(CAN_MESSAGE, data, msg.timestamp) - def log_event(self, text: str, timestamp: Optional[float] = None) -> None: + def log_event(self, text: str, timestamp: float | None = None) -> None: """Add an arbitrary message to the log file as a global marker. :param str text: @@ -530,7 +530,7 @@ def log_event(self, text: str, timestamp: Optional[float] = None) -> None: self._add_object(GLOBAL_MARKER, data + encoded + marker + comment, timestamp) def _add_object( - self, obj_type: int, data: bytes, timestamp: Optional[float] = None + self, obj_type: int, data: bytes, timestamp: float | None = None ) -> None: if timestamp is None: timestamp = self.stop_timestamp or time.time() @@ -574,7 +574,7 @@ def _flush(self) -> None: self._buffer = [tail] self._buffer_size = len(tail) if not self.compression_level: - data: "Union[bytes, memoryview[int]]" = uncompressed_data # noqa: UP037 + data: "bytes | memoryview[int]" = uncompressed_data # noqa: UP037 method = NO_COMPRESSION else: data = zlib.compress(uncompressed_data, self.compression_level) diff --git a/can/io/canutils.py b/can/io/canutils.py index 78d081637..800125b73 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -6,7 +6,7 @@ import logging from collections.abc import Generator -from typing import Any, Optional, TextIO, Union +from typing import Any, TextIO from can.message import Message @@ -36,7 +36,7 @@ class CanutilsLogReader(TextIOMessageReader): def __init__( self, - file: Union[StringPathLike, TextIO], + file: StringPathLike | TextIO, **kwargs: Any, ) -> None: """ @@ -63,7 +63,7 @@ def __iter__(self) -> Generator[Message, None, None]: timestamp = float(timestamp_string[1:-1]) can_id_string, data = frame.split("#", maxsplit=1) - channel: Union[int, str] + channel: int | str if channel_string.isdigit(): channel = int(channel_string) else: @@ -132,7 +132,7 @@ class CanutilsLogWriter(TextIOMessageWriter): def __init__( self, - file: Union[StringPathLike, TextIO], + file: StringPathLike | TextIO, channel: str = "vcan0", append: bool = False, **kwargs: Any, @@ -149,7 +149,7 @@ def __init__( super().__init__(file, mode="a" if append else "w") self.channel = channel - self.last_timestamp: Optional[float] = None + self.last_timestamp: float | None = None def on_message_received(self, msg: Message) -> None: # this is the case for the very first message: diff --git a/can/io/csv.py b/can/io/csv.py index 865ef9af0..0c8ba02a4 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -11,7 +11,7 @@ from base64 import b64decode, b64encode from collections.abc import Generator -from typing import Any, TextIO, Union +from typing import Any, TextIO from can.message import Message @@ -30,7 +30,7 @@ class CSVReader(TextIOMessageReader): def __init__( self, - file: Union[StringPathLike, TextIO], + file: StringPathLike | TextIO, **kwargs: Any, ) -> None: """ @@ -89,7 +89,7 @@ class CSVWriter(TextIOMessageWriter): def __init__( self, - file: Union[StringPathLike, TextIO], + file: StringPathLike | TextIO, append: bool = False, **kwargs: Any, ) -> None: diff --git a/can/io/generic.py b/can/io/generic.py index 21fc3e8e8..bda4e1cce 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -20,10 +20,8 @@ BinaryIO, Generic, Literal, - Optional, TextIO, TypeVar, - Union, ) from typing_extensions import Self @@ -71,9 +69,9 @@ def __enter__(self) -> Self: def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> Literal[False]: """Exit the context manager and ensure proper cleanup.""" self.stop() @@ -110,7 +108,7 @@ class FileIOMessageWriter(SizedMessageWriter, Generic[_IoTypeVar]): file: _IoTypeVar @abstractmethod - def __init__(self, file: Union[StringPathLike, _IoTypeVar], **kwargs: Any) -> None: + def __init__(self, file: StringPathLike | _IoTypeVar, **kwargs: Any) -> None: pass def stop(self) -> None: @@ -122,7 +120,7 @@ def file_size(self) -> int: return self.file.tell() -class TextIOMessageWriter(FileIOMessageWriter[Union[TextIO, TextIOWrapper]], ABC): +class TextIOMessageWriter(FileIOMessageWriter[TextIO | TextIOWrapper], ABC): """Text-based message writer implementation. :param file: Text file to write to @@ -132,8 +130,8 @@ class TextIOMessageWriter(FileIOMessageWriter[Union[TextIO, TextIOWrapper]], ABC def __init__( self, - file: Union[StringPathLike, TextIO, TextIOWrapper], - mode: "Union[OpenTextModeUpdating, OpenTextModeWriting]" = "w", + file: StringPathLike | TextIO | TextIOWrapper, + mode: "OpenTextModeUpdating | OpenTextModeWriting" = "w", **kwargs: Any, ) -> None: if isinstance(file, (str, os.PathLike)): @@ -144,7 +142,7 @@ def __init__( self.file = file -class BinaryIOMessageWriter(FileIOMessageWriter[Union[BinaryIO, BufferedIOBase]], ABC): +class BinaryIOMessageWriter(FileIOMessageWriter[BinaryIO | BufferedIOBase], ABC): """Binary file message writer implementation. :param file: Binary file to write to @@ -152,10 +150,10 @@ class BinaryIOMessageWriter(FileIOMessageWriter[Union[BinaryIO, BufferedIOBase]] :param kwargs: Additional implementation specific arguments """ - def __init__( + def __init__( # pylint: disable=unused-argument self, - file: Union[StringPathLike, BinaryIO, BufferedIOBase], - mode: "Union[OpenBinaryModeUpdating, OpenBinaryModeWriting]" = "wb", + file: StringPathLike | BinaryIO | BufferedIOBase, + mode: "OpenBinaryModeUpdating | OpenBinaryModeWriting" = "wb", **kwargs: Any, ) -> None: if isinstance(file, (str, os.PathLike)): @@ -188,9 +186,9 @@ def __enter__(self) -> Self: def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> Literal[False]: self.stop() return False @@ -210,14 +208,14 @@ class FileIOMessageReader(MessageReader, Generic[_IoTypeVar]): file: _IoTypeVar @abstractmethod - def __init__(self, file: Union[StringPathLike, _IoTypeVar], **kwargs: Any) -> None: + def __init__(self, file: StringPathLike | _IoTypeVar, **kwargs: Any) -> None: pass def stop(self) -> None: self.file.close() -class TextIOMessageReader(FileIOMessageReader[Union[TextIO, TextIOWrapper]], ABC): +class TextIOMessageReader(FileIOMessageReader[TextIO | TextIOWrapper], ABC): """Text-based message reader implementation. :param file: Text file to read from @@ -227,7 +225,7 @@ class TextIOMessageReader(FileIOMessageReader[Union[TextIO, TextIOWrapper]], ABC def __init__( self, - file: Union[StringPathLike, TextIO, TextIOWrapper], + file: StringPathLike | TextIO | TextIOWrapper, mode: "OpenTextModeReading" = "r", **kwargs: Any, ) -> None: @@ -239,7 +237,7 @@ def __init__( self.file = file -class BinaryIOMessageReader(FileIOMessageReader[Union[BinaryIO, BufferedIOBase]], ABC): +class BinaryIOMessageReader(FileIOMessageReader[BinaryIO | BufferedIOBase], ABC): """Binary file message reader implementation. :param file: Binary file to read from @@ -247,9 +245,9 @@ class BinaryIOMessageReader(FileIOMessageReader[Union[BinaryIO, BufferedIOBase]] :param kwargs: Additional implementation specific arguments """ - def __init__( + def __init__( # pylint: disable=unused-argument self, - file: Union[StringPathLike, BinaryIO, BufferedIOBase], + file: StringPathLike | BinaryIO | BufferedIOBase, mode: "OpenBinaryModeReading" = "rb", **kwargs: Any, ) -> None: diff --git a/can/io/logger.py b/can/io/logger.py index 4d8ddc070..5009c9756 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -6,15 +6,14 @@ import os import pathlib from abc import ABC, abstractmethod +from collections.abc import Callable from datetime import datetime from types import TracebackType from typing import ( Any, - Callable, ClassVar, Final, Literal, - Optional, ) from typing_extensions import Self @@ -107,7 +106,7 @@ def _compress(filename: StringPathLike, **kwargs: Any) -> FileIOMessageWriter[An def Logger( # noqa: N802 - filename: Optional[StringPathLike], **kwargs: Any + filename: StringPathLike | None, **kwargs: Any ) -> MessageWriter: """Find and return the appropriate :class:`~can.io.generic.MessageWriter` instance for a given file suffix. @@ -177,12 +176,12 @@ class BaseRotatingLogger(MessageWriter, ABC): #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename` #: method delegates to this callable. The parameters passed to the callable are #: those passed to :meth:`~BaseRotatingLogger.rotation_filename`. - namer: Optional[Callable[[StringPathLike], StringPathLike]] = None + namer: Callable[[StringPathLike], StringPathLike] | None = None #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotate` method #: delegates to this callable. The parameters passed to the callable are those #: passed to :meth:`~BaseRotatingLogger.rotate`. - rotator: Optional[Callable[[StringPathLike, StringPathLike], None]] = None + rotator: Callable[[StringPathLike, StringPathLike], None] | None = None #: An integer counter to track the number of rollovers. rollover_count: int = 0 @@ -286,9 +285,9 @@ def __enter__(self) -> Self: def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> Literal[False]: self.stop() return False diff --git a/can/io/mf4.py b/can/io/mf4.py index bf594e3a5..fcde2e193 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -13,7 +13,7 @@ from hashlib import md5 from io import BufferedIOBase, BytesIO from pathlib import Path -from typing import Any, BinaryIO, Optional, Union, cast +from typing import Any, BinaryIO, cast from ..message import Message from ..typechecking import StringPathLike @@ -93,8 +93,8 @@ class MF4Writer(BinaryIOMessageWriter): def __init__( self, - file: Union[StringPathLike, BinaryIO], - database: Optional[StringPathLike] = None, + file: StringPathLike | BinaryIO, + database: StringPathLike | None = None, compression_level: int = 2, **kwargs: Any, ) -> None: @@ -458,7 +458,7 @@ def __iter__(self) -> Generator[Message, None, None]: def __init__( self, - file: Union[StringPathLike, BinaryIO], + file: StringPathLike | BinaryIO, **kwargs: Any, ) -> None: """ @@ -497,7 +497,7 @@ def __iter__(self) -> Iterator[Message]: # No data, skip continue - acquisition_source: Optional[Source] = channel_group.acq_source + acquisition_source: Source | None = channel_group.acq_source if acquisition_source is None: # No source information, skip diff --git a/can/io/printer.py b/can/io/printer.py index 786cb7261..c41a83691 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -5,7 +5,7 @@ import logging import sys from io import TextIOWrapper -from typing import Any, TextIO, Union +from typing import Any, TextIO from ..message import Message from ..typechecking import StringPathLike @@ -26,7 +26,7 @@ class Printer(TextIOMessageWriter): def __init__( self, - file: Union[StringPathLike, TextIO, TextIOWrapper] = sys.stdout, + file: StringPathLike | TextIO | TextIOWrapper = sys.stdout, append: bool = False, **kwargs: Any, ) -> None: diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 73aa2961c..5f4885adb 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -9,9 +9,7 @@ import threading import time from collections.abc import Generator, Iterator -from typing import Any - -from typing_extensions import TypeAlias +from typing import Any, TypeAlias from can.listener import BufferedReader from can.message import Message diff --git a/can/io/trc.py b/can/io/trc.py index fa8ee88e7..c02bdcfe9 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -9,11 +9,11 @@ import logging import os -from collections.abc import Generator +from collections.abc import Callable, Generator from datetime import datetime, timedelta, timezone from enum import Enum from io import TextIOWrapper -from typing import Any, Callable, Optional, TextIO, Union +from typing import Any, TextIO from ..message import Message from ..typechecking import StringPathLike @@ -45,7 +45,7 @@ class TRCReader(TextIOMessageReader): def __init__( self, - file: Union[StringPathLike, TextIO], + file: StringPathLike | TextIO, **kwargs: Any, ) -> None: """ @@ -62,12 +62,10 @@ def __init__( if not self.file: raise ValueError("The given file cannot be None") - self._parse_cols: Callable[[tuple[str, ...]], Optional[Message]] = ( - lambda x: None - ) + self._parse_cols: Callable[[tuple[str, ...]], Message | None] = lambda x: None @property - def start_time(self) -> Optional[datetime]: + def start_time(self) -> datetime | None: if self._start_time: return datetime.fromtimestamp(self._start_time, timezone.utc) return None @@ -140,7 +138,7 @@ def _extract_header(self) -> str: return line - def _parse_msg_v1_0(self, cols: tuple[str, ...]) -> Optional[Message]: + def _parse_msg_v1_0(self, cols: tuple[str, ...]) -> Message | None: arbit_id = cols[2] if arbit_id == "FFFFFFFF": logger.info("TRCReader: Dropping bus info line") @@ -158,7 +156,7 @@ def _parse_msg_v1_0(self, cols: tuple[str, ...]) -> Optional[Message]: msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) return msg - def _parse_msg_v1_1(self, cols: tuple[str, ...]) -> Optional[Message]: + def _parse_msg_v1_1(self, cols: tuple[str, ...]) -> Message | None: arbit_id = cols[3] msg = Message() @@ -174,7 +172,7 @@ def _parse_msg_v1_1(self, cols: tuple[str, ...]) -> Optional[Message]: msg.is_rx = cols[2] == "Rx" return msg - def _parse_msg_v1_3(self, cols: tuple[str, ...]) -> Optional[Message]: + def _parse_msg_v1_3(self, cols: tuple[str, ...]) -> Message | None: arbit_id = cols[4] msg = Message() @@ -190,7 +188,7 @@ def _parse_msg_v1_3(self, cols: tuple[str, ...]) -> Optional[Message]: msg.is_rx = cols[3] == "Rx" return msg - def _parse_msg_v2_x(self, cols: tuple[str, ...]) -> Optional[Message]: + def _parse_msg_v2_x(self, cols: tuple[str, ...]) -> Message | None: type_ = cols[self.columns["T"]] bus = self.columns.get("B", None) @@ -218,7 +216,7 @@ def _parse_msg_v2_x(self, cols: tuple[str, ...]) -> Optional[Message]: return msg - def _parse_cols_v1_1(self, cols: tuple[str, ...]) -> Optional[Message]: + def _parse_cols_v1_1(self, cols: tuple[str, ...]) -> Message | None: dtype = cols[2] if dtype in ("Tx", "Rx"): return self._parse_msg_v1_1(cols) @@ -226,7 +224,7 @@ def _parse_cols_v1_1(self, cols: tuple[str, ...]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_v1_3(self, cols: tuple[str, ...]) -> Optional[Message]: + def _parse_cols_v1_3(self, cols: tuple[str, ...]) -> Message | None: dtype = cols[3] if dtype in ("Tx", "Rx"): return self._parse_msg_v1_3(cols) @@ -234,7 +232,7 @@ def _parse_cols_v1_3(self, cols: tuple[str, ...]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_v2_x(self, cols: tuple[str, ...]) -> Optional[Message]: + def _parse_cols_v2_x(self, cols: tuple[str, ...]) -> Message | None: dtype = cols[self.columns["T"]] if dtype in {"DT", "FD", "FB", "FE", "BI", "RR"}: return self._parse_msg_v2_x(cols) @@ -242,7 +240,7 @@ def _parse_cols_v2_x(self, cols: tuple[str, ...]) -> Optional[Message]: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_line(self, line: str) -> Optional[Message]: + def _parse_line(self, line: str) -> Message | None: logger.debug("TRCReader: Parse '%s'", line) try: cols = tuple(line.split(maxsplit=self._num_columns)) @@ -292,7 +290,7 @@ class TRCWriter(TextIOMessageWriter): def __init__( self, - file: Union[StringPathLike, TextIO, TextIOWrapper], + file: StringPathLike | TextIO | TextIOWrapper, channel: int = 1, **kwargs: Any, ) -> None: @@ -314,7 +312,7 @@ def __init__( self.filepath = os.path.abspath(self.file.name) self.header_written = False self.msgnr = 0 - self.first_timestamp: Optional[float] = None + self.first_timestamp: float | None = None self.file_version = TRCFileVersion.V2_1 self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 self._format_message = self._format_message_init diff --git a/can/listener.py b/can/listener.py index 7f8f436a0..1e289bea6 100644 --- a/can/listener.py +++ b/can/listener.py @@ -3,12 +3,11 @@ """ import asyncio -import sys import warnings from abc import ABC, abstractmethod from collections.abc import AsyncIterator from queue import Empty, SimpleQueue -from typing import Any, Optional +from typing import Any from can.bus import BusABC from can.message import Message @@ -99,7 +98,7 @@ def on_message_received(self, msg: Message) -> None: else: self.buffer.put(msg) - def get_message(self, timeout: float = 0.5) -> Optional[Message]: + def get_message(self, timeout: float = 0.5) -> Message | None: """ Attempts to retrieve the message that has been in the queue for the longest amount of time (FIFO). If no message is available, it blocks for given timeout or until a @@ -146,12 +145,6 @@ def __init__(self, **kwargs: Any) -> None: DeprecationWarning, stacklevel=2, ) - if sys.version_info < (3, 10): - self.buffer = asyncio.Queue( # pylint: disable=unexpected-keyword-arg - loop=kwargs["loop"] - ) - return - self.buffer = asyncio.Queue() def on_message_received(self, msg: Message) -> None: diff --git a/can/logger.py b/can/logger.py index 8274d6668..537356643 100644 --- a/can/logger.py +++ b/can/logger.py @@ -4,7 +4,6 @@ from datetime import datetime from typing import ( TYPE_CHECKING, - Union, ) from can import BusState, Logger, SizedRotatingLogger @@ -110,7 +109,7 @@ def main() -> None: print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}") print(f"Can Logger (Started on {datetime.now()})") - logger: Union[MessageWriter, BaseRotatingLogger] + logger: MessageWriter | BaseRotatingLogger if results.file_size: logger = SizedRotatingLogger( base_filename=results.log_file, diff --git a/can/message.py b/can/message.py index c1fbffd21..3e60ca641 100644 --- a/can/message.py +++ b/can/message.py @@ -8,7 +8,7 @@ from copy import deepcopy from math import isinf, isnan -from typing import Any, Optional +from typing import Any from . import typechecking @@ -54,9 +54,9 @@ def __init__( # pylint: disable=too-many-locals, too-many-arguments 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, + channel: typechecking.Channel | None = None, + dlc: int | None = None, + data: typechecking.CanData | None = None, is_fd: bool = False, is_rx: bool = True, bitrate_switch: bool = False, @@ -185,7 +185,7 @@ def __repr__(self) -> str: return f"can.Message({', '.join(args)})" - def __format__(self, format_spec: Optional[str]) -> str: + def __format__(self, format_spec: str | None) -> str: if not format_spec: return self.__str__() else: @@ -210,7 +210,7 @@ def __copy__(self) -> "Message": error_state_indicator=self.error_state_indicator, ) - def __deepcopy__(self, memo: Optional[dict[int, Any]]) -> "Message": + def __deepcopy__(self, memo: dict[int, Any] | None) -> "Message": return Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, @@ -289,7 +289,7 @@ def _check(self) -> None: def equals( self, other: "Message", - timestamp_delta: Optional[float] = 1.0e-6, + timestamp_delta: float | None = 1.0e-6, check_channel: bool = True, check_direction: bool = True, ) -> bool: diff --git a/can/notifier.py b/can/notifier.py index a2ee512fc..cb91cf7b4 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -7,16 +7,13 @@ import logging import threading import time -from collections.abc import Awaitable, Iterable +from collections.abc import Awaitable, Callable, Iterable from contextlib import AbstractContextManager from types import TracebackType from typing import ( Any, - Callable, Final, NamedTuple, - Optional, - Union, ) from can.bus import BusABC @@ -25,7 +22,7 @@ logger = logging.getLogger("can.Notifier") -MessageRecipient = Union[Listener, Callable[[Message], Union[Awaitable[None], None]]] +MessageRecipient = Listener | Callable[[Message], Awaitable[None] | None] class _BusNotifierPair(NamedTuple): @@ -109,10 +106,10 @@ class Notifier(AbstractContextManager["Notifier"]): def __init__( self, - bus: Union[BusABC, list[BusABC]], + bus: BusABC | list[BusABC], listeners: Iterable[MessageRecipient], timeout: float = 1.0, - loop: Optional[asyncio.AbstractEventLoop] = None, + loop: asyncio.AbstractEventLoop | None = None, ) -> None: """Manages the distribution of :class:`~can.Message` instances to listeners. @@ -142,19 +139,19 @@ def __init__( self._loop = loop #: Exception raised in thread - self.exception: Optional[Exception] = None + self.exception: Exception | None = None self._stopped = False self._lock = threading.Lock() - self._readers: list[Union[int, threading.Thread]] = [] + self._readers: list[int | threading.Thread] = [] self._tasks: set[asyncio.Task] = set() _bus_list: list[BusABC] = bus if isinstance(bus, list) else [bus] for each_bus in _bus_list: self.add_bus(each_bus) @property - def bus(self) -> Union[BusABC, tuple["BusABC", ...]]: + def bus(self) -> BusABC | tuple["BusABC", ...]: """Return the associated bus or a tuple of buses.""" if len(self._bus_list) == 1: return self._bus_list[0] @@ -322,9 +319,9 @@ def find_instances(bus: BusABC) -> tuple["Notifier", ...]: def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: if not self._stopped: self.stop() diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 35a4f400c..71d6a5536 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -1,6 +1,6 @@ from contextlib import nullcontext from threading import RLock -from typing import Any, Optional +from typing import Any from can import typechecking from can.bus import BusABC, BusState, CanProtocol @@ -40,9 +40,9 @@ class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method def __init__( self, - channel: Optional[typechecking.Channel] = None, - interface: Optional[str] = None, - config_context: Optional[str] = None, + channel: typechecking.Channel | None = None, + interface: str | None = None, + config_context: str | None = None, ignore_config: bool = False, **kwargs: Any, ) -> None: @@ -67,11 +67,11 @@ def __init__( self._lock_send = RLock() self._lock_recv = RLock() - def recv(self, timeout: Optional[float] = None) -> Optional[Message]: + def recv(self, timeout: float | None = None) -> Message | None: with self._lock_recv: return self.__wrapped__.recv(timeout=timeout) - def send(self, msg: Message, timeout: Optional[float] = None) -> None: + def send(self, msg: Message, timeout: float | None = None) -> None: with self._lock_send: return self.__wrapped__.send(msg=msg, timeout=timeout) @@ -79,16 +79,16 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: # `send` method is already synchronized @property - def filters(self) -> Optional[typechecking.CanFilters]: + def filters(self) -> typechecking.CanFilters | None: with self._lock_recv: return self.__wrapped__.filters @filters.setter - def filters(self, filters: Optional[typechecking.CanFilters]) -> None: + def filters(self, filters: typechecking.CanFilters | None) -> None: with self._lock_recv: self.__wrapped__.filters = filters - def set_filters(self, filters: Optional[typechecking.CanFilters] = None) -> None: + def set_filters(self, filters: typechecking.CanFilters | None = None) -> None: with self._lock_recv: return self.__wrapped__.set_filters(filters=filters) diff --git a/can/typechecking.py b/can/typechecking.py index 8c25e8b57..56ac5927f 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -1,14 +1,10 @@ """Types for mypy type-checking""" import io +import os import sys from collections.abc import Iterable, Sequence -from typing import IO, TYPE_CHECKING, Any, NewType, Union - -if sys.version_info >= (3, 10): - from typing import TypeAlias -else: - from typing_extensions import TypeAlias +from typing import IO, TYPE_CHECKING, Any, NewType, TypeAlias if sys.version_info >= (3, 12): from typing import TypedDict @@ -17,7 +13,6 @@ if TYPE_CHECKING: - import os import struct @@ -37,24 +32,24 @@ class CanFilter(_CanFilterBase, total=False): # this should have the same typing info. # # See: https://github.com/python/typing/issues/593 -CanData = Union[bytes, bytearray, int, Iterable[int]] +CanData = bytes | bytearray | int | Iterable[int] # Used for the Abstract Base Class ChannelStr = str ChannelInt = int -Channel = Union[ChannelInt, ChannelStr, Sequence[ChannelInt]] +Channel = ChannelInt | ChannelStr | Sequence[ChannelInt] # Used by the IO module -FileLike = Union[IO[Any], io.TextIOWrapper, io.BufferedIOBase] -StringPathLike = Union[str, "os.PathLike[str]"] +FileLike = IO[Any] | io.TextIOWrapper | io.BufferedIOBase +StringPathLike = str | os.PathLike[str] BusConfig = NewType("BusConfig", dict[str, Any]) # Used by CLI scripts -TAdditionalCliArgs: TypeAlias = dict[str, Union[str, int, float, bool]] +TAdditionalCliArgs: TypeAlias = dict[str, str | int | float | bool] TDataStructs: TypeAlias = dict[ - Union[int, tuple[int, ...]], - "Union[struct.Struct, tuple[struct.Struct, *tuple[float, ...]]]", + int | tuple[int, ...], + "struct.Struct | tuple[struct.Struct, *tuple[float, ...]]", ] @@ -63,7 +58,7 @@ class AutoDetectedConfig(TypedDict): channel: Channel -ReadableBytesLike = Union[bytes, bytearray, memoryview] +ReadableBytesLike = bytes | bytearray | memoryview class BitTimingDict(TypedDict): diff --git a/can/util.py b/can/util.py index 584b7dfa9..4cbeec60e 100644 --- a/can/util.py +++ b/can/util.py @@ -12,15 +12,12 @@ import platform import re import warnings -from collections.abc import Iterable +from collections.abc import Callable, Iterable from configparser import ConfigParser from time import get_clock_info, perf_counter, time from typing import ( Any, - Callable, - Optional, TypeVar, - Union, cast, ) @@ -50,7 +47,7 @@ def load_file_config( - path: Optional[typechecking.StringPathLike] = None, section: str = "default" + path: typechecking.StringPathLike | None = None, section: str = "default" ) -> dict[str, str]: """ Loads configuration from file with following content:: @@ -83,7 +80,7 @@ def load_file_config( return _config -def load_environment_config(context: Optional[str] = None) -> dict[str, str]: +def load_environment_config(context: str | None = None) -> dict[str, str]: """ Loads config dict from environmental variables (if set): @@ -120,9 +117,9 @@ def load_environment_config(context: Optional[str] = None) -> dict[str, str]: def load_config( - path: Optional[typechecking.StringPathLike] = None, - config: Optional[dict[str, Any]] = None, - context: Optional[str] = None, + path: typechecking.StringPathLike | None = None, + config: dict[str, Any] | None = None, + context: str | None = None, ) -> typechecking.BusConfig: """ Returns a dict with configuration details which is loaded from (in this order): @@ -176,7 +173,7 @@ def load_config( # Use the given dict for default values config_sources = cast( - "Iterable[Union[dict[str, Any], Callable[[Any], dict[str, Any]]]]", + "Iterable[dict[str, Any] | Callable[[Any], dict[str, Any]]]", [ given_config, can.rc, @@ -258,7 +255,7 @@ def _create_bus_config(config: dict[str, Any]) -> typechecking.BusConfig: return cast("typechecking.BusConfig", config) -def _dict2timing(data: dict[str, Any]) -> Union[BitTiming, BitTimingFd, None]: +def _dict2timing(data: dict[str, Any]) -> BitTiming | BitTimingFd | None: """Try to instantiate a :class:`~can.BitTiming` or :class:`~can.BitTimingFd` from a dictionary. Return `None` if not possible.""" @@ -325,7 +322,7 @@ def dlc2len(dlc: int) -> int: return CAN_FD_DLC[dlc] if dlc <= 15 else 64 -def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]: +def channel2int(channel: typechecking.Channel | None) -> int | None: """Try to convert the channel to an integer. :param channel: @@ -348,8 +345,8 @@ def channel2int(channel: Optional[typechecking.Channel]) -> Optional[int]: def deprecated_args_alias( deprecation_start: str, - deprecation_end: Optional[str] = None, - **aliases: Optional[str], + deprecation_end: str | None = None, + **aliases: str | None, ) -> Callable[[Callable[P1, T1]], Callable[P1, T1]]: """Allows to rename/deprecate a function kwarg(s) and optionally have the deprecated kwarg(s) set as alias(es) @@ -399,9 +396,9 @@ def wrapper(*args: P1.args, **kwargs: P1.kwargs) -> T1: def _rename_kwargs( func_name: str, start: str, - end: Optional[str], + end: str | None, kwargs: dict[str, Any], - aliases: dict[str, Optional[str]], + aliases: dict[str, str | None], ) -> None: """Helper function for `deprecated_args_alias`""" for alias, new in aliases.items(): @@ -501,7 +498,7 @@ def time_perfcounter_correlation() -> tuple[float, float]: return t1, performance_counter -def cast_from_string(string_val: str) -> Union[str, int, float, bool]: +def cast_from_string(string_val: str) -> str | int | float | bool: """Perform trivial type conversion from :class:`str` values. :param string_val: diff --git a/can/viewer.py b/can/viewer.py index 97bda1676..8d9d228bb 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -173,7 +173,9 @@ def unpack_data(cmd: int, cmd_to_struct: TDataStructs, data: bytes) -> list[floa # The conversion from raw values to SI-units are given in the rest of the tuple values = [ d // val if isinstance(val, int) else float(d) / val - for d, val in zip(struct_t.unpack(data), value[1:]) + for d, val in zip( + struct_t.unpack(data), value[1:], strict=False + ) ] else: # No conversion from SI-units is needed diff --git a/doc/changelog.d/1996.removed.md b/doc/changelog.d/1996.removed.md new file mode 100644 index 000000000..77458ad75 --- /dev/null +++ b/doc/changelog.d/1996.removed.md @@ -0,0 +1 @@ +Remove support for end-of-life Python 3.9. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index bc526ce73..551a7f3bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "packaging >= 23.1", "typing_extensions>=3.10.0.0", ] -requires-python = ">=3.9" +requires-python = ">=3.10" license = "LGPL-3.0-only" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -27,7 +27,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -89,9 +88,9 @@ docs = [ ] lint = [ "pylint==3.3.*", - "ruff==0.12.11", - "black==25.1.*", - "mypy==1.17.*", + "ruff==0.14.*", + "black==25.9.*", + "mypy==1.18.*", ] test = [ "pytest==8.4.*", @@ -100,7 +99,7 @@ test = [ "coveralls==4.0.*", "pytest-cov==6.2.*", "coverage==7.10.*", - "hypothesis>=6.136,<6.138", + "hypothesis==6.*", "parameterized==0.9.*", ] dev = [ @@ -132,6 +131,7 @@ disallow_incomplete_defs = true warn_redundant_casts = true warn_unused_ignores = true exclude = [ + "^build", "^doc/conf.py$", "^test", "^can/interfaces/etas", diff --git a/tox.ini b/tox.ini index 5f393cb93..4e96291ed 100644 --- a/tox.ini +++ b/tox.ini @@ -69,11 +69,11 @@ dependency_groups = lint extras = commands = - mypy --python-version 3.9 . mypy --python-version 3.10 . mypy --python-version 3.11 . mypy --python-version 3.12 . mypy --python-version 3.13 . + mypy --python-version 3.14 . [pytest] testpaths = test From 39a2c7fedcde0564957e10c78ebd49e8f03463c2 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:03:48 +0200 Subject: [PATCH 1224/1235] add loop arg to player script (#1986) --- can/player.py | 95 +++++++++++++++++++++++---------- doc/changelog.d/1815.added.md | 1 + doc/changelog.d/1815.removed.md | 1 + test/test_player.py | 55 ++++++++++++++++--- 4 files changed, 117 insertions(+), 35 deletions(-) create mode 100644 doc/changelog.d/1815.added.md create mode 100644 doc/changelog.d/1815.removed.md diff --git a/can/player.py b/can/player.py index a92cccc3d..6190a58d8 100644 --- a/can/player.py +++ b/can/player.py @@ -7,6 +7,7 @@ import argparse import errno +import math import sys from datetime import datetime from typing import TYPE_CHECKING, cast @@ -26,19 +27,41 @@ from can import Message +def _parse_loop(value: str) -> int | float: + """Parse the loop argument, allowing integer or 'i' for infinite.""" + if value == "i": + return float("inf") + try: + return int(value) + except ValueError as exc: + err_msg = "Loop count must be an integer or 'i' for infinite." + raise argparse.ArgumentTypeError(err_msg) from exc + + +def _format_player_start_message(iteration: int, loop_count: int | float) -> str: + """ + Generate a status message indicating the start of a CAN log replay iteration. + + :param iteration: + The current loop iteration (zero-based). + :param loop_count: + Total number of replay loops, or infinity for endless replay. + :return: + A formatted string describing the replay start and loop information. + """ + if loop_count < 2: + loop_info = "" + else: + loop_val = "∞" if math.isinf(loop_count) else str(loop_count) + loop_info = f" [loop {iteration + 1}/{loop_val}]" + return f"Can LogReader (Started on {datetime.now()}){loop_info}" + + def main() -> None: parser = argparse.ArgumentParser(description="Replay CAN traffic.") player_group = parser.add_argument_group("Player arguments") - player_group.add_argument( - "-f", - "--file_name", - dest="log_file", - help="Path and base log filename, for supported types see can.LogReader.", - default=None, - ) - player_group.add_argument( "-v", action="count", @@ -73,9 +96,20 @@ def main() -> None: "--skip", type=float, default=60 * 60 * 24, - help=" skip gaps greater than 's' seconds", + help="Skip gaps greater than 's' seconds between messages. " + "Default is 86400 (24 hours), meaning only very large gaps are skipped. " + "Set to 0 to never skip any gaps (all delays are preserved). " + "Set to a very small value (e.g., 1e-4) " + "to skip all gaps and send messages as fast as possible.", + ) + player_group.add_argument( + "-l", + "--loop", + type=_parse_loop, + metavar="NUM", + default=1, + help="Replay file NUM times. Use 'i' for infinite loop (default: 1)", ) - player_group.add_argument( "infile", metavar="input-file", @@ -103,25 +137,28 @@ def main() -> None: error_frames = results.error_frames with create_bus_from_namespace(results) as bus: - with LogReader(results.infile, **additional_config) as reader: - in_sync = MessageSync( - cast("Iterable[Message]", reader), - timestamps=results.timestamps, - gap=results.gap, - skip=results.skip, - ) - - print(f"Can LogReader (Started on {datetime.now()})") - - try: - for message in in_sync: - if message.is_error_frame and not error_frames: - continue - if verbosity >= 3: - print(message) - bus.send(message) - except KeyboardInterrupt: - pass + loop_count: int | float = results.loop + iteration = 0 + try: + while iteration < loop_count: + with LogReader(results.infile, **additional_config) as reader: + in_sync = MessageSync( + cast("Iterable[Message]", reader), + timestamps=results.timestamps, + gap=results.gap, + skip=results.skip, + ) + print(_format_player_start_message(iteration, loop_count)) + + for message in in_sync: + if message.is_error_frame and not error_frames: + continue + if verbosity >= 3: + print(message) + bus.send(message) + iteration += 1 + except KeyboardInterrupt: + pass if __name__ == "__main__": diff --git a/doc/changelog.d/1815.added.md b/doc/changelog.d/1815.added.md new file mode 100644 index 000000000..65756fb41 --- /dev/null +++ b/doc/changelog.d/1815.added.md @@ -0,0 +1 @@ +Added support for replaying CAN log files multiple times or infinitely in the player script via the new --loop/-l argument. diff --git a/doc/changelog.d/1815.removed.md b/doc/changelog.d/1815.removed.md new file mode 100644 index 000000000..61b4e9b1d --- /dev/null +++ b/doc/changelog.d/1815.removed.md @@ -0,0 +1 @@ +Removed the unused --file_name/-f argument from the player CLI. diff --git a/test/test_player.py b/test/test_player.py index e5e77fe8a..c4c3c90ef 100755 --- a/test/test_player.py +++ b/test/test_player.py @@ -11,6 +11,8 @@ from unittest import mock from unittest.mock import Mock +from parameterized import parameterized + import can import can.player @@ -38,7 +40,7 @@ def assertSuccessfulCleanup(self): self.mock_virtual_bus.__exit__.assert_called_once() def test_play_virtual(self): - sys.argv = self.baseargs + [self.logfile] + sys.argv = [*self.baseargs, self.logfile] can.player.main() msg1 = can.Message( timestamp=2.501, @@ -65,8 +67,8 @@ def test_play_virtual(self): self.assertSuccessfulCleanup() def test_play_virtual_verbose(self): - sys.argv = self.baseargs + ["-v", self.logfile] - with unittest.mock.patch("sys.stdout", new_callable=io.StringIO) as mock_stdout: + sys.argv = [*self.baseargs, "-v", self.logfile] + with mock.patch("sys.stdout", new_callable=io.StringIO) as mock_stdout: can.player.main() self.assertIn("09 08 07 06 05 04 03 02", mock_stdout.getvalue()) self.assertIn("05 0c 00 00 00 00 00 00", mock_stdout.getvalue()) @@ -76,7 +78,7 @@ def test_play_virtual_verbose(self): def test_play_virtual_exit(self): self.MockSleep.side_effect = [None, KeyboardInterrupt] - sys.argv = self.baseargs + [self.logfile] + sys.argv = [*self.baseargs, self.logfile] can.player.main() assert self.mock_virtual_bus.send.call_count <= 2 self.assertSuccessfulCleanup() @@ -85,7 +87,7 @@ def test_play_skip_error_frame(self): logfile = os.path.join( os.path.dirname(__file__), "data", "logfile_errorframes.asc" ) - sys.argv = self.baseargs + ["-v", logfile] + sys.argv = [*self.baseargs, "-v", logfile] can.player.main() self.assertEqual(self.mock_virtual_bus.send.call_count, 9) self.assertSuccessfulCleanup() @@ -94,11 +96,52 @@ def test_play_error_frame(self): logfile = os.path.join( os.path.dirname(__file__), "data", "logfile_errorframes.asc" ) - sys.argv = self.baseargs + ["-v", "--error-frames", logfile] + sys.argv = [*self.baseargs, "-v", "--error-frames", logfile] can.player.main() self.assertEqual(self.mock_virtual_bus.send.call_count, 12) self.assertSuccessfulCleanup() + @parameterized.expand([0, 1, 2, 3]) + def test_play_loop(self, loop_val): + sys.argv = [*self.baseargs, "--loop", str(loop_val), self.logfile] + can.player.main() + msg1 = can.Message( + timestamp=2.501, + arbitration_id=0xC8, + is_extended_id=False, + is_fd=False, + is_rx=False, + channel=1, + dlc=8, + data=[0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2], + ) + msg2 = can.Message( + timestamp=17.876708, + arbitration_id=0x6F9, + is_extended_id=False, + is_fd=False, + is_rx=True, + channel=0, + dlc=8, + data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], + ) + for i in range(loop_val): + self.assertTrue( + msg1.equals(self.mock_virtual_bus.send.mock_calls[2 * i + 0].args[0]) + ) + self.assertTrue( + msg2.equals(self.mock_virtual_bus.send.mock_calls[2 * i + 1].args[0]) + ) + self.assertEqual(self.mock_virtual_bus.send.call_count, 2 * loop_val) + self.assertSuccessfulCleanup() + + def test_play_loop_infinite(self): + self.mock_virtual_bus.send.side_effect = [None] * 99 + [KeyboardInterrupt] + sys.argv = [*self.baseargs, "-l", "i", self.logfile] + can.player.main() + self.assertEqual(self.mock_virtual_bus.send.call_count, 100) + self.assertSuccessfulCleanup() + class TestPlayerCompressedFile(TestPlayerScriptModule): """ From ec78efc79c6c456822f3eefaab650ac35286c564 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:06:05 +0000 Subject: [PATCH 1225/1235] Bump the github-actions group with 2 updates Bumps the github-actions group with 2 updates: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish). Updates `astral-sh/setup-uv` from 6.6.1 to 6.8.0 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/557e51de59eb14aaaba2ed9621916900a91d50c6...d0cc045d04ccac9d8b7881df0226f9e82c39688e) Updates `pypa/gh-action-pypi-publish` from 1.12.4 to 1.13.0 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/76f52bc884231f62b9a034ebfe128415bbaabdfc...ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 6.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12022ba35..e85906a23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # 6.6.1 + uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 - name: Install tox run: uv tool install tox --with tox-uv - name: Setup SocketCAN @@ -84,7 +84,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # 6.6.1 + uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 - name: Install tox run: uv tool install tox --with tox-uv - name: Run linters @@ -102,7 +102,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # 6.6.1 + uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 - name: Install tox run: uv tool install tox --with tox-uv - name: Build documentation @@ -118,7 +118,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # 6.6.1 + uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 - name: Build wheel and sdist run: uv build - name: Check build artifacts @@ -151,4 +151,4 @@ jobs: subject-path: 'dist/*' - name: Publish release distributions to PyPI - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # 1.12.4 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # 1.13.0 From efd4de5619331d3ae8da10688979624cbb6d0342 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:03:50 +0000 Subject: [PATCH 1226/1235] Update pytest-cov requirement in the dev-deps group Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version. Updates `pytest-cov` to 7.0.0 - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.2.0...v7.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 7.0.0 dependency-type: direct:production dependency-group: dev-deps ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 551a7f3bd..3c263579f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ test = [ "pytest-timeout==2.4.*", "pytest-modern==0.7.*;platform_system!='Windows'", "coveralls==4.0.*", - "pytest-cov==6.2.*", + "pytest-cov==7.0.*", "coverage==7.10.*", "hypothesis==6.*", "parameterized==0.9.*", From b4d5094fa70277228fa2dc9fa1f30228fbdd79b7 Mon Sep 17 00:00:00 2001 From: Varun Penumudi <73636316+varunpenumudi@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:00:26 +0530 Subject: [PATCH 1227/1235] SeedBus: Added can_filters parameter in SeedBus.__init__ method (#1999) * Added hardware filter support for SeeedBus during initialization * Implement software fallback for can_filters in SeedBus - Updated the __init__ function in SeedBus - Implemented software fallback, if the user passes multiple filters in can_filters dict. * updated changelog for PR #1999 * Updated _recv_internal method of SeedBus - updated _recv_internal method of SeedBus to return also the Boolean value based on whether hw filter is enabled or not --- can/interfaces/seeedstudio/seeedstudio.py | 36 +++++++++++++++++------ doc/changelog.d/1995.added.md | 1 + doc/interfaces/seeedstudio.rst | 17 ++++++++++- 3 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 doc/changelog.d/1995.added.md diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index a0817e932..26339616c 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -63,6 +63,7 @@ def __init__( frame_type="STD", operation_mode="normal", bitrate=500000, + can_filters=None, **kwargs, ): """ @@ -85,6 +86,12 @@ def __init__( :param bitrate CAN bus bit rate, selected from available list. + :param can_filters: + A list of CAN filter dictionaries. If one filter is provided, + it will be used by the high-performance hardware filter. If + zero or more than one filter is provided, software-based + filtering will be used. Defaults to None (no filtering). + :raises can.CanInitializationError: If the given parameters are invalid. :raises can.CanInterfaceNotImplementedError: If the serial module is not installed. """ @@ -94,11 +101,21 @@ def __init__( "the serial module is not installed" ) + can_id = 0x00 + can_mask = 0x00 + self._is_filtered = False + + if can_filters and len(can_filters) == 1: + self._is_filtered = True + hw_filter = can_filters[0] + can_id = hw_filter["can_id"] + can_mask = hw_filter["can_mask"] + self.bit_rate = bitrate self.frame_type = frame_type self.op_mode = operation_mode - self.filter_id = bytearray([0x00, 0x00, 0x00, 0x00]) - self.mask_id = bytearray([0x00, 0x00, 0x00, 0x00]) + self.filter_id = struct.pack(" Date: Sun, 16 Nov 2025 12:27:37 +0100 Subject: [PATCH 1228/1235] fix pylint complaints (#2006) --- can/thread_safe_bus.py | 75 ++++++++++++++++++++++-------------------- pyproject.toml | 2 +- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 71d6a5536..518604364 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -1,12 +1,14 @@ from contextlib import nullcontext from threading import RLock -from typing import Any - -from can import typechecking -from can.bus import BusABC, BusState, CanProtocol -from can.message import Message +from typing import TYPE_CHECKING, Any, cast +from . import typechecking +from .bus import BusState, CanProtocol from .interface import Bus +from .message import Message + +if TYPE_CHECKING: + from .bus import BusABC try: # Only raise an exception on instantiation but allow module @@ -15,11 +17,13 @@ import_exc = None except ImportError as exc: - ObjectProxy = object + ObjectProxy = None # type: ignore[misc,assignment] import_exc = exc -class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method +class ThreadSafeBus( + ObjectProxy +): # pylint: disable=abstract-method # type: ignore[assignment] """ Contains a thread safe :class:`can.BusABC` implementation that wraps around an existing interface instance. All public methods @@ -36,8 +40,6 @@ class ThreadSafeBus(ObjectProxy): # pylint: disable=abstract-method instead of :meth:`~can.BusABC.recv` directly. """ - __wrapped__: BusABC - def __init__( self, channel: typechecking.Channel | None = None, @@ -59,58 +61,61 @@ def __init__( ) ) + # store wrapped bus as a proxy-local attribute. Name it with the + # `_self_` prefix so wrapt won't forward it onto the wrapped object. + self._self_wrapped = cast( + "BusABC", object.__getattribute__(self, "__wrapped__") + ) + # now, BusABC.send_periodic() does not need a lock anymore, but the # implementation still requires a context manager - self.__wrapped__._lock_send_periodic = nullcontext() # type: ignore[assignment] + self._self_wrapped._lock_send_periodic = nullcontext() # type: ignore[assignment] # init locks for sending and receiving separately - self._lock_send = RLock() - self._lock_recv = RLock() + self._self_lock_send = RLock() + self._self_lock_recv = RLock() def recv(self, timeout: float | None = None) -> Message | None: - with self._lock_recv: - return self.__wrapped__.recv(timeout=timeout) + with self._self_lock_recv: + return self._self_wrapped.recv(timeout=timeout) def send(self, msg: Message, timeout: float | None = None) -> None: - with self._lock_send: - return self.__wrapped__.send(msg=msg, timeout=timeout) - - # send_periodic does not need a lock, since the underlying - # `send` method is already synchronized + with self._self_lock_send: + return self._self_wrapped.send(msg=msg, timeout=timeout) @property def filters(self) -> typechecking.CanFilters | None: - with self._lock_recv: - return self.__wrapped__.filters + with self._self_lock_recv: + return self._self_wrapped.filters @filters.setter def filters(self, filters: typechecking.CanFilters | None) -> None: - with self._lock_recv: - self.__wrapped__.filters = filters + with self._self_lock_recv: + self._self_wrapped.filters = filters def set_filters(self, filters: typechecking.CanFilters | None = None) -> None: - with self._lock_recv: - return self.__wrapped__.set_filters(filters=filters) + with self._self_lock_recv: + return self._self_wrapped.set_filters(filters=filters) def flush_tx_buffer(self) -> None: - with self._lock_send: - return self.__wrapped__.flush_tx_buffer() + with self._self_lock_send: + return self._self_wrapped.flush_tx_buffer() def shutdown(self) -> None: - with self._lock_send, self._lock_recv: - return self.__wrapped__.shutdown() + with self._self_lock_send, self._self_lock_recv: + return self._self_wrapped.shutdown() @property def state(self) -> BusState: - with self._lock_send, self._lock_recv: - return self.__wrapped__.state + with self._self_lock_send, self._self_lock_recv: + return self._self_wrapped.state @state.setter def state(self, new_state: BusState) -> None: - with self._lock_send, self._lock_recv: - self.__wrapped__.state = new_state + with self._self_lock_send, self._self_lock_recv: + self._self_wrapped.state = new_state @property def protocol(self) -> CanProtocol: - with self._lock_send, self._lock_recv: - return self.__wrapped__.protocol + with self._self_lock_send, self._self_lock_recv: + return self._self_wrapped.protocol diff --git a/pyproject.toml b/pyproject.toml index 3c263579f..c59970544 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ docs = [ "furo", ] lint = [ - "pylint==3.3.*", + "pylint==4.0.*", "ruff==0.14.*", "black==25.9.*", "mypy==1.18.*", From 61a1e7610b3ef14b3c5fa150e9df09251fef571f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Nov 2025 11:30:07 +0000 Subject: [PATCH 1229/1235] Bump the dev-deps group across 1 directory with 3 updates Updates the requirements on [black](https://github.com/psf/black), [pytest](https://github.com/pytest-dev/pytest) and [coverage](https://github.com/coveragepy/coveragepy) to permit the latest version. Updates `black` to 25.11.0 - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/25.9.0...25.11.0) Updates `pytest` to 9.0.1 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.4.0.dev0...9.0.1) Updates `coverage` to 7.11.3 - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.10.0...7.11.3) --- updated-dependencies: - dependency-name: black dependency-version: 25.11.0 dependency-type: direct:development dependency-group: dev-deps - dependency-name: pytest dependency-version: 9.0.1 dependency-type: direct:development dependency-group: dev-deps - dependency-name: coverage dependency-version: 7.11.3 dependency-type: direct:development dependency-group: dev-deps ... Signed-off-by: dependabot[bot] --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c59970544..eb823bd64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,16 +89,16 @@ docs = [ lint = [ "pylint==4.0.*", "ruff==0.14.*", - "black==25.9.*", + "black==25.11.*", "mypy==1.18.*", ] test = [ - "pytest==8.4.*", + "pytest==9.0.*", "pytest-timeout==2.4.*", "pytest-modern==0.7.*;platform_system!='Windows'", "coveralls==4.0.*", "pytest-cov==7.0.*", - "coverage==7.10.*", + "coverage==7.11.*", "hypothesis==6.*", "parameterized==0.9.*", ] From 3f54ccaf0cb900af9bcf616069e4ce8d8a5eb4d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:19:36 +0000 Subject: [PATCH 1230/1235] Bump the github-actions group with 3 updates Bumps the github-actions group with 3 updates: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `astral-sh/setup-uv` from 7.0.0 to 7.1.2 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/eb1897b8dc4b5d5bfe39a428a8f2304605e0983c...85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41) Updates `actions/upload-artifact` from 4.6.2 to 5.0.0 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...330a01c490aca151604b8cf639adc76d48f6c5d4) Updates `actions/download-artifact` from 5.0.0 to 6.0.0 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/634f93cb2916e3fdff6788551b99b062d0335ce0...018cc2cf5baa6db3ef3c5f8a56943fffe632ef53) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 7.1.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/download-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e85906a23..ffa24aca7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # 7.1.2 - name: Install tox run: uv tool install tox --with tox-uv - name: Setup SocketCAN @@ -84,7 +84,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # 7.1.2 - name: Install tox run: uv tool install tox --with tox-uv - name: Run linters @@ -102,7 +102,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # 7.1.2 - name: Install tox run: uv tool install tox --with tox-uv - name: Build documentation @@ -118,13 +118,13 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # 7.0.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # 7.1.2 - name: Build wheel and sdist run: uv build - name: Check build artifacts run: uvx twine check --strict dist/* - name: Save artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # 5.0.0 with: name: release path: ./dist @@ -140,7 +140,7 @@ jobs: # upload to PyPI only on release if: github.event.release && github.event.action == 'published' steps: - - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # 5.0.0 + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # 6.0.0 with: path: dist merge-multiple: true From 58860affe829f4b9979b33f9ff5bd1c702ba69f4 Mon Sep 17 00:00:00 2001 From: Ben Gardiner Date: Sat, 29 Nov 2025 23:21:41 -0500 Subject: [PATCH 1231/1235] add RP1210 python-can driver --- doc/plugin-interface.rst | 3 +++ pyproject.toml | 1 + 2 files changed, 4 insertions(+) diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index 2f295b678..a18e27daf 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -81,6 +81,8 @@ The table below lists interface drivers that can be added by installing addition +----------------------------+----------------------------------------------------------+ | `python-can-coe`_ | A CAN-over-Ethernet interface for Technische Alternative | +----------------------------+----------------------------------------------------------+ +| `RP1210`_ | CAN channels in RP1210 Vehicle Diagnostic Adapters | ++----------------------------+----------------------------------------------------------+ .. _python-can-canine: https://github.com/tinymovr/python-can-canine .. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector @@ -90,4 +92,5 @@ The table below lists interface drivers that can be added by installing addition .. _python-can-cando: https://github.com/belliriccardo/python-can-cando .. _python-can-candle: https://github.com/BIRLab/python-can-candle .. _python-can-coe: https://c0d3.sh/smarthome/python-can-coe +.. _RP1210: https://github.com/dfieschko/RP1210 diff --git a/pyproject.toml b/pyproject.toml index eb823bd64..90412b95f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ sontheim = ["python-can-sontheim>=0.1.2"] canine = ["python-can-canine>=0.2.2"] zlgcan = ["zlgcan"] candle = ["python-can-candle>=1.2.2"] +rp1210 = ["rp1210>=1.0.1"] viewer = [ "windows-curses; platform_system == 'Windows' and platform_python_implementation=='CPython'" ] From 2630fb3eba9e756351406acfa81e1e7a4687d3d3 Mon Sep 17 00:00:00 2001 From: Ged Lex <79421501+Gedlex@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:30:20 +0100 Subject: [PATCH 1232/1235] ASCReader: Improve datetime parsing and support double-defined AM/PM cases (#2009) * ASCReader: Improve datetime parsing and support double-defined AM/PM cases * Fixed code formatting and add a news fragment for PR 2009 * Updated formatting of logformats_test.py * Updated test for cross-platform compatibility and (hopefully) fixed formatting for good now * Corrected micro- to milliseconds --- can/io/asc.py | 49 +++++++++++++++++++-------------- doc/changelog.d/2009.changed.md | 1 + test/logformats_test.py | 34 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 doc/changelog.d/2009.changed.md diff --git a/can/io/asc.py b/can/io/asc.py index 2c80458c4..fcf8fc5e4 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -116,41 +116,50 @@ def _extract_header(self) -> None: @staticmethod def _datetime_to_timestamp(datetime_string: str) -> float: - # ugly locale independent solution month_map = { - "Jan": 1, - "Feb": 2, - "Mar": 3, - "Apr": 4, - "May": 5, - "Jun": 6, - "Jul": 7, - "Aug": 8, - "Sep": 9, - "Oct": 10, - "Nov": 11, - "Dec": 12, - "Mär": 3, - "Mai": 5, - "Okt": 10, - "Dez": 12, + "jan": 1, + "feb": 2, + "mar": 3, + "apr": 4, + "may": 5, + "jun": 6, + "jul": 7, + "aug": 8, + "sep": 9, + "oct": 10, + "nov": 11, + "dec": 12, + "mär": 3, + "mai": 5, + "okt": 10, + "dez": 12, } - for name, number in month_map.items(): - datetime_string = datetime_string.replace(name, str(number).zfill(2)) datetime_formats = ( "%m %d %I:%M:%S.%f %p %Y", "%m %d %I:%M:%S %p %Y", "%m %d %H:%M:%S.%f %Y", "%m %d %H:%M:%S %Y", + "%m %d %H:%M:%S.%f %p %Y", + "%m %d %H:%M:%S %p %Y", ) + + datetime_string_parts = datetime_string.split(" ", 1) + month = datetime_string_parts[0].strip().lower() + + try: + datetime_string_parts[0] = f"{month_map[month]:02d}" + except KeyError: + raise ValueError(f"Unsupported month abbreviation: {month}") from None + datetime_string = " ".join(datetime_string_parts) + for format_str in datetime_formats: try: return datetime.strptime(datetime_string, format_str).timestamp() except ValueError: continue - raise ValueError(f"Incompatible datetime string {datetime_string}") + raise ValueError(f"Unsupported datetime format: '{datetime_string}'") def _extract_can_id(self, str_can_id: str, msg_kwargs: dict[str, Any]) -> None: if str_can_id[-1:].lower() == "x": diff --git a/doc/changelog.d/2009.changed.md b/doc/changelog.d/2009.changed.md new file mode 100644 index 000000000..6e68198a1 --- /dev/null +++ b/doc/changelog.d/2009.changed.md @@ -0,0 +1 @@ +Improved datetime parsing and added support for “double-defined” datetime strings (such as, e.g., `"30 15:06:13.191 pm 2017"`) for ASCReader class. \ No newline at end of file diff --git a/test/logformats_test.py b/test/logformats_test.py index f4bd1191f..f8a8de91d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -680,6 +680,40 @@ def test_write(self): self.assertEqual(expected_file.read_text(), actual_file.read_text()) + @parameterized.expand( + [ + ( + "May 27 04:09:35.000 pm 2014", + datetime(2014, 5, 27, 16, 9, 35, 0).timestamp(), + ), + ( + "Mai 27 04:09:35.000 pm 2014", + datetime(2014, 5, 27, 16, 9, 35, 0).timestamp(), + ), + ( + "Apr 28 10:44:52.480 2022", + datetime(2022, 4, 28, 10, 44, 52, 480000).timestamp(), + ), + ( + "Sep 30 15:06:13.191 2017", + datetime(2017, 9, 30, 15, 6, 13, 191000).timestamp(), + ), + ( + "Sep 30 15:06:13.191 pm 2017", + datetime(2017, 9, 30, 15, 6, 13, 191000).timestamp(), + ), + ( + "Sep 30 15:06:13.191 am 2017", + datetime(2017, 9, 30, 15, 6, 13, 191000).timestamp(), + ), + ] + ) + def test_datetime_to_timestamp( + self, datetime_string: str, expected_timestamp: float + ): + timestamp = can.ASCReader._datetime_to_timestamp(datetime_string) + self.assertAlmostEqual(timestamp, expected_timestamp) + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From d48dcb8c7ee80843b074341af8f3f17f341e0d9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:57:16 +0000 Subject: [PATCH 1233/1235] Bump the dev-deps group with 2 updates Updates the requirements on [mypy](https://github.com/python/mypy) and [coverage](https://github.com/coveragepy/coveragepy) to permit the latest version. Updates `mypy` to 1.19.0 - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.18.1...v1.19.0) Updates `coverage` to 7.12.0 - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.11.0...7.12.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.19.0 dependency-type: direct:development dependency-group: dev-deps - dependency-name: coverage dependency-version: 7.12.0 dependency-type: direct:development dependency-group: dev-deps ... Signed-off-by: dependabot[bot] --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 90412b95f..740d02a96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ lint = [ "pylint==4.0.*", "ruff==0.14.*", "black==25.11.*", - "mypy==1.18.*", + "mypy==1.19.*", ] test = [ "pytest==9.0.*", @@ -99,7 +99,7 @@ test = [ "pytest-modern==0.7.*;platform_system!='Windows'", "coveralls==4.0.*", "pytest-cov==7.0.*", - "coverage==7.11.*", + "coverage==7.12.*", "hypothesis==6.*", "parameterized==0.9.*", ] From c8bb0f8f7cbf65317fcb4b4623264d6bba1e2a5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:08:00 +0000 Subject: [PATCH 1234/1235] Bump the github-actions group with 3 updates Bumps the github-actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [coverallsapp/github-action](https://github.com/coverallsapp/github-action). Updates `actions/checkout` from 5.0.0 to 6.0.0 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...1af3b93b6815bc44a9784bd300feb67ff0d1eeb3) Updates `astral-sh/setup-uv` from 7.1.2 to 7.1.4 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41...1e862dfacbd1d6d858c55d9b792c756523627244) Updates `coverallsapp/github-action` from 2.3.6 to 2.3.7 - [Release notes](https://github.com/coverallsapp/github-action/releases) - [Commits](https://github.com/coverallsapp/github-action/compare/648a8eb78e6d50909eff900e4ec85cab4524a45b...5cbfd81b66ca5d10c19b062c04de0199c215fb6e) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: astral-sh/setup-uv dependency-version: 7.1.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: coverallsapp/github-action dependency-version: 2.3.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffa24aca7..e340b2508 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,12 +33,12 @@ jobs: ] fail-fast: false steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 with: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # 7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # 7.1.4 - name: Install tox run: uv tool install tox --with tox-uv - name: Setup SocketCAN @@ -55,7 +55,7 @@ jobs: # See: https://github.com/pypy/pypy/issues/3808 TEST_SOCKETCAN: "${{ matrix.os == 'ubuntu-latest' && ! startsWith(matrix.env, 'pypy' ) }}" - name: Coveralls Parallel - uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # 2.3.6 + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # 2.3.7 with: github-token: ${{ secrets.github_token }} flag-name: Unittests-${{ matrix.os }}-${{ matrix.env }} @@ -66,12 +66,12 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 with: fetch-depth: 0 persist-credentials: false - name: Coveralls Finished - uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # 2.3.6 + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # 2.3.7 with: github-token: ${{ secrets.github_token }} parallel-finished: true @@ -79,12 +79,12 @@ jobs: static-code-analysis: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 with: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # 7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # 7.1.4 - name: Install tox run: uv tool install tox --with tox-uv - name: Run linters @@ -97,12 +97,12 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 with: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # 7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # 7.1.4 - name: Install tox run: uv tool install tox --with tox-uv - name: Build documentation @@ -113,12 +113,12 @@ jobs: name: Packaging runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 with: fetch-depth: 0 persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # 7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # 7.1.4 - name: Build wheel and sdist run: uv build - name: Check build artifacts From b57bc514a64e61ff1579a7d456310ef2bc30c80d Mon Sep 17 00:00:00 2001 From: Gao Yichuan Date: Sat, 6 Dec 2025 19:22:06 +0800 Subject: [PATCH 1235/1235] Add python-can-damiao plugin support (#2014) Add python-can-damiao as an optional dependency plugin, providing support for Damiao USB-CAN adapters. The plugin enables CAN communication through Damiao USB-CAN adapters with a simple interface following the python-can plugin API. Tested with: - Damiao USB-CAN adapter - DM4310 motor Repository: https://github.com/gaoyichuan/python-can-damiao --- doc/plugin-interface.rst | 3 +++ pyproject.toml | 1 + 2 files changed, 4 insertions(+) diff --git a/doc/plugin-interface.rst b/doc/plugin-interface.rst index a18e27daf..612148033 100644 --- a/doc/plugin-interface.rst +++ b/doc/plugin-interface.rst @@ -83,6 +83,8 @@ The table below lists interface drivers that can be added by installing addition +----------------------------+----------------------------------------------------------+ | `RP1210`_ | CAN channels in RP1210 Vehicle Diagnostic Adapters | +----------------------------+----------------------------------------------------------+ +| `python-can-damiao`_ | Interface for Damiao USB-CAN adapters | ++----------------------------+----------------------------------------------------------+ .. _python-can-canine: https://github.com/tinymovr/python-can-canine .. _python-can-cvector: https://github.com/zariiii9003/python-can-cvector @@ -93,4 +95,5 @@ The table below lists interface drivers that can be added by installing addition .. _python-can-candle: https://github.com/BIRLab/python-can-candle .. _python-can-coe: https://c0d3.sh/smarthome/python-can-coe .. _RP1210: https://github.com/dfieschko/RP1210 +.. _python-can-damiao: https://github.com/gaoyichuan/python-can-damiao diff --git a/pyproject.toml b/pyproject.toml index 740d02a96..94bf60823 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ canine = ["python-can-canine>=0.2.2"] zlgcan = ["zlgcan"] candle = ["python-can-candle>=1.2.2"] rp1210 = ["rp1210>=1.0.1"] +damiao = ["python-can-damiao"] viewer = [ "windows-curses; platform_system == 'Windows' and platform_python_implementation=='CPython'" ]