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 01/48] 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 37dc484bf425a268271276eca1bbb809412f1eae Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 23 Jun 2019 12:36:12 +0200 Subject: [PATCH 02/48] 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 1753dc81f8344892d93ac28b9a5807f23b485093 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 24 Jun 2019 10:25:56 +0200 Subject: [PATCH 03/48] 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 8fef6997b55ea9802c78cf8fbf86fc0f9d13568a Mon Sep 17 00:00:00 2001 From: Karl Date: Tue, 16 Jul 2019 22:46:26 -0700 Subject: [PATCH 04/48] 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 05/48] 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 06/48] 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 07/48] 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 08/48] 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 09/48] 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 10/48] 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 11/48] 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 12/48] 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 13/48] 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 14/48] 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 15/48] 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 16/48] 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 17/48] 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 18/48] 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 19/48] 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 20/48] 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 21/48] 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 22/48] 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 23/48] 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 24/48] 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 25/48] 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 26/48] 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 27/48] 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 28/48] 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 29/48] 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 30/48] 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 31/48] 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 32/48] 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 33/48] 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 34/48] 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 35/48] 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 36/48] 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 37/48] 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 38/48] 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 39/48] 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 40/48] 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 41/48] 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 42/48] 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 43/48] 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 44/48] 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 45/48] 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 46/48] 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 47/48] 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 1ac5510f7e18b7c5c9d87576f4aeda906ef7036a Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Sat, 17 Aug 2019 12:40:52 +0200 Subject: [PATCH 48/48] 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)