From 2a30f2519873b80909b1bb6c415e69d184d42501 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 18 May 2019 08:46:52 +1000 Subject: [PATCH 001/120] update wheel package before deploy --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index b45e17d95..bcd18033a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,6 +88,8 @@ jobs: - stage: deploy name: "PyPi Deployment" python: "3.7" + before_install: + - travis_retry pip install -U wheel setuptools deploy: provider: pypi user: hardbyte From ad3f830d6057d037a80f24e5ba25a9c3cf794529 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 18 May 2019 09:14:08 +1000 Subject: [PATCH 002/120] Minor version bump and doc update --- can/__init__.py | 2 +- doc/development.rst | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index a612363ae..4057322dd 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.2.0" +__version__ = "3.2.1-alpha.2" log = logging.getLogger('can') diff --git a/doc/development.rst b/doc/development.rst index 602e4e347..fdb5bcf25 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -73,8 +73,10 @@ The modules in ``python-can`` are: +---------------------------------+------------------------------------------------------+ -Creating a new Release ----------------------- +Process for creating a new Release +---------------------------------- + +Note many of these steps are carried out by the CI system on creating a tag in git. - Release from the ``master`` branch. - Update the library version in ``__init__.py`` using `semantic versioning `__. @@ -84,8 +86,9 @@ Creating a new Release - For larger changes update ``doc/history.rst``. - Sanity check that documentation has stayed inline with code. - Create a temporary virtual environment. Run ``python setup.py install`` and ``python setup.py test``. +- Ensure the ``setuptools`` and ``wheel`` tools are up to date: ``pip install -U setuptools wheel``. - Create and upload the distribution: ``python setup.py sdist bdist_wheel``. -- Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl``. +- [Optionally] Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl``. - Upload with twine ``twine upload dist/python-can-X.Y.Z*``. - In a new virtual env check that the package can be installed with pip: ``pip install python-can==X.Y.Z``. - Create a new tag in the repository. From a91ca251a63c7eb70dbc2c2109bed9faad16e4ab Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 25 Jun 2019 09:45:42 +1000 Subject: [PATCH 003/120] Minor version bump to v3.2.1 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 4057322dd..d2ca9208c 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.2.1-alpha.2" +__version__ = "3.2.1" log = logging.getLogger('can') From 667278ece35edf6460996e6f9186db59d8692ccb Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 25 Jun 2019 09:48:03 +1000 Subject: [PATCH 004/120] Update changelog for v3.2.1 --- CHANGELOG.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2f50dca8d..a69ddf91c 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,12 @@ -Version 3.2.0 +Version 3.2.1 ==== +* CAN FD 64 frame support to blf reader +* Minor fix to use latest tools when building wheels on travis. +* Updates links in documentation. + +Version 3.2.0 +==== Major features -------------- From a0f0ea750fe36df01261486e9b11db5e9a7a3f9a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 27 Jun 2019 11:11:36 +1000 Subject: [PATCH 005/120] Update version to v3.3.0 Add notes to changelog. --- CHANGELOG.txt | 7 ++++--- can/__init__.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a69ddf91c..8d15e2170 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,9 +1,10 @@ -Version 3.2.1 +Version 3.3.0 ==== -* CAN FD 64 frame support to blf reader +* Adding CAN FD 64 frame support to blf reader +* Updates to installation instructions +* Clean up bits generator in PCAN interface #588 * Minor fix to use latest tools when building wheels on travis. -* Updates links in documentation. Version 3.2.0 ==== diff --git a/can/__init__.py b/can/__init__.py index d2ca9208c..10245b220 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.2.1" +__version__ = "3.3.0" log = logging.getLogger('can') From 591967c45233abf906950bbd59af914550007815 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 23 Jul 2019 21:50:56 +1000 Subject: [PATCH 006/120] Backport pytest-runner fix from #633 --- setup.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c600b7215..27121b61a 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ from os.path import isfile, join import re import logging +import sys from setuptools import setup, find_packages logging.basicConfig(level=logging.WARNING) @@ -41,6 +42,14 @@ extras_require['test'] = tests_require +# Check for 'pytest-runner' only if setup.py was invoked with 'test'. +# This optimizes setup.py for cases when pytest-runner is not needed, +# using the approach that is suggested upstream. +# +# See https://pypi.org/project/pytest-runner/#conditional-requirement +needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) +pytest_runner = ["pytest-runner"] if needs_pytest else [] + setup( # Description @@ -104,7 +113,7 @@ 'typing;python_version<"3.5"', 'windows-curses;platform_system=="Windows"', ], - setup_requires=["pytest-runner"], + setup_requires=pytest_runner, extras_require=extras_require, tests_require=tests_require ) From 1f0fb8b680a24e727a6995949c4f41d6251abd56 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 23 Jul 2019 21:52:59 +1000 Subject: [PATCH 007/120] Bump version to 3.3.1 --- CHANGELOG.txt | 5 +++++ can/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8d15e2170..5c492d19f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,8 @@ +Version 3.3.1 +==== + +Minor fix to setup.py to only require pytest-runner when necessary. + Version 3.3.0 ==== diff --git a/can/__init__.py b/can/__init__.py index 10245b220..e00e7dd53 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.0" +__version__ = "3.3.1" log = logging.getLogger('can') From 9bf4c0d8108cdbb5e9b1e3fd91126363f237452b Mon Sep 17 00:00:00 2001 From: Colin Rafferty Date: Mon, 29 Jul 2019 10:19:19 -0400 Subject: [PATCH 008/120] 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 864308bab..f062d62aa 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -385,9 +385,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 4405bc5d3c815f6f5535ba9360acb90936a04a76 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 15 Aug 2019 21:00:39 +1000 Subject: [PATCH 009/120] Bump version to 3.3.2 --- CHANGELOG.txt | 5 +++++ can/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5c492d19f..933318a66 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,8 @@ +Version 3.3.2 +==== + +Minor bug fix release addressing issue in PCAN RTR. + Version 3.3.1 ==== diff --git a/can/__init__.py b/can/__init__.py index e00e7dd53..44aa90e4a 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.1" +__version__ = "3.3.2" log = logging.getLogger('can') From 1d4c6a78265412a303df807babefcd5e29577a24 Mon Sep 17 00:00:00 2001 From: Colin Rafferty Date: Mon, 29 Jul 2019 10:19:19 -0400 Subject: [PATCH 010/120] 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 864308bab..f062d62aa 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -385,9 +385,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 29a235bc6e51002b243126b778bd9bc318652be9 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 15 Aug 2019 21:00:39 +1000 Subject: [PATCH 011/120] Bump version to 3.3.2 --- CHANGELOG.txt | 5 +++++ can/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5c492d19f..933318a66 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,8 @@ +Version 3.3.2 +==== + +Minor bug fix release addressing issue in PCAN RTR. + Version 3.3.1 ==== diff --git a/can/__init__.py b/can/__init__.py index e00e7dd53..44aa90e4a 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.1" +__version__ = "3.3.2" log = logging.getLogger('can') From fbe11048f5c0a5801ed330a880d7709bb75d814f Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 24 Oct 2019 21:23:00 +1100 Subject: [PATCH 012/120] Backport #713 - RTR bug fix in canutils.py --- can/io/canutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/canutils.py b/can/io/canutils.py index 69c0227a4..b5cec0cb5 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -62,7 +62,7 @@ def __iter__(self): else: isExtended = False canId = int(canId, 16) - + dataBin = None if data and data[0].lower() == 'r': isRemoteFrame = True if len(data) > 1: From 7505d21aa032735a25e2b12e9ac95500c21e6968 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 24 Oct 2019 21:42:07 +1100 Subject: [PATCH 013/120] Bump version to 3.3.3 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 44aa90e4a..481dc29e3 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.2" +__version__ = "3.3.3" log = logging.getLogger('can') From f69b162766e014ee9a56671092075a0d73e1542d Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Sun, 29 Dec 2019 09:19:17 +1300 Subject: [PATCH 014/120] Backport fix to ASCReader #701 Skip J1939TP messages --- can/io/asc.py | 8 ++++++-- test/data/logfile.asc | 46 ++++++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 3ed50f04a..4f1115e98 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -28,7 +28,8 @@ class ASCReader(BaseIOHandler): """ - Iterator of CAN messages from a ASC logging file. + Iterator of CAN messages from a ASC logging file. Meta data (comments, + bus statistics, J1939 Transport Protocol messages) is ignored. TODO: turn relative timestamps back to absolute form """ @@ -77,7 +78,10 @@ def __iter__(self): channel=channel) yield msg - elif not isinstance(channel, int) or dummy.strip()[0:10].lower() == 'statistic:': + elif (not isinstance(channel, int) + or dummy.strip()[0:10].lower() == 'statistic:' + or dummy.split(None, 1)[0] == "J1939TP" + ): pass elif dummy[-1:].lower() == 'r': diff --git a/test/data/logfile.asc b/test/data/logfile.asc index 4b7c64363..b855811a2 100644 --- a/test/data/logfile.asc +++ b/test/data/logfile.asc @@ -1,18 +1,28 @@ -date Sam Sep 30 15:06:13.191 2017 -base hex timestamps absolute -internal events logged -// version 9.0.0 -Begin Triggerblock Sam Sep 30 15:06:13.191 2017 - 0.000000 Start of measurement - 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0 - 0.015991 CAN 2 Status:chip status error active - 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% - 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% - 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% - 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0 - 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 - 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 - 18.015997 1 Statistic: D 2 R 0 XD 0 XR 0 E 0 O 0 B 0.04% - 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% - 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% -End TriggerBlock +date Sam Sep 30 15:06:13.191 2017 +base hex timestamps absolute +internal events logged +// version 9.0.0 +Begin Triggerblock Sam Sep 30 15:06:13.191 2017 + 0.000000 Start of measurement + 0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0 + 0.015991 CAN 2 Status:chip status error active + 1.015991 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 1.015991 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 2.015992 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 3.098426 1 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273910 BitCount = 141 ID = 418119424x + 3.148421 1 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 271910 BitCount = 140 ID = 418119424x + 3.197693 1 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 3.248765 1 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283910 BitCount = 146 ID = 418119424x + 3.297743 1 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF + 17.876707 CAN 1 Status:chip status error passive - TxErr: 131 RxErr: 0 + 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 + 17.876976 1 6F8 Rx d 8 FF 00 0C FE 00 00 00 00 Length = 239910 BitCount = 124 ID = 1784 + 18.015997 1 Statistic: D 2 R 0 XD 0 XR 0 E 0 O 0 B 0.04% + 20.105214 2 18EBFF00x Rx d 8 01 A0 0F A6 60 3B D1 40 Length = 273925 BitCount = 141 ID = 418119424x + 20.155119 2 18EBFF00x Rx d 8 02 1F DE 80 25 DF C0 2B Length = 272152 BitCount = 140 ID = 418119424x + 20.204671 2 18EBFF00x Rx d 8 03 E1 00 4B FF FF 3C 0F Length = 283910 BitCount = 146 ID = 418119424x + 20.248887 2 18EBFF00x Rx d 8 04 00 4B FF FF FF FF FF Length = 283925 BitCount = 146 ID = 418119424x + 20.305233 2 J1939TP FEE3p 6 0 0 - Rx d 23 A0 0F A6 60 3B D1 40 1F DE 80 25 DF C0 2B E1 00 4B FF FF 3C 0F 00 4B FF FF FF FF FF FF FF FF FF FF FF FF + 113.016026 1 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% + 113.016026 2 Statistic: D 0 R 0 XD 0 XR 0 E 0 O 0 B 0.00% +End TriggerBlock From 250d290792d865dd06e422ed1a97b6b391062a20 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Sun, 29 Dec 2019 09:24:50 +1300 Subject: [PATCH 015/120] Backport fix to can.player #690 Expose options to send or skip error frame messages --- can/player.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/can/player.py b/can/player.py index c712f1714..2b1864a36 100644 --- a/can/player.py +++ b/can/player.py @@ -47,6 +47,12 @@ def main(): help='''Ignore timestamps (send all frames immediately with minimum gap between frames)''', action='store_false') + parser.add_argument( + "--error-frames", + help="Also send error frames to the interface.", + action="store_true", + ) + parser.add_argument('-g', '--gap', type=float, help=''' minimum time between replayed frames''', default=0.0001) parser.add_argument('-s', '--skip', type=float, default=60*60*24, @@ -68,6 +74,8 @@ def main(): logging_level_name = ['critical', 'error', 'warning', 'info', 'debug', 'subdebug'][min(5, verbosity)] can.set_logging_level(logging_level_name) + error_frames = results.error_frames + config = {"single_handle": True} if results.interface: config["interface"] = results.interface @@ -84,6 +92,8 @@ def main(): try: for m in in_sync: + if m.is_error_frame and not error_frames: + continue if verbosity >= 3: print(m) bus.send(m) From 0e3812d218f03947d5fc7db59c61981b53e5b6e1 Mon Sep 17 00:00:00 2001 From: Karl Date: Fri, 12 Jul 2019 05:00:11 +1200 Subject: [PATCH 016/120] Backport socketcan fix: Read the BCM status before creating a Task Currently bus.send_periodic blindly wraps the BCM socket API, and sends a BCM operation with the TX_SETUP opcode. However, the semantics that the kernel driver provides are an "upsert". As such, when one attempts to create two Tasks with the same CAN Arbitration ID, it ends up silently modifying the periodic messages, which is contrary to what our API implies. This fixes the problem by first sending a TX_READ opcode with the CAN Arbitration ID that we wish to create. The kernel driver will return -EINVAL if a periodic transmission with the given Arbitration ID does not exist. If this is the case, then we can continue creating the Task, otherwise we error and notify the user. #605 --- can/interfaces/socketcan/constants.py | 5 ++-- can/interfaces/socketcan/socketcan.py | 35 +++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/can/interfaces/socketcan/constants.py b/can/interfaces/socketcan/constants.py index b56eaae64..1fa6fdf72 100644 --- a/can/interfaces/socketcan/constants.py +++ b/can/interfaces/socketcan/constants.py @@ -9,8 +9,9 @@ CAN_EFF_FLAG = 0x80000000 # BCM opcodes -CAN_BCM_TX_SETUP = 1 -CAN_BCM_TX_DELETE = 2 +CAN_BCM_TX_SETUP = 1 +CAN_BCM_TX_DELETE = 2 +CAN_BCM_TX_READ = 3 # BCM flags SETTIMER = 0x0001 diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 633c87b22..b69b2636f 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -346,8 +346,39 @@ def _tx_setup(self, message): count = 0 ival1 = 0 ival2 = self.period - header = build_bcm_transmit_header(self.can_id_with_flags, count, ival1, - ival2, self.flags) + + # First do a TX_READ before creating a new task, and check if we get + # EINVAL. If so, then we are referring to a CAN message with the same + # ID + check_header = build_bcm_header( + opcode=CAN_BCM_TX_READ, + flags=0, + count=0, + ival1_seconds=0, + ival1_usec=0, + ival2_seconds=0, + ival2_usec=0, + can_id=self.can_id_with_flags, + nframes=0, + ) + try: + self.bcm_socket.send(check_header) + except OSError as e: + assert e.errno == errno.EINVAL + else: + raise ValueError( + "A periodic Task for Arbitration ID {} has already been created".format( + message.arbitration_id + ) + ) + + header = build_bcm_transmit_header( + self.can_id_with_flags, + count, + ival1, + ival2, + self.flags + ) frame = build_can_frame(message) log.debug("Sending BCM command") send_bcm(self.bcm_socket, header + frame) From cee9d642cc57b64ad62914391cab40cd14de87e6 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 13 Jul 2019 14:38:59 -0700 Subject: [PATCH 017/120] Raise Exception instead of using assert on EINVAL --- can/interfaces/socketcan/socketcan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index b69b2636f..b671f926a 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -364,7 +364,8 @@ def _tx_setup(self, message): try: self.bcm_socket.send(check_header) except OSError as e: - assert e.errno == errno.EINVAL + if e.errno != errno.EINVAL: + raise e else: raise ValueError( "A periodic Task for Arbitration ID {} has already been created".format( From a479d7f2fb609f21d2e15439ce595af38a1103ab Mon Sep 17 00:00:00 2001 From: nick black Date: Sun, 9 Jun 2019 03:32:42 +1200 Subject: [PATCH 018/120] Backport canalystii fix to init #617 Pass kwargs through to BusABC's initializer. Doesn't backport changing baud to bitrate to avoid changing the API --- can/interfaces/canalystii.py | 6 ++++-- doc/configuration.rst | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 35f240a66..6a2fd51a9 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -66,7 +66,9 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): - def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None): + def __init__( + self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None, **kwargs + ): """ :param channel: channel number @@ -76,7 +78,7 @@ def __init__(self, channel, device=0, baud=None, Timing0=None, Timing1=None, can :param Timing1: :param can_filters: filters for packet """ - super(CANalystIIBus, self).__init__(channel, can_filters) + super(CANalystIIBus, self).__init__(channel, can_filters, **kwargs) if isinstance(channel, (list, tuple)): self.channels = channel diff --git a/doc/configuration.rst b/doc/configuration.rst index dda2ace2a..142e816da 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -126,3 +126,7 @@ Lookup table of interface names: +---------------------+-------------------------------------+ | ``"virtual"`` | :doc:`interfaces/virtual` | +---------------------+-------------------------------------+ +| ``"canalystii"`` | :doc:`interfaces/canalystii` | ++---------------------+-------------------------------------+ +| ``"systec"`` | :doc:`interfaces/systec` | ++---------------------+-------------------------------------+ From 1587c3639bbb5c1227ebd7b35139934f24e5c13a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 23 Dec 2019 22:18:19 +1300 Subject: [PATCH 019/120] Backport updating test deps (#745) * pin versions of testing dependencies including coverage --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 27121b61a..29c7478f7 100644 --- a/setup.py +++ b/setup.py @@ -33,11 +33,14 @@ 'mock~=2.0', 'pytest~=4.3', 'pytest-timeout~=1.3', - 'pytest-cov~=2.6', + 'pytest-cov~=2.8', + # coveragepy==5.0 fails with `Safety level may not be changed inside a transaction` + # on python 3.6 on MACOS + 'coverage<5', 'codecov~=2.0', 'future', 'six', - 'hypothesis' + 'hypothesis~=4.56' ] + extras_require['serial'] extras_require['test'] = tests_require From f9198f3f30c2a9c57d44d6e1f4b61a50ac5b3042 Mon Sep 17 00:00:00 2001 From: chrisoro <4160557+chrisoro@users.noreply.github.com> Date: Tue, 24 Dec 2019 07:16:31 +0100 Subject: [PATCH 020/120] Exclude all test packages, not just toplevel (#740) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 29c7478f7..e5b05e8dc 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ # Code version=version, - packages=find_packages(exclude=["test", "doc", "scripts", "examples"]), + packages=find_packages(exclude=["test*", "doc", "scripts", "examples"]), scripts=list(filter(isfile, (join("scripts/", f) for f in listdir("scripts/")))), # Author From 6d44fd0ecac17cee7085ad7019ea7f123bf78bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Sch=C3=A4rlund?= Date: Sat, 22 Jun 2019 10:14:05 +0200 Subject: [PATCH 021/120] Avoid padding CAN_FD_MESSAGE_64 objects to 4 bytes --- can/io/blf.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index d162fdebc..a17a6b6a7 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -195,8 +195,13 @@ def __iter__(self): raise BLFParseError() obj_size = header[3] + obj_type = header[4] # Calculate position of next object - next_pos = pos + obj_size + (obj_size % 4) + if obj_size % 4 and obj_type != CAN_FD_MESSAGE_64: + next_pos = pos + obj_size + (obj_size % 4) + else: + # CAN_FD_MESSAGE_64 objects are not padded to 4 bytes. + next_pos = pos + obj_size if next_pos > len(data): # Object continues in next log container break @@ -222,7 +227,6 @@ def __iter__(self): factor = 1e-9 timestamp = timestamp * factor + self.start_timestamp - obj_type = header[4] # Both CAN message types have the same starting content if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): (channel, flags, dlc, can_id, From ba4c6761286150c5ea3de39a74c73e076b293ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Sch=C3=A4rlund?= Date: Sun, 23 Jun 2019 13:55:21 +0200 Subject: [PATCH 022/120] Refactor to save calculations --- can/io/blf.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index a17a6b6a7..91f2945c9 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -197,11 +197,9 @@ def __iter__(self): obj_size = header[3] obj_type = header[4] # Calculate position of next object - if obj_size % 4 and obj_type != CAN_FD_MESSAGE_64: - next_pos = pos + obj_size + (obj_size % 4) - else: - # CAN_FD_MESSAGE_64 objects are not padded to 4 bytes. - next_pos = pos + obj_size + next_pos = pos + obj_size + if obj_type != CAN_FD_MESSAGE_64: + next_pos += obj_size % 4 if next_pos > len(data): # Object continues in next log container break From 9fb3c23f3b167630ae3ae2ab2137c496c7ec2def Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 17 Feb 2020 22:12:50 +1300 Subject: [PATCH 023/120] Backport accepting bitrate instead of baud in CANalystIIBus Backported from #617 --- can/interfaces/canalystii.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 6a2fd51a9..859fdca9a 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,3 +1,4 @@ +import warnings from ctypes import * import logging import platform @@ -67,13 +68,13 @@ class VCI_CAN_OBJ(Structure): class CANalystIIBus(BusABC): def __init__( - self, channel, device=0, baud=None, Timing0=None, Timing1=None, can_filters=None, **kwargs + self, channel, device=0, bitrate=None, baud=None, Timing0=None, Timing1=None, can_filters=None, **kwargs ): """ :param channel: channel number :param device: device number - :param baud: baud rate + :param baud: baud rate. Renamed to bitrate in next release. :param Timing0: customize the timing register if baudrate is not specified :param Timing1: :param can_filters: filters for packet @@ -93,10 +94,15 @@ def __init__( self.channel_info = "CANalyst-II: device {}, channels {}".format(self.device, self.channels) if baud is not None: + warnings.warn('Argument baud will be deprecated in version 4, use bitrate instead', + PendingDeprecationWarning) + bitrate = baud + + if bitrate is not None: try: - Timing0, Timing1 = TIMING_DICT[baud] + Timing0, Timing1 = TIMING_DICT[bitrate] except KeyError: - raise ValueError("Baudrate is not supported") + raise ValueError("Bitrate is not supported") if Timing0 is None or Timing1 is None: raise ValueError("Timing registers are not set") From 6041bbcf37a588d0e7e6d96578ebee1d56db2f3d Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 19 Apr 2020 12:17:35 +1200 Subject: [PATCH 024/120] Add changelog for release 3.3.3 and append alpha 0 tag for testing. --- CHANGELOG.txt | 16 ++++++++++++++++ can/__init__.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 933318a66..c22c5bad6 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,19 @@ +Version 3.3.3 +==== + +Backported fixes from Python 3 only 4.x development. + +* Exclude test packages from distribution. #740 +* RTR crash fix in canutils log reader parsing RTR frames. #713 +* Skip J1939 messages in ASC Reader. #701 +* Exposes a configuration option to allow the CAN message player to send error frames + (and sets the default to not send error frames). #690 +* Fixes the semantics provided by periodic tasks in SocketCAN interface. #638 +* Avoid padding CAN_FD_MESSAGE_64 objects to 4 bytes. #628 +* Fixes the broken CANalyst-II interface. #617 +* Socketcan BCM status fix. #605 + + Version 3.3.2 ==== diff --git a/can/__init__.py b/can/__init__.py index 481dc29e3..c467396c8 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.3" +__version__ = "3.3.3-alpha.0" log = logging.getLogger('can') From e45c8cc93202ad19d4024daf066867b94adcbb83 Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 19 Apr 2020 19:30:15 +1200 Subject: [PATCH 025/120] Fix Vector CANlib treatment of empty app name In Python 2, the str type was used for text and bytes, whereas in Python 3, these are separate and incompatible types. This broke instantiation of a VectorBus when the app_name parameter in __init__ was set to None. This correctly sets it to a bytes object. Fixes #796 --- can/interfaces/vector/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 251b9fa56..65af0147b 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -87,7 +87,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, else: # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(',')] - self._app_name = app_name.encode() if app_name is not None else '' + self._app_name = app_name.encode() if app_name is not None else b'' self.channel_info = 'Application %s: %s' % ( app_name, ', '.join('CAN %d' % (ch + 1) for ch in self.channels)) From e36e37612131001dcd8398a55f55b4a5714646c5 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 20 Apr 2020 09:49:49 +1200 Subject: [PATCH 026/120] 3.3.3-a.1 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index c467396c8..8aa111be7 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.3-alpha.0" +__version__ = "3.3.3-alpha.1" log = logging.getLogger('can') From 2ae3de5efa7254bb4132ec3af332a63c81726de9 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 21 Apr 2020 08:52:15 +1200 Subject: [PATCH 027/120] Backport fix for handling empty csv file #771 Bump pre-release version to 3.3.3-a.2 --- can/__init__.py | 2 +- can/io/csv.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 8aa111be7..43996ff4f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.3-alpha.1" +__version__ = "3.3.3-alpha.2" log = logging.getLogger('can') diff --git a/can/io/csv.py b/can/io/csv.py index 92f841f8f..32e34d0fb 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -91,7 +91,11 @@ def __init__(self, file): def __iter__(self): # skip the header line - next(self.file) + try: + next(self.file) + except StopIteration: + # don't crash on a file with only a header + return for line in self.file: From 5180591a2721c8a26b8adc1fb1e43f1adcdcfd49 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 23 Mar 2020 16:11:25 -0400 Subject: [PATCH 028/120] Backport caching msg.data value in neovi interface (#798) --- can/interfaces/ics_neovi/neovi_bus.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 4baee6177..530d7c524 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -334,11 +334,12 @@ def send(self, msg, timeout=None): flag3 |= ics.SPY_STATUS3_CANFD_ESI message.ArbIDOrHeader = msg.arbitration_id - message.NumberBytesData = len(msg.data) - message.Data = tuple(msg.data[:8]) - if msg.is_fd and len(msg.data) > 8: + msg_data = msg.data + message.NumberBytesData = len(msg_data) + message.Data = tuple(msg_data[:8]) + if msg.is_fd and len(msg_data) > 8: message.ExtraDataPtrEnabled = 1 - message.ExtraDataPtr = tuple(msg.data) + message.ExtraDataPtr = tuple(msg_data) message.StatusBitField = flag0 message.StatusBitField2 = 0 message.StatusBitField3 = flag3 From 390ff1cd8cf2b766a38215711d5bba5f2ebdb07f Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Tue, 21 Apr 2020 08:58:51 +1200 Subject: [PATCH 029/120] Update changelog with more backported fixes for 3.3.3 release --- CHANGELOG.txt | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c22c5bad6..3399feb65 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,15 +3,18 @@ Version 3.3.3 Backported fixes from Python 3 only 4.x development. -* Exclude test packages from distribution. #740 -* RTR crash fix in canutils log reader parsing RTR frames. #713 -* Skip J1939 messages in ASC Reader. #701 -* Exposes a configuration option to allow the CAN message player to send error frames - (and sets the default to not send error frames). #690 -* Fixes the semantics provided by periodic tasks in SocketCAN interface. #638 -* Avoid padding CAN_FD_MESSAGE_64 objects to 4 bytes. #628 -* Fixes the broken CANalyst-II interface. #617 -* Socketcan BCM status fix. #605 +* #798 Backport caching msg.data value in neovi interface +* #796 Fix Vector CANlib treatment of empty app name +* #771 Handle empty CSV file +* #740 Exclude test packages from distribution. +* #713 RTR crash fix in canutils log reader parsing RTR frames. +* #701 Skip J1939 messages in ASC Reader. +* #690 Exposes a configuration option to allow the CAN message player to send error frames + (and sets the default to not send error frames). +* #638 Fixes the semantics provided by periodic tasks in SocketCAN interface. +* #628 Avoid padding CAN_FD_MESSAGE_64 objects to 4 bytes. +* #617 Fixes the broken CANalyst-II interface. +* #605 Socketcan BCM status fix. Version 3.3.2 From 16d89bc8fc210fd423d34c4dfbb9952b92418722 Mon Sep 17 00:00:00 2001 From: Syed Date: Fri, 31 Jan 2020 04:53:11 +1300 Subject: [PATCH 030/120] Backport #741 - Fixing ASCII reader unable to read FD frames --- can/io/asc.py | 35 +++++++++++++++++++++++++++++------ test/logformats_test.py | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 4f1115e98..60f845bd0 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -59,9 +59,13 @@ def __iter__(self): temp = line.strip() if not temp or not temp[0].isdigit(): continue - + is_fd = False try: timestamp, channel, dummy = temp.split(None, 2) # , frameType, dlc, frameData + if channel == "CANFD": + timestamp, _, channel, _, dummy = temp.split(None, 4) + is_fd = True + except ValueError: # we parsed an empty comment continue @@ -95,16 +99,32 @@ def __iter__(self): yield msg else: + brs = None + esi = None + data_length = 0 try: - # this only works if dlc > 0 and thus data is availabe - can_id_str, _, _, dlc, data = dummy.split(None, 4) + # this only works if dlc > 0 and thus data is available + if not is_fd: + can_id_str, _, _, dlc, data = dummy.split(None, 4) + else: + can_id_str, frame_name, brs, esi, dlc, data_length, data = dummy.split( + None, 6 + ) + if frame_name.isdigit(): + # Empty frame_name + can_id_str, brs, esi, dlc, data_length, data = dummy.split( + None, 5 + ) except ValueError: # but if not, we only want to get the stuff up to the dlc can_id_str, _, _, dlc = dummy.split(None, 3) # and we set data to an empty sequence manually data = '' - - dlc = int(dlc) + dlc = int(dlc, 16) + if is_fd: + # For fd frames, dlc and data length might not be equal and + # data_length is the actual size of the data + dlc = int(data_length) frame = bytearray() data = data.split() for byte in data[0:dlc]: @@ -119,7 +139,10 @@ def __iter__(self): is_remote_frame=False, dlc=dlc, data=frame, - channel=channel + is_fd=is_fd, + channel=channel, + bitrate_switch=is_fd and brs == "1", + error_state_indicator=is_fd and esi == "1", ) self.stop() diff --git a/test/logformats_test.py b/test/logformats_test.py index d9551e5d6..41bf2e9c4 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -315,7 +315,7 @@ class TestAscFileFormat(ReaderWriterTest): def _setup_instance(self): super(TestAscFileFormat, self)._setup_instance_helper( can.ASCWriter, can.ASCReader, - check_fd=False, + check_fd=True, check_comments=True, preserves_channel=False, adds_default_channel=0 ) From d2d701269dcbba6d945ad1e4b33ba1ab448fb161 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 7 May 2020 09:08:55 +1200 Subject: [PATCH 031/120] 3.3.3-a.3 --- CHANGELOG.txt | 9 +++++---- can/__init__.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 3399feb65..c2a6ea38a 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,11 +1,12 @@ Version 3.3.3 ==== -Backported fixes from Python 3 only 4.x development. +Backported fixes from 4.x development branch which targets Python 3. -* #798 Backport caching msg.data value in neovi interface -* #796 Fix Vector CANlib treatment of empty app name -* #771 Handle empty CSV file +* #798 Backport caching msg.data value in neovi interface. +* #796 Fix Vector CANlib treatment of empty app name. +* #771 Handle empty CSV file. +* #741 ASCII reader can now handle FD frames. * #740 Exclude test packages from distribution. * #713 RTR crash fix in canutils log reader parsing RTR frames. * #701 Skip J1939 messages in ASC Reader. diff --git a/can/__init__.py b/can/__init__.py index 43996ff4f..0e30f1c18 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.3-alpha.2" +__version__ = "3.3.3-alpha.3" log = logging.getLogger('can') From 8b76232e283c9fab05515d96cb6bc5b32d7d0684 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Mon, 18 May 2020 10:16:58 +1200 Subject: [PATCH 032/120] Set version to 3.3.3 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 0e30f1c18..481dc29e3 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.3-alpha.3" +__version__ = "3.3.3" log = logging.getLogger('can') From cdf1e5a4c7ec1caa74a0abba575d8ea3ae197aae Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 18 Jun 2020 21:28:30 +1200 Subject: [PATCH 033/120] Start 3.3.4 release notes --- CHANGELOG.txt | 8 ++++++++ can/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c2a6ea38a..1584ef235 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,11 @@ +Version 3.3.4 +==== + +Last call for Python2 support. + +* #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. + + Version 3.3.3 ==== diff --git a/can/__init__.py b/can/__init__.py index 481dc29e3..45886435d 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.3" +__version__ = "3.3.4-dev0" log = logging.getLogger('can') From ea142fdb9a35091f83ae6617fa829422b3cdd2b2 Mon Sep 17 00:00:00 2001 From: karl ding Date: Thu, 18 Jun 2020 02:43:18 -0700 Subject: [PATCH 034/120] Fix SocketCAN periodic Tasks on Python 2 (#850) socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. Also cherry pick over the relevant cyclic SocketCAN tests and enable Travis CI for SocketCAN tests on Python 2. Fixes #845 --- .travis.yml | 8 ++ can/interfaces/socketcan/socketcan.py | 2 +- test/test_cyclic_socketcan.py | 176 ++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 test/test_cyclic_socketcan.py diff --git a/.travis.yml b/.travis.yml index bcd18033a..3683f5fbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,6 +52,14 @@ jobs: # Unit Testing Stage + # testing socketcan on Trusty & Python 2.7, since it is not available on Xenial + - stage: test + name: Socketcan + os: linux + dist: trusty + python: "2.7" + sudo: required + env: TEST_SOCKETCAN=TRUE # testing socketcan on Trusty & Python 3.6, since it is not available on Xenial - stage: test name: Socketcan diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index b671f926a..4bb989dd7 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -363,7 +363,7 @@ def _tx_setup(self, message): ) try: self.bcm_socket.send(check_header) - except OSError as e: + except (socket.error, OSError) as e: if e.errno != errno.EINVAL: raise e else: diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py new file mode 100644 index 000000000..43a763c79 --- /dev/null +++ b/test/test_cyclic_socketcan.py @@ -0,0 +1,176 @@ +""" +This module tests multiple message cyclic send tasks. +""" +import unittest + +import time +import can + +from .config import TEST_INTERFACE_SOCKETCAN + + +@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +class CyclicSocketCan(unittest.TestCase): + BITRATE = 500000 + TIMEOUT = 0.1 + + INTERFACE_1 = "socketcan" + CHANNEL_1 = "vcan0" + INTERFACE_2 = "socketcan" + CHANNEL_2 = "vcan0" + + PERIOD = 1.0 + + DELTA = 0.01 + + def _find_start_index(self, tx_messages, message): + """ + :param tx_messages: + The list of messages that were passed to the periodic backend + :param message: + The message whose data we wish to match and align to + + :returns: start index in the tx_messages + """ + start_index = -1 + for index, tx_message in enumerate(tx_messages): + if tx_message.data == message.data: + start_index = index + break + return start_index + + def setUp(self): + self._send_bus = can.Bus( + interface=self.INTERFACE_1, channel=self.CHANNEL_1, bitrate=self.BITRATE + ) + self._recv_bus = can.Bus( + interface=self.INTERFACE_2, channel=self.CHANNEL_2, bitrate=self.BITRATE + ) + + def tearDown(self): + self._send_bus.shutdown() + self._recv_bus.shutdown() + + def test_cyclic_initializer_message(self): + message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + task = self._send_bus.send_periodic(message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + # Take advantage of kernel's queueing mechanisms + time.sleep(4 * self.PERIOD) + task.stop() + + for _ in range(4): + tx_message = message + rx_message = self._recv_bus.recv(self.TIMEOUT) + + self.assertIsNotNone(rx_message) + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + def test_create_same_id_raises_exception(self): + messages_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + messages_b = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + + task_a = self._send_bus.send_periodic(messages_a, 1) + self.assertIsInstance(task_a, can.broadcastmanager.CyclicSendTaskABC) + + # The second one raises a ValueError when we attempt to create a new + # Task, since it has the same arbitration ID. + with self.assertRaises(ValueError): + task_b = self._send_bus.send_periodic(messages_b, 1) + + def test_modify_data_message(self): + message_odd = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + message_even = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + task = self._send_bus.send_periodic(message_odd, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + results_odd = [] + results_even = [] + for _ in range(1 * 4): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_odd.append(result) + + task.modify_data(message_even) + for _ in range(1 * 4): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_even.append(result) + + task.stop() + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results_even): + tx_message = message_even + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + if rx_index != 0: + prev_rx_message = results_even[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + for rx_index, rx_message in enumerate(results_odd): + tx_message = message_odd + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + if rx_index != 0: + prev_rx_message = results_odd[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + +if __name__ == "__main__": + unittest.main() From ed58372c66a5c53297781e0b10e75093e7aa012e Mon Sep 17 00:00:00 2001 From: Johan Brus <69041456+johanbrus@users.noreply.github.com> Date: Tue, 4 Aug 2020 10:19:45 +0200 Subject: [PATCH 035/120] Update configuration.rst (#879) the line can.interfaces.interface gave the error "ModuleNotFoundError: No module named 'can.interfaces.interface' Hence propose to remove 'interfaces' --- doc/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index 142e816da..230eec1bd 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -19,7 +19,7 @@ the **interface** and **channel** before importing from ``can.interfaces``. can.rc['interface'] = 'socketcan' can.rc['channel'] = 'vcan0' can.rc['bitrate'] = 500000 - from can.interfaces.interface import Bus + from can.interface import Bus bus = Bus() From 0c34e5070ccaa3c89d6c1893370083943f0d5390 Mon Sep 17 00:00:00 2001 From: Karl Date: Mon, 3 Aug 2020 22:14:45 -0700 Subject: [PATCH 036/120] Fix RecursionError in Message.__getattr__ __getattr__ incorrectly assumes that self._dict always exists. However, if self._dict doesn't exist, the function attempts to call __getattr__ again, which results in infinite recursion when serializing the object via pickle. This fixes the implementation of __getattr__ and adds a test to exercise pickling/unpickling the Message. Fixes #804 --- can/message.py | 4 +++- test/test_message_class.py | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/can/message.py b/can/message.py index f85218fc0..9002cc625 100644 --- a/can/message.py +++ b/can/message.py @@ -53,7 +53,9 @@ def __getattr__(self, key): # TODO keep this for a version, in order to not break old code # this entire method (as well as the _dict attribute in __slots__ and the __setattr__ method) # can be removed in 4.0 - # this method is only called if the attribute was not found elsewhere, like in __slots__ + # this method is only called if the attribute was not found elsewhere, like in __slots_ + if key not in self.__slots__: + raise AttributeError try: warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) return self._dict[key] diff --git a/test/test_message_class.py b/test/test_message_class.py index 85dbe8560..05ed14b2a 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -5,12 +5,15 @@ import sys from math import isinf, isnan from copy import copy, deepcopy +import pickle from hypothesis import given, settings, reproduce_failure import hypothesis.strategies as st from can import Message +from .message_helper import ComparingMessagesTestCase + class TestMessageClass(unittest.TestCase): """ @@ -70,7 +73,7 @@ def test_methods(self, **kwargs): # check copies and equalities if is_valid: - self.assertEqual(message, message) + self.assertEqual(message, message) normal_copy = copy(message) deep_copy = deepcopy(message) for other in (normal_copy, deep_copy, message): @@ -79,5 +82,31 @@ def test_methods(self, **kwargs): self.assertTrue(message.equals(other, timestamp_delta=0)) -if __name__ == '__main__': +class MessageSerialization(unittest.TestCase, ComparingMessagesTestCase): + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=0.016, preserves_channel=True + ) + + def test_serialization(self): + message = Message( + timestamp=1.0, + arbitration_id=0x401, + is_extended_id=False, + is_remote_frame=False, + is_error_frame=False, + channel=1, + dlc=6, + data=bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]), + is_fd=False, + ) + + serialized = pickle.dumps(message, -1) + deserialized = pickle.loads(serialized) + + self.assertMessageEqual(message, deserialized) + + +if __name__ == "__main__": unittest.main() From 23cf849f1bbdd1aa40576220d2583f625e8aeb66 Mon Sep 17 00:00:00 2001 From: zhupumpkin <11852827@qq.com> Date: Fri, 6 Mar 2020 10:31:43 +0800 Subject: [PATCH 037/120] fix issue 787 --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index 9002cc625..2246f1d05 100644 --- a/can/message.py +++ b/can/message.py @@ -112,7 +112,7 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, if is_extended_id is not None: self.is_extended_id = is_extended_id else: - self.is_extended_id = True if extended_id is None else extended_id + self.is_extended_id = False if extended_id is None else extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame From 9682d6a916be2d9dbe61857636de20c99e723204 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Aug 2020 12:01:22 +1200 Subject: [PATCH 038/120] Update changelog and bump to alpha version --- CHANGELOG.txt | 5 +++++ can/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1584ef235..70b865475 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,11 @@ Version 3.3.4 Last call for Python2 support. * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. +* #846 Use inter-process mutex to prevent concurrent neoVI device open. +* #879 Updating incorrect api documentation. +* #885 Fix recursion message in Message.__getattr__ +* #845 Fix socketcan issue +* #788 Fix issue with extended_id Version 3.3.3 diff --git a/can/__init__.py b/can/__init__.py index 45886435d..d3c824cad 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.4-dev0" +__version__ = "3.3.4-alpha0" log = logging.getLogger('can') From 0988fba52446e18c35fe1ebc3e5db11749fd0db9 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Aug 2020 16:05:58 +1200 Subject: [PATCH 039/120] Revert change of Message extended id default behaviour. --- CHANGELOG.txt | 1 - can/__init__.py | 2 +- can/message.py | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 70b865475..9aa2d95db 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -8,7 +8,6 @@ Last call for Python2 support. * #879 Updating incorrect api documentation. * #885 Fix recursion message in Message.__getattr__ * #845 Fix socketcan issue -* #788 Fix issue with extended_id Version 3.3.3 diff --git a/can/__init__.py b/can/__init__.py index d3c824cad..e68fcd743 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.4-alpha0" +__version__ = "3.3.4-alpha1" log = logging.getLogger('can') diff --git a/can/message.py b/can/message.py index 2246f1d05..84f675183 100644 --- a/can/message.py +++ b/can/message.py @@ -112,7 +112,8 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, if is_extended_id is not None: self.is_extended_id = is_extended_id else: - self.is_extended_id = False if extended_id is None else extended_id + # Default behaviour is to create extended id messages + self.is_extended_id = True if extended_id is None else extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame From 254514178a7e602687fe22beeef8672628794165 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Aug 2020 16:22:16 +1200 Subject: [PATCH 040/120] Upgrade version of pytest --- .travis.yml | 1 + setup.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3683f5fbc..cca71b50e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ env: install: - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - travis_retry pip install .[test] + - pip freeze script: - | diff --git a/setup.py b/setup.py index e5b05e8dc..797409c81 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ from __future__ import absolute_import +import platform from os import listdir from os.path import isfile, join import re @@ -31,7 +32,7 @@ tests_require = [ 'mock~=2.0', - 'pytest~=4.3', + 'pytest~=4.6', 'pytest-timeout~=1.3', 'pytest-cov~=2.8', # coveragepy==5.0 fails with `Safety level may not be changed inside a transaction` @@ -53,7 +54,6 @@ needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) pytest_runner = ["pytest-runner"] if needs_pytest else [] - setup( # Description name="python-can", From b47ca2fae4fb15771b519b572f91bfdf171874f3 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Aug 2020 19:48:34 +1200 Subject: [PATCH 041/120] Tag version 3.3.4-beta.0 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index e68fcd743..cfce6f0a0 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.4-alpha1" +__version__ = "3.3.4-beta.0" log = logging.getLogger('can') From 5c7810f65e2935ffea30054a3b4ac350da1e844e Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 16 Aug 2020 15:54:35 +1200 Subject: [PATCH 042/120] Change ascii conversion to be compatible with Python2.7 in ixxat backend. Contributed by @wkemps --- can/interfaces/ixxat/canlib.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 84c8751c1..eac867344 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -328,10 +328,12 @@ def __init__(self, channel, can_filters=None, **kwargs): else: raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) else: - if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): + if (UniqueHardwareId is None) or ( + self._device_info.UniqueHardwareId.AsChar == UniqueHardwareId.encode("ascii")): break else: - log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) + log.debug("Ignoring IXXAT with hardware id '%s'.", + self._device_info.UniqueHardwareId.AsChar.decode("ascii")) _canlib.vciEnumDeviceClose(self._device_handle) _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) From bdf46cd16ba8edf70199baa564d1f2139118a68f Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 16 Aug 2020 15:55:07 +1200 Subject: [PATCH 043/120] Version 3.3.4 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index cfce6f0a0..2704efc3d 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.4-beta.0" +__version__ = "3.3.4" log = logging.getLogger('can') From d271323b9bf565fc500acaae774892c3d79ad075 Mon Sep 17 00:00:00 2001 From: karl ding Date: Wed, 26 Aug 2020 03:38:27 -0700 Subject: [PATCH 044/120] Fix iteration in Bus.stop_all_periodic_tasks (#901) Fix iteration in Bus.stop_all_periodic_tasks so tasks are properly stopped. In addition, add a test in order to verify that Cyclic Tasks are correctly stopped when multiple tasks are active on a particular bus. The test is carried out via the SocketCAN interface. Note: This is a squashed cherry pick of the following commits from develop: - 8112d13ceb4493067174be899d4f014131cd4114 - 1aa0bc64033b5fff6bc88474e7d1afb52e81ee7a Addresses #634 --- can/bus.py | 12 +++++++-- test/test_cyclic_socketcan.py | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/can/bus.py b/can/bus.py index 2b36b3c57..2b3044cf1 100644 --- a/can/bus.py +++ b/can/bus.py @@ -247,13 +247,21 @@ def _send_periodic_internal(self, msg, period, duration=None): return task def stop_all_periodic_tasks(self, remove_tasks=True): - """Stop sending any messages that were started using bus.send_periodic + """Stop sending any messages that were started using **bus.send_periodic**. + + .. note:: + The result is undefined if a single task throws an exception while being stopped. :param bool remove_tasks: Stop tracking the stopped tasks. """ for task in self._periodic_tasks: - task.stop(remove_task=remove_tasks) + # we cannot let `task.stop()` modify `self._periodic_tasks` while we are + # iterating over it (#634) + task.stop(remove_task=False) + + if remove_tasks: + self._periodic_tasks = [] def __iter__(self): """Allow iteration on messages as they are received. diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index 43a763c79..831264bf4 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -171,6 +171,56 @@ def test_modify_data_message(self): <= self.DELTA ) + def test_stop_all_periodic_tasks_and_remove_task(self): + message_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + message_b = can.Message( + arbitration_id=0x402, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + message_c = can.Message( + arbitration_id=0x403, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + + # Start Tasks + task_a = self._send_bus.send_periodic(message_a, self.PERIOD) + task_b = self._send_bus.send_periodic(message_b, self.PERIOD) + task_c = self._send_bus.send_periodic(message_c, self.PERIOD) + + self.assertIsInstance(task_a, can.broadcastmanager.ModifiableCyclicTaskABC) + self.assertIsInstance(task_b, can.broadcastmanager.ModifiableCyclicTaskABC) + self.assertIsInstance(task_c, can.broadcastmanager.ModifiableCyclicTaskABC) + + for _ in range(6): + _ = self._recv_bus.recv(self.PERIOD) + + # Stop all tasks and delete + self._send_bus.stop_all_periodic_tasks(remove_tasks=True) + + # Now wait for a few periods, after which we should definitely not + # receive any CAN messages + time.sleep(4 * self.PERIOD) + + # If we successfully deleted everything, then we will eventually read + # 0 messages. + successfully_stopped = False + for _ in range(6): + rx_message = self._recv_bus.recv(self.PERIOD) + + if rx_message is None: + successfully_stopped = True + break + self.assertTrue(successfully_stopped, "Still received messages after stopping") + + # None of the tasks should still be associated with the bus + self.assertEqual(0, len(self._send_bus._periodic_tasks)) + if __name__ == "__main__": unittest.main() From 57b1a341103a52fa6db848fe15b40b3fb6e7a5c3 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 26 Aug 2020 22:42:16 +1200 Subject: [PATCH 045/120] Update CHANGELOG.txt --- CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9aa2d95db..1e63e0d4b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,8 +3,8 @@ Version 3.3.4 Last call for Python2 support. +* #901 Fix iteration in Bus.stop_all_periodic_tasks * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. -* #846 Use inter-process mutex to prevent concurrent neoVI device open. * #879 Updating incorrect api documentation. * #885 Fix recursion message in Message.__getattr__ * #845 Fix socketcan issue From 9452b54ef3200e8fe296b046b4fb78ecd3c9abff Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 28 Aug 2020 09:15:20 -0400 Subject: [PATCH 046/120] Backport Neovi features for release 3.3.4 (#903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use inter-process mutex to prevent concurrent neoVI device open When neoVI server is enabled, there is an issue with concurrent device open. * Move neoVI open lock to module level * Adding dummy file lock when importing FileLock fails * Grammar fix * Raising more precise API error when set bitrate fails Also calling shutdown before raising exception from the init if the device is opened * Simplified if fd condition in init * Moving filelock to neovi extras_require * Adding comma back * Update CHANGELOG.txt Co-authored-by: Pierre-Luc Tessier Gagné --- CHANGELOG.txt | 1 + can/interfaces/ics_neovi/neovi_bus.py | 59 +++++++++++++++++++++++---- setup.py | 2 +- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1e63e0d4b..1f8cf82bf 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -15,6 +15,7 @@ Version 3.3.3 Backported fixes from 4.x development branch which targets Python 3. +* #846 Use inter-process mutex to prevent concurrent neoVI device open. * #798 Backport caching msg.data value in neovi interface. * #796 Fix Vector CANlib treatment of empty app name. * #771 Handle empty CSV file. diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 530d7c524..314967708 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -11,6 +11,8 @@ """ import logging +import os +import tempfile from collections import deque from can import Message, CanError, BusABC @@ -27,6 +29,35 @@ ics = None +try: + from filelock import FileLock +except ImportError as ie: + + logger.warning( + "Using ICS NeoVi can backend without the " + "filelock module installed may cause some issues!: %s", + ie, + ) + + class FileLock: + """Dummy file lock that does not actually do anything""" + + def __init__(self, lock_file, timeout=-1): + self._lock_file = lock_file + self.timeout = timeout + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + return None + + +# Use inter-process mutex to prevent concurrent device open. +# When neoVI server is enabled, there is an issue with concurrent device open. +open_lock = FileLock(os.path.join(tempfile.gettempdir(), "neovi.lock")) + + class ICSApiError(CanError): """ Indicates an error with the ICS API. @@ -118,18 +149,28 @@ def __init__(self, channel, can_filters=None, **kwargs): type_filter = kwargs.get('type_filter') serial = kwargs.get('serial') self.dev = self._find_device(type_filter, serial) - ics.open_device(self.dev) - if 'bitrate' in kwargs: - for channel in self.channels: - ics.set_bit_rate(self.dev, kwargs.get('bitrate'), channel) + with open_lock: + ics.open_device(self.dev) - fd = kwargs.get('fd', False) - if fd: - if 'data_bitrate' in kwargs: + try: + if "bitrate" in kwargs: for channel in self.channels: - ics.set_fd_bit_rate( - self.dev, kwargs.get('data_bitrate'), channel) + ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel) + + if kwargs.get("fd", False): + if "data_bitrate" in kwargs: + for channel in self.channels: + ics.set_fd_bit_rate( + self.dev, kwargs.get("data_bitrate"), channel + ) + except ics.RuntimeError as re: + logger.error(re) + err = ICSApiError(*ics.get_last_api_error(self.dev)) + try: + self.shutdown() + finally: + raise err self._use_system_timestamp = bool( kwargs.get('use_system_timestamp', False) diff --git a/setup.py b/setup.py index 797409c81..8f270378b 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ # Dependencies extras_require = { 'serial': ['pyserial~=3.0'], - 'neovi': ['python-ics>=2.12'] + 'neovi': ['python-ics>=2.12', 'filelock'] } tests_require = [ From 551cc946fe70cd3aa95d2d8b758604aeeb28a3e7 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 28 Aug 2020 09:16:52 -0400 Subject: [PATCH 047/120] Update CHANGELOG.txt --- CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1f8cf82bf..d11885fc0 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ Version 3.3.4 Last call for Python2 support. +* #846 Use inter-process mutex to prevent concurrent neoVI device open. * #901 Fix iteration in Bus.stop_all_periodic_tasks * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. * #879 Updating incorrect api documentation. @@ -15,7 +16,6 @@ Version 3.3.3 Backported fixes from 4.x development branch which targets Python 3. -* #846 Use inter-process mutex to prevent concurrent neoVI device open. * #798 Backport caching msg.data value in neovi interface. * #796 Fix Vector CANlib treatment of empty app name. * #771 Handle empty CSV file. From 453a4b7112e1de2ff07999a86c013c02c7f4684f Mon Sep 17 00:00:00 2001 From: Karl Date: Wed, 30 Sep 2020 21:33:37 -0700 Subject: [PATCH 048/120] vector: Skip channels without CAN support Skip any channels that don't have support for CAN (ex. LIN, Digital/Analog IO) instead of accepting everything when using auto-detection. Note: This is a backport of #641 to 3.3.4 Addresses #908 --- can/interfaces/vector/canlib.py | 2 ++ can/interfaces/vector/vxlapi.py | 1 + 2 files changed, 3 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 65af0147b..0864ecefe 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -387,6 +387,8 @@ def _detect_available_configs(): channel_configs = get_channel_configs() LOG.info('Found %d channels', len(channel_configs)) for channel_config in channel_configs: + if not channel_config.channelBusCapabilities & vxlapi.XL_BUS_ACTIVE_CAP_CAN: + continue LOG.info('Channel index %d: %s', channel_config.channelIndex, channel_config.name.decode('ascii')) diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index ae87706c4..461a62197 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -63,6 +63,7 @@ XL_INTERFACE_VERSION = 3 XL_INTERFACE_VERSION_V4 = 4 +XL_BUS_ACTIVE_CAP_CAN = XL_BUS_TYPE_CAN << 16 XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 0x80000000 # structure for XL_RECEIVE_MSG, XL_TRANSMIT_MSG From 14c016272136c66c3737c0fe33aeca9e3ec72915 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 3 Oct 2020 13:37:37 +0200 Subject: [PATCH 049/120] Update CHANGELOG.txt --- CHANGELOG.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d11885fc0..7899d50b6 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ Version 3.3.4 Last call for Python2 support. +* #916 Vector: Skip channels without CAN support * #846 Use inter-process mutex to prevent concurrent neoVI device open. * #901 Fix iteration in Bus.stop_all_periodic_tasks * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. From d3ee3285b55571cc076b32d3d8c0bf7a7f44c792 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 4 Oct 2020 16:44:20 +1300 Subject: [PATCH 050/120] Remove usused import from setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8f270378b..898f54c16 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ from __future__ import absolute_import -import platform from os import listdir from os.path import isfile, join import re @@ -67,6 +66,7 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", From 9717c9074df052d655a3d0b2bbd8aa887f2b5929 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 21 Mar 2021 17:04:25 +0100 Subject: [PATCH 051/120] Added file for implementation of trc file read and write --- can/io/trc.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 can/io/trc.py diff --git a/can/io/trc.py b/can/io/trc.py new file mode 100644 index 000000000..c1d6bf511 --- /dev/null +++ b/can/io/trc.py @@ -0,0 +1,56 @@ +# coding: utf-8 + +""" +Reader and writer for can logging files in peak trc format +""" + +from __future__ import absolute_import + +import logging + +from ..listener import Listener +from .generic import BaseIOHandler + + +logger = logging.getLogger('can.io.trc') + + +class TRCReader(BaseIOHandler): + """ + Iterator of CAN messages from a TRC logging file. + """ + + def __init__(self, file): + """ + :param file: a path-like object or as file-like object to read from + If this is a file-like object, is has to opened in text + read mode, not binary read mode. + """ + super(TRCReader, self).__init__(file, mode='r') + + def __iter__(self): + pass + + +class TRCWriter(BaseIOHandler, Listener): + """Logs CAN data to text file (.trc). + + The measurement starts with the timestamp of the first registered message. + If a message has a timestamp smaller than the previous one or None, + it gets assigned the timestamp that was written for the last message. + It the first message does not have a timestamp, it is set to zero. + """ + + def __init__(self, file, channel=1): + """ + :param file: a path-like object or as file-like object to write to + If this is a file-like object, is has to opened in text + write mode, not binary write mode. + :param channel: a default channel to use when the message does not + have a channel set + """ + super(TRCWriter, self).__init__(file, mode='w') + self.channel = channel + + def on_message_received(self, msg): + pass From cdaac0935e28973df886fe075c66a4fbda5a0ffa Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Wed, 26 May 2021 06:48:48 +0200 Subject: [PATCH 052/120] Added trc file header template --- can/io/trc.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/can/io/trc.py b/can/io/trc.py index c1d6bf511..b3be8594f 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -15,6 +15,30 @@ logger = logging.getLogger('can.io.trc') +# Format for trace file header. +# +# Fields: +# - days: Number of days that have passed since 30. December 1899 +# - milliseconds: milliseconds since 00:00:00 of day +# - filepath: full path to log file +# - starttime: starttime of trace formatted as %d.%m.%Y %H:%M:%S +FMT_TRC_HEADER_VER_1_1 = """\ +;$FILEVERSION=1.1 +;$STARTTIME={days}.{milliseconds} +; +; {filepath} +; +; Start time: {starttime} +; Message Number +; | Time Offset (ms) +; | | Type +; | | | ID (hex) +; | | | | Data Length Code +; | | | | | Data Bytes (hex) ... +; | | | | | | +;---+-- ----+---- --+-- ----+--- + -+ -- -- -- -- -- -- --""" + + class TRCReader(BaseIOHandler): """ Iterator of CAN messages from a TRC logging file. From acd91dc23155246a71b4b8019561b5699030179e Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Wed, 26 May 2021 06:59:27 +0200 Subject: [PATCH 053/120] Added link where to lookup the trc file format description --- can/io/trc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index b3be8594f..97fa2f5a1 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -2,7 +2,12 @@ """ Reader and writer for can logging files in peak trc format -""" + +See https://www.peak-system.com/produktcd/Pdf/English/PEAK_CAN_TRC_File_Format.pdf +for file format description + +Version 1.1 will be implemented as it is most commonly used +""" # noqa from __future__ import absolute_import From 136eef1e1ffb118c81b03d4080b907d502d1a133 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 22:07:34 +0100 Subject: [PATCH 054/120] Add trc reader and writer to backends --- can/io/__init__.py | 1 + can/io/logger.py | 3 +++ can/io/player.py | 3 +++ 3 files changed, 7 insertions(+) diff --git a/can/io/__init__.py b/can/io/__init__.py index 66e3a8c56..c514c7176 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -14,3 +14,4 @@ from .csv import CSVWriter, CSVReader from .sqlite import SqliteReader, SqliteWriter from .printer import Printer +from .trc import TRCReader, TRCWriter diff --git a/can/io/logger.py b/can/io/logger.py index 3890e7432..1a6a81fb7 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -27,6 +27,7 @@ from .csv import CSVWriter from .sqlite import SqliteWriter from .printer import Printer +from .trc import TRCWriter from ..typechecking import StringPathLike @@ -42,6 +43,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method * .db: :class:`can.SqliteWriter` * .log :class:`can.CanutilsLogWriter` * .txt :class:`can.Printer` + * .trc :class:`can.TRCWriter` The **filename** may also be *None*, to fall back to :class:`can.Printer`. @@ -61,6 +63,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method ".db": SqliteWriter, ".log": CanutilsLogWriter, ".txt": Printer, + ".trc": TRCWriter, } @staticmethod diff --git a/can/io/player.py b/can/io/player.py index f710e15d5..757f63ca2 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -19,6 +19,7 @@ from .canutils import CanutilsLogReader from .csv import CSVReader from .sqlite import SqliteReader +from .trc import TRCReader class LogReader(BaseIOHandler): @@ -32,6 +33,7 @@ class LogReader(BaseIOHandler): * .csv * .db * .log + * .trc Exposes a simple iterator interface, to use simply: @@ -55,6 +57,7 @@ class LogReader(BaseIOHandler): ".csv": CSVReader, ".db": SqliteReader, ".log": CanutilsLogReader, + ".trc": TRCReader, } @staticmethod From 9c44b4e12f2b1963e9611cb13000289708714e28 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 21:08:27 +0000 Subject: [PATCH 055/120] Format code with black --- can/io/trc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 97fa2f5a1..59c8b78a1 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -7,7 +7,7 @@ for file format description Version 1.1 will be implemented as it is most commonly used -""" # noqa +""" # noqa from __future__ import absolute_import @@ -17,7 +17,7 @@ from .generic import BaseIOHandler -logger = logging.getLogger('can.io.trc') +logger = logging.getLogger("can.io.trc") # Format for trace file header. @@ -55,7 +55,7 @@ def __init__(self, file): If this is a file-like object, is has to opened in text read mode, not binary read mode. """ - super(TRCReader, self).__init__(file, mode='r') + super(TRCReader, self).__init__(file, mode="r") def __iter__(self): pass @@ -78,7 +78,7 @@ def __init__(self, file, channel=1): :param channel: a default channel to use when the message does not have a channel set """ - super(TRCWriter, self).__init__(file, mode='w') + super(TRCWriter, self).__init__(file, mode="w") self.channel = channel def on_message_received(self, msg): From dbf692c6e5294c3f7619e9e3b1171be73e3f95b2 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 22:17:14 +0100 Subject: [PATCH 056/120] Add example trace file as conversion from asc log --- test/data/test_CanMessage.trc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/data/test_CanMessage.trc diff --git a/test/data/test_CanMessage.trc b/test/data/test_CanMessage.trc new file mode 100644 index 000000000..215997b57 --- /dev/null +++ b/test/data/test_CanMessage.trc @@ -0,0 +1,23 @@ +;$FILEVERSION=2.1 +;$STARTTIME=0 +;$COLUMNS=N,O,T,B,I,d,R,L,D +; +; C:\Users\User\Desktop\python-can\test\data\test_CanMessage.trc +; Start time: 30.09.2017 22:06:13.191.000 +; Generated by PEAK-Converter Version 2.2.4.136 +; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage.asc +;------------------------------------------------------------------------------- +; Bus Name Connection Protocol +; N/A N/A N/A N/A +;------------------------------------------------------------------------------- +; Message Time Type ID Rx/Tx +; Number Offset | Bus [hex] | Reserved +; | [ms] | | | | | Data Length Code +; | | | | | | | | Data [hex] ... +; | | | | | | | | | +;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- +;Begin Triggerblock Sat Sep 30 10:06:13.191 PM 2017 +; 0.000000 Start of measurement + 1 2501.000 DT 2 00C8 Tx - 8 09 08 07 06 05 04 03 02 + 2 17876.708 DT 1 06F9 Rx - 8 05 0C 00 00 00 00 00 00 +;End TriggerBlock From d458179b17afafb9ad053bcbc95170a745baeaa1 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 22:34:26 +0100 Subject: [PATCH 057/120] Add TRCWriter and TRCReader to can package --- can/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/__init__.py b/can/__init__.py index c95b19ebf..596ab5114 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -30,6 +30,7 @@ from .io import CanutilsLogReader, CanutilsLogWriter from .io import CSVWriter, CSVReader from .io import SqliteWriter, SqliteReader +from .io import TRCReader, TRCWriter from .util import set_logging_level From c239428fe51d727445db88a6b19e3cb966461cbf Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 22:50:38 +0100 Subject: [PATCH 058/120] Basic header extraction in reader --- can/io/trc.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 59c8b78a1..224eb589b 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -9,10 +9,11 @@ Version 1.1 will be implemented as it is most commonly used """ # noqa -from __future__ import absolute_import +from typing import cast, Any, Generator, IO import logging +from ..message import Message from ..listener import Listener from .generic import BaseIOHandler @@ -57,9 +58,20 @@ def __init__(self, file): """ super(TRCReader, self).__init__(file, mode="r") - def __iter__(self): - pass + if not self.file: + raise ValueError("The given file cannot be None") + + def _extract_header(self): + for line in self.file: + if line.startswith(";"): + continue + else: + break + def __iter__(self) -> Generator[Message, None, None]: + # This is guaranteed to not be None since we raise ValueError in __init__ + self.file = cast(IO[Any], self.file) + self._extract_header() class TRCWriter(BaseIOHandler, Listener): """Logs CAN data to text file (.trc). From 8ada087e46edc9aa9fb09812e0a21fd11491acfb Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 23:03:24 +0100 Subject: [PATCH 059/120] Implement read of file version --- can/io/trc.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index 224eb589b..70771eacf 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -61,9 +61,18 @@ def __init__(self, file): if not self.file: raise ValueError("The given file cannot be None") + self.file_version = None + def _extract_header(self): for line in self.file: - if line.startswith(";"): + line = line.strip() + if line.startswith(";$FILEVERSION"): + logger.debug(F"TRCReader: Found file version '{line}'") + try: + self.file_version = line.split('=')[1] + except IndexError: + logger.debug(F"TRCReader: Failed to parse version") + elif line.startswith(";"): continue else: break From 4301b357d598f59a28a23115ea4436e561cf5ec6 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 23:27:10 +0100 Subject: [PATCH 060/120] Remove useless format string --- can/io/trc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index 70771eacf..ac345ed02 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -71,7 +71,7 @@ def _extract_header(self): try: self.file_version = line.split('=')[1] except IndexError: - logger.debug(F"TRCReader: Failed to parse version") + logger.debug("TRCReader: Failed to parse version") elif line.startswith(";"): continue else: From 8f3969186a7b9db9ce4719c06dc0479c1da77647 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 23:28:22 +0100 Subject: [PATCH 061/120] Implement basic message parsing --- can/io/trc.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/can/io/trc.py b/can/io/trc.py index ac345ed02..00af1c37d 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -82,6 +82,26 @@ def __iter__(self) -> Generator[Message, None, None]: self.file = cast(IO[Any], self.file) self._extract_header() + for line in self.file: + temp = line.strip() + if temp.startswith(';'): + # Comment line + continue + + cols = temp.split() + try: + msg = Message() + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(cols[4], 16) + msg.is_extended_id = len(cols[4]) > 4 + msg.channel = int(cols[3]) + msg.dlc = int(cols[7]) + msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) + yield msg + except IndexError: + logger.warning(F"TRCReader: Failed to parse message '{temp}'") + + class TRCWriter(BaseIOHandler, Listener): """Logs CAN data to text file (.trc). From a9a83853ed4e0d3cde690428f398de5a2dcfe4fe Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 23:34:25 +0100 Subject: [PATCH 062/120] Move message parsing to separate function --- can/io/trc.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 00af1c37d..ca4835769 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -77,6 +77,20 @@ def _extract_header(self): else: break + def _parse_msg(self, line): + cols = line.split() + try: + msg = Message() + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(cols[4], 16) + msg.is_extended_id = len(cols[4]) > 4 + msg.channel = int(cols[3]) + msg.dlc = int(cols[7]) + msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) + return msg + except IndexError: + logger.warning(F"TRCReader: Failed to parse message '{line}'") + def __iter__(self) -> Generator[Message, None, None]: # This is guaranteed to not be None since we raise ValueError in __init__ self.file = cast(IO[Any], self.file) @@ -88,18 +102,9 @@ def __iter__(self) -> Generator[Message, None, None]: # Comment line continue - cols = temp.split() - try: - msg = Message() - msg.timestamp = float(cols[1]) / 1000 - msg.arbitration_id = int(cols[4], 16) - msg.is_extended_id = len(cols[4]) > 4 - msg.channel = int(cols[3]) - msg.dlc = int(cols[7]) - msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) + msg = self._parse_msg(temp) + if msg is not None: yield msg - except IndexError: - logger.warning(F"TRCReader: Failed to parse message '{temp}'") class TRCWriter(BaseIOHandler, Listener): From 8a0f8ba9b2444d8ac3e9949b1c714ad7b3a4add4 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 23:42:56 +0100 Subject: [PATCH 063/120] Implement parse of first message --- can/io/trc.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index ca4835769..b99572efd 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -75,7 +75,7 @@ def _extract_header(self): elif line.startswith(";"): continue else: - break + return line def _parse_msg(self, line): cols = line.split() @@ -94,7 +94,11 @@ def _parse_msg(self, line): def __iter__(self) -> Generator[Message, None, None]: # This is guaranteed to not be None since we raise ValueError in __init__ self.file = cast(IO[Any], self.file) - self._extract_header() + first_line = self._extract_header() + + msg = self._parse_msg(first_line) + if msg is not None: + yield msg for line in self.file: temp = line.strip() From daeb26a6c7cc941299fff9b62125eae6306af6e2 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 23:49:40 +0100 Subject: [PATCH 064/120] Handle none rx messages --- can/io/trc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/io/trc.py b/can/io/trc.py index b99572efd..32e6042fb 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -87,6 +87,7 @@ def _parse_msg(self, line): msg.channel = int(cols[3]) msg.dlc = int(cols[7]) msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) + msg.is_rx = cols[5] == 'Rx' return msg except IndexError: logger.warning(F"TRCReader: Failed to parse message '{line}'") From 1562a62896eead5872409491f71cb6f251c8434a Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 23:50:12 +0100 Subject: [PATCH 065/120] Handle empty files --- can/io/trc.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 32e6042fb..1b5bcde17 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -97,9 +97,10 @@ def __iter__(self) -> Generator[Message, None, None]: self.file = cast(IO[Any], self.file) first_line = self._extract_header() - msg = self._parse_msg(first_line) - if msg is not None: - yield msg + if first_line is not None: + msg = self._parse_msg(first_line) + if msg is not None: + yield msg for line in self.file: temp = line.strip() From ec48c1a315ae4057f574ced1052467269d616d3f Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 00:01:12 +0100 Subject: [PATCH 066/120] Add test method for trc files --- test/logformats_test.py | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/logformats_test.py b/test/logformats_test.py index 400bf369d..a0ef4c995 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -783,6 +783,65 @@ def test_not_crashes_with_file(self): printer(message) +class TestTrcFileFormat(ReaderWriterTest): + """Tests can.TRCWriter and can.TRCReader""" + + def _setup_instance(self): + super()._setup_instance_helper( + can.TRCWriter, + can.TRCReader, + check_fd=False, + check_comments=False, + preserves_channel=False, + allowed_timestamp_delta=0.001, + adds_default_channel=0, + ) + + @unittest.skip("not implemented") + def test_path_like_explicit_stop(self): + pass + + @unittest.skip("not implemented") + def test_path_like_context_manager(self): + pass + + @unittest.skip("not implemented") + def test_file_like_explicit_stop(self): + pass + + @unittest.skip("not implemented") + def test_file_like_context_manager(self): + pass + + def _read_log_file(self, filename, **kwargs): + logfile = os.path.join(os.path.dirname(__file__), "data", filename) + with can.TRCReader(logfile, **kwargs) as reader: + return list(reader) + + def test_can_message(self): + expected_messages = [ + can.Message( + timestamp=2.5010, + arbitration_id=0xC8, + is_extended_id=False, + is_rx=False, + channel=1, + dlc=8, + data=[9, 8, 7, 6, 5, 4, 3, 2], + ), + can.Message( + timestamp=17.876708, + arbitration_id=0x6F9, + is_extended_id=False, + channel=0, + dlc=0x8, + data=[5, 0xC, 0, 0, 0, 0, 0, 0], + ), + ] + actual = self._read_log_file("test_CanMessage.trc") + self.assertMessagesEqual(actual, expected_messages) + + # this excludes the base class from being executed as a test case itself del ReaderWriterTest From a19456523ca54f2dd0d45dc806e161491bcd6db9 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Fri, 17 Dec 2021 23:02:42 +0000 Subject: [PATCH 067/120] Format code with black --- can/io/trc.py | 10 +++++----- test/logformats_test.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 1b5bcde17..d7eb78c1f 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -67,9 +67,9 @@ def _extract_header(self): for line in self.file: line = line.strip() if line.startswith(";$FILEVERSION"): - logger.debug(F"TRCReader: Found file version '{line}'") + logger.debug(f"TRCReader: Found file version '{line}'") try: - self.file_version = line.split('=')[1] + self.file_version = line.split("=")[1] except IndexError: logger.debug("TRCReader: Failed to parse version") elif line.startswith(";"): @@ -87,10 +87,10 @@ def _parse_msg(self, line): msg.channel = int(cols[3]) msg.dlc = int(cols[7]) msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) - msg.is_rx = cols[5] == 'Rx' + msg.is_rx = cols[5] == "Rx" return msg except IndexError: - logger.warning(F"TRCReader: Failed to parse message '{line}'") + logger.warning(f"TRCReader: Failed to parse message '{line}'") def __iter__(self) -> Generator[Message, None, None]: # This is guaranteed to not be None since we raise ValueError in __init__ @@ -104,7 +104,7 @@ def __iter__(self) -> Generator[Message, None, None]: for line in self.file: temp = line.strip() - if temp.startswith(';'): + if temp.startswith(";"): # Comment line continue diff --git a/test/logformats_test.py b/test/logformats_test.py index a0ef4c995..4c57eaf95 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -804,7 +804,7 @@ def test_path_like_explicit_stop(self): @unittest.skip("not implemented") def test_path_like_context_manager(self): pass - + @unittest.skip("not implemented") def test_file_like_explicit_stop(self): pass From 211f8efb2d32d3f728dba825adaedcbe07775030 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 00:15:33 +0100 Subject: [PATCH 068/120] Implement stop at end of log file --- can/io/trc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/io/trc.py b/can/io/trc.py index d7eb78c1f..703a04f49 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -112,6 +112,8 @@ def __iter__(self) -> Generator[Message, None, None]: if msg is not None: yield msg + self.stop() + class TRCWriter(BaseIOHandler, Listener): """Logs CAN data to text file (.trc). From 3137cf842e966f070ff717a66915b6346628a993 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 01:01:10 +0100 Subject: [PATCH 069/120] Implement basic write of header version 2.1 --- can/io/trc.py | 78 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 703a04f49..ef4c76540 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -9,8 +9,9 @@ Version 1.1 will be implemented as it is most commonly used """ # noqa -from typing import cast, Any, Generator, IO - +from typing import cast, Any, Generator, IO, Optional +from datetime import datetime, timedelta +import os import logging from ..message import Message @@ -28,21 +29,25 @@ # - milliseconds: milliseconds since 00:00:00 of day # - filepath: full path to log file # - starttime: starttime of trace formatted as %d.%m.%Y %H:%M:%S -FMT_TRC_HEADER_VER_1_1 = """\ -;$FILEVERSION=1.1 +FMT_TRC_HEADER_VER_2_1 = """\ +;$FILEVERSION=2.1 ;$STARTTIME={days}.{milliseconds} +;$COLUMNS=N,O,T,B,I,d,R,L,D ; ; {filepath} ; ; Start time: {starttime} -; Message Number -; | Time Offset (ms) -; | | Type -; | | | ID (hex) -; | | | | Data Length Code -; | | | | | Data Bytes (hex) ... -; | | | | | | -;---+-- ----+---- --+-- ----+--- + -+ -- -- -- -- -- -- --""" +; Generated by python-can TRCWriter +;------------------------------------------------------------------------------- +; Bus Name Connection Protocol +; N/A N/A N/A N/A +;------------------------------------------------------------------------------- +; Message Time Type ID Rx/Tx +; Number Offset | Bus [hex] | Reserved +; | [ms] | | | | | Data Length Code +; | | | | | | | | Data [hex] ... +; | | | | | | | | | +;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --""" class TRCReader(BaseIOHandler): @@ -121,10 +126,10 @@ class TRCWriter(BaseIOHandler, Listener): The measurement starts with the timestamp of the first registered message. If a message has a timestamp smaller than the previous one or None, it gets assigned the timestamp that was written for the last message. - It the first message does not have a timestamp, it is set to zero. + If the first message does not have a timestamp, it is set to zero. """ - def __init__(self, file, channel=1): + def __init__(self, file, channel: int = 1): """ :param file: a path-like object or as file-like object to write to If this is a file-like object, is has to opened in text @@ -134,6 +139,45 @@ def __init__(self, file, channel=1): """ super(TRCWriter, self).__init__(file, mode="w") self.channel = channel - - def on_message_received(self, msg): - pass + if type(file) is str: + self.filepath = os.path.abspath(file) + else: + self.filepath = "Unknown" + + self.header_written = False + + def write_header(self, timestamp): + # write start of file header + reftime = datetime(year=1899, month=12, day=30) + starttime = datetime.now() + timedelta(seconds=timestamp) + header_time = starttime - reftime + + self.file.write(FMT_TRC_HEADER_VER_2_1.format( + days=header_time.days, + milliseconds=int((header_time.seconds * 1000) + (header_time.microseconds / 1000)), + filepath=self.filepath, + starttime=starttime.strftime("%d.%m.%Y %H:%M:%S"), + ) + ) + self.header_written = True + + def log_event(self, message: str, timestamp: Optional[float] = None) -> None: + if not self.header_written: + self.write_header(timestamp) + + self.file.write(f"{message}\n") + + def on_message_received(self, msg: Message) -> None: + + if msg.is_error_frame: + logger.warning("TRCWriter: Logging error frames is not implemented") + return + if msg.is_remote_frame: + logger.warning("TRCWriter: Logging remote frames is not implemented") + return + + if msg.is_fd: + logger.warning("TRCWriter: Logging CAN FD is not implemented") + return + + self.log_event("", msg.timestamp) From 4f293af4c0456cdc6d7883a3e212424a8d835e97 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 00:01:49 +0000 Subject: [PATCH 070/120] Format code with black --- can/io/trc.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index ef4c76540..62a77ffdd 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -152,11 +152,14 @@ def write_header(self, timestamp): starttime = datetime.now() + timedelta(seconds=timestamp) header_time = starttime - reftime - self.file.write(FMT_TRC_HEADER_VER_2_1.format( - days=header_time.days, - milliseconds=int((header_time.seconds * 1000) + (header_time.microseconds / 1000)), - filepath=self.filepath, - starttime=starttime.strftime("%d.%m.%Y %H:%M:%S"), + self.file.write( + FMT_TRC_HEADER_VER_2_1.format( + days=header_time.days, + milliseconds=int( + (header_time.seconds * 1000) + (header_time.microseconds / 1000) + ), + filepath=self.filepath, + starttime=starttime.strftime("%d.%m.%Y %H:%M:%S"), ) ) self.header_written = True From 14fedc2394b1e8487f32c8aab35bdae09af0a6b7 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 01:03:19 +0100 Subject: [PATCH 071/120] Add newline after header --- can/io/trc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index ef4c76540..cc66e3c16 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -47,7 +47,8 @@ ; | [ms] | | | | | Data Length Code ; | | | | | | | | Data [hex] ... ; | | | | | | | | | -;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --""" +;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- +""" class TRCReader(BaseIOHandler): From 0d433bd00fd85b056b48ae330b3934671da8564e Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 01:39:13 +0100 Subject: [PATCH 072/120] Implement write of messages --- can/io/trc.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index cc66e3c16..f339ddf02 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -16,6 +16,7 @@ from ..message import Message from ..listener import Listener +from ..util import channel2int from .generic import BaseIOHandler @@ -130,6 +131,8 @@ class TRCWriter(BaseIOHandler, Listener): If the first message does not have a timestamp, it is set to zero. """ + FORMAT_MESSAGE = "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" + def __init__(self, file, channel: int = 1): """ :param file: a path-like object or as file-like object to write to @@ -146,10 +149,13 @@ def __init__(self, file, channel: int = 1): self.filepath = "Unknown" self.header_written = False + self.msgnr = 0 + self.first_timestamp = 0.0 def write_header(self, timestamp): # write start of file header reftime = datetime(year=1899, month=12, day=30) + self.first_timestamp = timestamp starttime = datetime.now() + timedelta(seconds=timestamp) header_time = starttime - reftime @@ -173,12 +179,37 @@ def on_message_received(self, msg: Message) -> None: if msg.is_error_frame: logger.warning("TRCWriter: Logging error frames is not implemented") return + if msg.is_remote_frame: logger.warning("TRCWriter: Logging remote frames is not implemented") return + else: + data = [f"{byte:02X}" for byte in msg.data] + + if msg.is_extended_id: + arb_id = f"{msg.arbitration_id:07X}" + else: + arb_id = f"{msg.arbitration_id:04X}" + + channel = channel2int(msg.channel) + if channel is None: + channel = self.channel + else: + # Many interfaces start channel numbering at 0 which is invalid + channel += 1 if msg.is_fd: logger.warning("TRCWriter: Logging CAN FD is not implemented") return - - self.log_event("", msg.timestamp) + else: + serialized = self.FORMAT_MESSAGE.format( + msgnr=self.msgnr, + time=(msg.timestamp-self.first_timestamp) * 1000, + channel=channel, + id=arb_id, + dir="Rx" if msg.is_rx else "Tx", + dlc=msg.dlc, + data=" ".join(data), + ) + self.msgnr += 1 + self.log_event(serialized, msg.timestamp) From af5e69b3701542b911ad4e58be6de5cb508cd17f Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 00:41:05 +0000 Subject: [PATCH 073/120] Format code with black --- can/io/trc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 8116ca085..1bb9f51bc 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -131,7 +131,9 @@ class TRCWriter(BaseIOHandler, Listener): If the first message does not have a timestamp, it is set to zero. """ - FORMAT_MESSAGE = "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" + FORMAT_MESSAGE = ( + "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" + ) def __init__(self, file, channel: int = 1): """ @@ -207,7 +209,7 @@ def on_message_received(self, msg: Message) -> None: else: serialized = self.FORMAT_MESSAGE.format( msgnr=self.msgnr, - time=(msg.timestamp-self.first_timestamp) * 1000, + time=(msg.timestamp - self.first_timestamp) * 1000, channel=channel, id=arb_id, dir="Rx" if msg.is_rx else "Tx", From 5b97680a278807d9265af70935196ec82e7aaf64 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 01:43:13 +0100 Subject: [PATCH 074/120] Enable test for trace file writing --- test/logformats_test.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 4c57eaf95..7c11c9cc8 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -790,6 +790,8 @@ def _setup_instance(self): super()._setup_instance_helper( can.TRCWriter, can.TRCReader, + check_remote_frames=False, + check_error_frames=False, check_fd=False, check_comments=False, preserves_channel=False, @@ -797,22 +799,6 @@ def _setup_instance(self): adds_default_channel=0, ) - @unittest.skip("not implemented") - def test_path_like_explicit_stop(self): - pass - - @unittest.skip("not implemented") - def test_path_like_context_manager(self): - pass - - @unittest.skip("not implemented") - def test_file_like_explicit_stop(self): - pass - - @unittest.skip("not implemented") - def test_file_like_context_manager(self): - pass - def _read_log_file(self, filename, **kwargs): logfile = os.path.join(os.path.dirname(__file__), "data", filename) with can.TRCReader(logfile, **kwargs) as reader: From f287a63a2f69804c7ede6384b5f2ec180ac578b5 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 14:36:18 +0100 Subject: [PATCH 075/120] Add test files for different pcan trace file versions --- test/data/test_CanMessage_V1_0_BUS1.trc | 28 ++++++++++++++++++++++++ test/data/test_CanMessage_V1_1.trc | 25 +++++++++++++++++++++ test/data/test_CanMessage_V2_0_BUS1.trc | 28 ++++++++++++++++++++++++ test/data/test_CanMessage_V2_1.trc | 29 +++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 test/data/test_CanMessage_V1_0_BUS1.trc create mode 100644 test/data/test_CanMessage_V1_1.trc create mode 100644 test/data/test_CanMessage_V2_0_BUS1.trc create mode 100644 test/data/test_CanMessage_V2_1.trc diff --git a/test/data/test_CanMessage_V1_0_BUS1.trc b/test/data/test_CanMessage_V1_0_BUS1.trc new file mode 100644 index 000000000..8985db188 --- /dev/null +++ b/test/data/test_CanMessage_V1_0_BUS1.trc @@ -0,0 +1,28 @@ +;########################################################################## +; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_0_BUS1.trc +; +; CAN activities imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +; Start time: 18.12.2021 14:28:07.062 +; PCAN-Net: N/A +; Generated by PEAK-Converter Version 2.2.4.136 +; +; Columns description: +; ~~~~~~~~~~~~~~~~~~~~~ +; +-current number in actual sample +; | +time offset of message (ms) +; | | +ID of message (hex) +; | | | +data length code +; | | | | +data bytes (hex) ... +; | | | | | +;----+- ---+--- ----+--- + -+ -- -- ... + 1) 17535 00000100 8 00 00 00 00 00 00 00 00 + 2) 17540 FFFFFFFF 4 00 00 00 08 -- -- -- -- BUSHEAVY + 3) 17700 00000100 8 00 00 00 00 00 00 00 00 + 4) 17873 00000100 8 00 00 00 00 00 00 00 00 + 5) 19295 0000 8 00 00 00 00 00 00 00 00 + 6) 19500 0000 8 00 00 00 00 00 00 00 00 + 7) 19705 0000 8 00 00 00 00 00 00 00 00 + 8) 20592 00000100 8 00 00 00 00 00 00 00 00 + 9) 20798 00000100 8 00 00 00 00 00 00 00 00 + 10) 20956 00000100 8 00 00 00 00 00 00 00 00 + 11) 21097 00000100 8 00 00 00 00 00 00 00 00 diff --git a/test/data/test_CanMessage_V1_1.trc b/test/data/test_CanMessage_V1_1.trc new file mode 100644 index 000000000..5a02cd59b --- /dev/null +++ b/test/data/test_CanMessage_V1_1.trc @@ -0,0 +1,25 @@ +;$FILEVERSION=1.1 +;$STARTTIME=44548.6028595139 +; +; Start time: 18.12.2021 14:28:07.062.0 +; Generated by PCAN-View v5.0.0.814 +; +; Message Number +; | Time Offset (ms) +; | | Type +; | | | ID (hex) +; | | | | Data Length +; | | | | | Data Bytes (hex) ... +; | | | | | | +;---+-- ----+---- --+-- ----+--- + -+ -- -- -- -- -- -- -- + 1) 17535.4 Tx 00000100 8 00 00 00 00 00 00 00 00 + 2) 17540.3 Warng FFFFFFFF 4 00 00 00 08 BUSHEAVY + 3) 17700.3 Tx 00000100 8 00 00 00 00 00 00 00 00 + 4) 17873.8 Tx 00000100 8 00 00 00 00 00 00 00 00 + 5) 19295.4 Tx 0000 8 00 00 00 00 00 00 00 00 + 6) 19500.6 Tx 0000 8 00 00 00 00 00 00 00 00 + 7) 19705.2 Tx 0000 8 00 00 00 00 00 00 00 00 + 8) 20592.7 Tx 00000100 8 00 00 00 00 00 00 00 00 + 9) 20798.6 Tx 00000100 8 00 00 00 00 00 00 00 00 + 10) 20956.0 Tx 00000100 8 00 00 00 00 00 00 00 00 + 11) 21097.1 Tx 00000100 8 00 00 00 00 00 00 00 00 diff --git a/test/data/test_CanMessage_V2_0_BUS1.trc b/test/data/test_CanMessage_V2_0_BUS1.trc new file mode 100644 index 000000000..cf2384df0 --- /dev/null +++ b/test/data/test_CanMessage_V2_0_BUS1.trc @@ -0,0 +1,28 @@ +;$FILEVERSION=2.0 +;$STARTTIME=44548.6028595139 +;$COLUMNS=N,O,T,I,d,l,D +; +; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V2_0_BUS1.trc +; Start time: 18.12.2021 14:28:07.062.001 +; Generated by PEAK-Converter Version 2.2.4.136 +; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +;------------------------------------------------------------------------------- +; Connection Bit rate +; N/A N/A +;------------------------------------------------------------------------------- +; Message Time Type ID Rx/Tx +; Number Offset | [hex] | Data Length +; | [ms] | | | | Data [hex] ... +; | | | | | | | +;---+-- ------+------ +- --+----- +- +- +- -- -- -- -- -- -- -- + 1 17535.400 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 2 17540.300 ST Rx 00 00 00 08 + 3 17700.300 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 4 17873.800 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 5 19295.400 DT 0000 Tx 8 00 00 00 00 00 00 00 00 + 6 19500.600 DT 0000 Tx 8 00 00 00 00 00 00 00 00 + 7 19705.200 DT 0000 Tx 8 00 00 00 00 00 00 00 00 + 8 20592.700 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 9 20798.600 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 10 20956.000 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 + 11 21097.100 DT 00000100 Tx 8 00 00 00 00 00 00 00 00 diff --git a/test/data/test_CanMessage_V2_1.trc b/test/data/test_CanMessage_V2_1.trc new file mode 100644 index 000000000..55ceefaf1 --- /dev/null +++ b/test/data/test_CanMessage_V2_1.trc @@ -0,0 +1,29 @@ +;$FILEVERSION=2.1 +;$STARTTIME=44548.6028595139 +;$COLUMNS=N,O,T,B,I,d,R,L,D +; +; C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V2_1.trc +; Start time: 18.12.2021 14:28:07.062.001 +; Generated by PEAK-Converter Version 2.2.4.136 +; Data imported from C:\Users\User\Desktop\python-can\test\data\test_CanMessage_V1_1.trc +;------------------------------------------------------------------------------- +; Bus Name Connection Protocol +; N/A N/A N/A N/A +;------------------------------------------------------------------------------- +; Message Time Type ID Rx/Tx +; Number Offset | Bus [hex] | Reserved +; | [ms] | | | | | Data Length Code +; | | | | | | | | Data [hex] ... +; | | | | | | | | | +;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- + 1 17535.400 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 2 17540.300 ST 1 - Rx - 4 00 00 00 08 + 3 17700.300 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 4 17873.800 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 5 19295.400 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00 + 6 19500.600 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00 + 7 19705.200 DT 1 0000 Tx - 8 00 00 00 00 00 00 00 00 + 8 20592.700 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 9 20798.600 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 10 20956.000 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 + 11 21097.100 DT 1 00000100 Tx - 8 00 00 00 00 00 00 00 00 From 56ddf2a077f410eed4df6db3d205592a3c5aadf1 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:01:16 +0100 Subject: [PATCH 076/120] Use binary mode for write to ensure correct line ending on all platforms --- can/io/trc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 1bb9f51bc..573994534 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -138,12 +138,12 @@ class TRCWriter(BaseIOHandler, Listener): def __init__(self, file, channel: int = 1): """ :param file: a path-like object or as file-like object to write to - If this is a file-like object, is has to opened in text - write mode, not binary write mode. + If this is a file-like object, is has to opened in binary + write mode, not text write mode. :param channel: a default channel to use when the message does not have a channel set """ - super(TRCWriter, self).__init__(file, mode="w") + super(TRCWriter, self).__init__(file, mode="wb") self.channel = channel if type(file) is str: self.filepath = os.path.abspath(file) @@ -169,7 +169,7 @@ def write_header(self, timestamp): ), filepath=self.filepath, starttime=starttime.strftime("%d.%m.%Y %H:%M:%S"), - ) + ).encode('ascii') ) self.header_written = True @@ -177,7 +177,7 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: if not self.header_written: self.write_header(timestamp) - self.file.write(f"{message}\n") + self.file.write(f"{message}\r\n".encode('ascii')) def on_message_received(self, msg: Message) -> None: From b31e11baa24a555ae67a55ba2028efa7978f0a6c Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:04:07 +0100 Subject: [PATCH 077/120] Add handler for file write --- can/io/trc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index 573994534..5ab8a03c4 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -154,6 +154,9 @@ def __init__(self, file, channel: int = 1): self.msgnr = 0 self.first_timestamp = 0.0 + def _write_line(self, line): + self.file.write((line + '\r\n').encode('ascii')) + def write_header(self, timestamp): # write start of file header reftime = datetime(year=1899, month=12, day=30) @@ -177,7 +180,7 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: if not self.header_written: self.write_header(timestamp) - self.file.write(f"{message}\r\n".encode('ascii')) + self._write_line(message) def on_message_received(self, msg: Message) -> None: From 7d9a55d49edf49fcb527e5137b0f3375b2c98d2a Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:10:09 +0100 Subject: [PATCH 078/120] Move some header lines to specific write function --- can/io/trc.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 5ab8a03c4..a88ba0564 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -31,9 +31,6 @@ # - filepath: full path to log file # - starttime: starttime of trace formatted as %d.%m.%Y %H:%M:%S FMT_TRC_HEADER_VER_2_1 = """\ -;$FILEVERSION=2.1 -;$STARTTIME={days}.{milliseconds} -;$COLUMNS=N,O,T,B,I,d,R,L,D ; ; {filepath} ; @@ -157,6 +154,15 @@ def __init__(self, file, channel: int = 1): def _write_line(self, line): self.file.write((line + '\r\n').encode('ascii')) + def _write_header_V2_1(self, header_time, starttime): + milliseconds = int( + (header_time.seconds * 1000) + (header_time.microseconds / 1000) + ) + + self._write_line(';$FILEVERSION=2.1') + self._write_line(f';$STARTTIME={header_time.days}.{milliseconds}') + self._write_line(';$COLUMNS=N,O,T,B,I,d,R,L,D') + def write_header(self, timestamp): # write start of file header reftime = datetime(year=1899, month=12, day=30) @@ -164,6 +170,8 @@ def write_header(self, timestamp): starttime = datetime.now() + timedelta(seconds=timestamp) header_time = starttime - reftime + self._write_header_V2_1(header_time, starttime) + self.file.write( FMT_TRC_HEADER_VER_2_1.format( days=header_time.days, From c8c40a511f4599e17c6b6b105f1aeb1d6117a96e Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:22:58 +0100 Subject: [PATCH 079/120] Move lines to function --- can/io/trc.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index a88ba0564..831e2eed8 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -31,15 +31,6 @@ # - filepath: full path to log file # - starttime: starttime of trace formatted as %d.%m.%Y %H:%M:%S FMT_TRC_HEADER_VER_2_1 = """\ -; -; {filepath} -; -; Start time: {starttime} -; Generated by python-can TRCWriter -;------------------------------------------------------------------------------- -; Bus Name Connection Protocol -; N/A N/A N/A N/A -;------------------------------------------------------------------------------- ; Message Time Type ID Rx/Tx ; Number Offset | Bus [hex] | Reserved ; | [ms] | | | | | Data Length Code @@ -162,6 +153,15 @@ def _write_header_V2_1(self, header_time, starttime): self._write_line(';$FILEVERSION=2.1') self._write_line(f';$STARTTIME={header_time.days}.{milliseconds}') self._write_line(';$COLUMNS=N,O,T,B,I,d,R,L,D') + self._write_line(';') + self._write_line(f'; {self.filepath}') + self._write_line(';') + self._write_line(f'; Start time: {starttime}') + self._write_line('; Generated by python-can TRCWriter') + self._write_line(';-------------------------------------------------------------------------------') + self._write_line('; Bus Name Connection Protocol') + self._write_line('; N/A N/A N/A N/A') + self._write_line(';-------------------------------------------------------------------------------') def write_header(self, timestamp): # write start of file header From 8592e126e0bf5a3edc83cef08d629a783bc71148 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:25:52 +0100 Subject: [PATCH 080/120] Move write of file header to class --- can/io/trc.py | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 831e2eed8..98b3e443c 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -23,23 +23,6 @@ logger = logging.getLogger("can.io.trc") -# Format for trace file header. -# -# Fields: -# - days: Number of days that have passed since 30. December 1899 -# - milliseconds: milliseconds since 00:00:00 of day -# - filepath: full path to log file -# - starttime: starttime of trace formatted as %d.%m.%Y %H:%M:%S -FMT_TRC_HEADER_VER_2_1 = """\ -; Message Time Type ID Rx/Tx -; Number Offset | Bus [hex] | Reserved -; | [ms] | | | | | Data Length Code -; | | | | | | | | Data [hex] ... -; | | | | | | | | | -;---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- -""" - - class TRCReader(BaseIOHandler): """ Iterator of CAN messages from a TRC logging file. @@ -162,6 +145,12 @@ def _write_header_V2_1(self, header_time, starttime): self._write_line('; Bus Name Connection Protocol') self._write_line('; N/A N/A N/A N/A') self._write_line(';-------------------------------------------------------------------------------') + self._write_line('; Message Time Type ID Rx/Tx') + self._write_line('; Number Offset | Bus [hex] | Reserved') + self._write_line('; | [ms] | | | | | Data Length Code') + self._write_line('; | | | | | | | | Data [hex] ...') + self._write_line('; | | | | | | | | |') + self._write_line(';---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --') def write_header(self, timestamp): # write start of file header @@ -171,17 +160,6 @@ def write_header(self, timestamp): header_time = starttime - reftime self._write_header_V2_1(header_time, starttime) - - self.file.write( - FMT_TRC_HEADER_VER_2_1.format( - days=header_time.days, - milliseconds=int( - (header_time.seconds * 1000) + (header_time.microseconds / 1000) - ), - filepath=self.filepath, - starttime=starttime.strftime("%d.%m.%Y %H:%M:%S"), - ).encode('ascii') - ) self.header_written = True def log_event(self, message: str, timestamp: Optional[float] = None) -> None: From b434e38a829758ea60955422b9c13f604ac62842 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 14:27:39 +0000 Subject: [PATCH 081/120] Format code with black --- can/io/trc.py | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 98b3e443c..4946a8acd 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -126,31 +126,39 @@ def __init__(self, file, channel: int = 1): self.first_timestamp = 0.0 def _write_line(self, line): - self.file.write((line + '\r\n').encode('ascii')) + self.file.write((line + "\r\n").encode("ascii")) def _write_header_V2_1(self, header_time, starttime): milliseconds = int( (header_time.seconds * 1000) + (header_time.microseconds / 1000) ) - self._write_line(';$FILEVERSION=2.1') - self._write_line(f';$STARTTIME={header_time.days}.{milliseconds}') - self._write_line(';$COLUMNS=N,O,T,B,I,d,R,L,D') - self._write_line(';') - self._write_line(f'; {self.filepath}') - self._write_line(';') - self._write_line(f'; Start time: {starttime}') - self._write_line('; Generated by python-can TRCWriter') - self._write_line(';-------------------------------------------------------------------------------') - self._write_line('; Bus Name Connection Protocol') - self._write_line('; N/A N/A N/A N/A') - self._write_line(';-------------------------------------------------------------------------------') - self._write_line('; Message Time Type ID Rx/Tx') - self._write_line('; Number Offset | Bus [hex] | Reserved') - self._write_line('; | [ms] | | | | | Data Length Code') - self._write_line('; | | | | | | | | Data [hex] ...') - self._write_line('; | | | | | | | | |') - self._write_line(';---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --') + self._write_line(";$FILEVERSION=2.1") + self._write_line(f";$STARTTIME={header_time.days}.{milliseconds}") + self._write_line(";$COLUMNS=N,O,T,B,I,d,R,L,D") + self._write_line(";") + self._write_line(f"; {self.filepath}") + self._write_line(";") + self._write_line(f"; Start time: {starttime}") + self._write_line("; Generated by python-can TRCWriter") + self._write_line( + ";-------------------------------------------------------------------------------" + ) + self._write_line("; Bus Name Connection Protocol") + self._write_line("; N/A N/A N/A N/A") + self._write_line( + ";-------------------------------------------------------------------------------" + ) + self._write_line("; Message Time Type ID Rx/Tx") + self._write_line("; Number Offset | Bus [hex] | Reserved") + self._write_line("; | [ms] | | | | | Data Length Code") + self._write_line( + "; | | | | | | | | Data [hex] ..." + ) + self._write_line("; | | | | | | | | |") + self._write_line( + ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --" + ) def write_header(self, timestamp): # write start of file header From 0ed3ce4ebe93796ce3bf8d23e5a740fe52805389 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:32:23 +0100 Subject: [PATCH 082/120] Use new function for better code readability --- can/io/trc.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 4946a8acd..4ada32f0b 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -128,6 +128,10 @@ def __init__(self, file, channel: int = 1): def _write_line(self, line): self.file.write((line + "\r\n").encode("ascii")) + def _write_lines(self, lines: list): + for line in lines: + self._write_line(line) + def _write_header_V2_1(self, header_time, starttime): milliseconds = int( (header_time.seconds * 1000) + (header_time.microseconds / 1000) @@ -149,16 +153,14 @@ def _write_header_V2_1(self, header_time, starttime): self._write_line( ";-------------------------------------------------------------------------------" ) - self._write_line("; Message Time Type ID Rx/Tx") - self._write_line("; Number Offset | Bus [hex] | Reserved") - self._write_line("; | [ms] | | | | | Data Length Code") - self._write_line( - "; | | | | | | | | Data [hex] ..." - ) - self._write_line("; | | | | | | | | |") - self._write_line( - ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --" - ) + self._write_lines([ + "; Message Time Type ID Rx/Tx", + "; Number Offset | Bus [hex] | Reserved", + "; | [ms] | | | | | Data Length Code", + "; | | | | | | | | Data [hex] ...", + "; | | | | | | | | |", + ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --", + ]) def write_header(self, timestamp): # write start of file header From 795a7b671bb993e2769b5cc7afcf4b48f4086e97 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 14:32:57 +0000 Subject: [PATCH 083/120] Format code with black --- can/io/trc.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 4ada32f0b..1f2c78ae7 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -153,14 +153,16 @@ def _write_header_V2_1(self, header_time, starttime): self._write_line( ";-------------------------------------------------------------------------------" ) - self._write_lines([ - "; Message Time Type ID Rx/Tx", - "; Number Offset | Bus [hex] | Reserved", - "; | [ms] | | | | | Data Length Code", - "; | | | | | | | | Data [hex] ...", - "; | | | | | | | | |", - ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --", - ]) + self._write_lines( + [ + "; Message Time Type ID Rx/Tx", + "; Number Offset | Bus [hex] | Reserved", + "; | [ms] | | | | | Data Length Code", + "; | | | | | | | | Data [hex] ...", + "; | | | | | | | | |", + ";---+-- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --", + ] + ) def write_header(self, timestamp): # write start of file header From 5fdf153fb011db17c0e710457f1545cbbbdf1272 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:40:53 +0100 Subject: [PATCH 084/120] Add enum for file versions --- can/io/trc.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/can/io/trc.py b/can/io/trc.py index 1f2c78ae7..ac7d6dc13 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -11,6 +11,7 @@ from typing import cast, Any, Generator, IO, Optional from datetime import datetime, timedelta +from enum import Enum import os import logging @@ -23,6 +24,16 @@ logger = logging.getLogger("can.io.trc") +class TRCFileVersion(Enum): + UNKNOWN = 0 + V1_0 = 100 + V1_1 = 101 + V1_2 = 102 + V1_3 = 103 + V2_0 = 200 + V2_1 = 201 + + class TRCReader(BaseIOHandler): """ Iterator of CAN messages from a TRC logging file. @@ -35,6 +46,7 @@ def __init__(self, file): read mode, not binary read mode. """ super(TRCReader, self).__init__(file, mode="r") + self.file_version = TRCFileVersion.UNKNOWN if not self.file: raise ValueError("The given file cannot be None") @@ -124,6 +136,7 @@ def __init__(self, file, channel: int = 1): self.header_written = False self.msgnr = 0 self.first_timestamp = 0.0 + self.file_version = TRCFileVersion.V2_1 def _write_line(self, line): self.file.write((line + "\r\n").encode("ascii")) From 943a246c5eb2cb8ade6eb7cf3a062da1744a27fb Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:50:50 +0100 Subject: [PATCH 085/120] Handle text io streams correctly --- can/io/trc.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index ac7d6dc13..3f919a81f 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -12,6 +12,7 @@ from typing import cast, Any, Generator, IO, Optional from datetime import datetime, timedelta from enum import Enum +from io import TextIOWrapper import os import logging @@ -130,17 +131,26 @@ def __init__(self, file, channel: int = 1): self.channel = channel if type(file) is str: self.filepath = os.path.abspath(file) + self._write_line = self._write_line_binary + elif type(file) is TextIOWrapper: + self.filepath = "Unknown" + self._write_line = self._write_line_text + logger.warning("TRCWriter: Text mode io can result in wrong line endings") else: self.filepath = "Unknown" + self._write_line = self._write_line_binary self.header_written = False self.msgnr = 0 self.first_timestamp = 0.0 self.file_version = TRCFileVersion.V2_1 - def _write_line(self, line): + def _write_line_binary(self, line): self.file.write((line + "\r\n").encode("ascii")) + def _write_line_text(self, line): + self.file.write(line + "\r\n") + def _write_lines(self, lines: list): for line in lines: self._write_line(line) From 57150848806a9fb84eda6f7604225b46f557203b Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:54:35 +0100 Subject: [PATCH 086/120] send line ending setting to logger --- can/io/trc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/io/trc.py b/can/io/trc.py index 3f919a81f..337e7d5ad 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -136,6 +136,7 @@ def __init__(self, file, channel: int = 1): self.filepath = "Unknown" self._write_line = self._write_line_text logger.warning("TRCWriter: Text mode io can result in wrong line endings") + logger.debug(f"TRCWriter: Text mode io line ending setting: {file.newlines}") else: self.filepath = "Unknown" self._write_line = self._write_line_binary From d826c069313b934f7a127436c2d3b252d94bffe7 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 14:55:25 +0000 Subject: [PATCH 087/120] Format code with black --- can/io/trc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index 337e7d5ad..1ba72be69 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -136,7 +136,9 @@ def __init__(self, file, channel: int = 1): self.filepath = "Unknown" self._write_line = self._write_line_text logger.warning("TRCWriter: Text mode io can result in wrong line endings") - logger.debug(f"TRCWriter: Text mode io line ending setting: {file.newlines}") + logger.debug( + f"TRCWriter: Text mode io line ending setting: {file.newlines}" + ) else: self.filepath = "Unknown" self._write_line = self._write_line_binary From 3040d6371d4e6dd8fc85d3db5ac946b9358300b1 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 16:00:32 +0100 Subject: [PATCH 088/120] Add check for file version in writer --- can/io/trc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index 1ba72be69..dfb95fbe4 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -197,7 +197,10 @@ def write_header(self, timestamp): starttime = datetime.now() + timedelta(seconds=timestamp) header_time = starttime - reftime - self._write_header_V2_1(header_time, starttime) + if self.file_version == TRCFileVersion.V2_1: + self._write_header_V2_1(header_time, starttime) + else: + raise NotImplementedError("File format is not supported") self.header_written = True def log_event(self, message: str, timestamp: Optional[float] = None) -> None: From a691afe35ca1b881100ef6dc52755e759fd90b51 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 16:07:16 +0100 Subject: [PATCH 089/120] Add TRCFileVersion as export --- can/__init__.py | 2 +- can/io/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 596ab5114..666cb8fb1 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -30,7 +30,7 @@ from .io import CanutilsLogReader, CanutilsLogWriter from .io import CSVWriter, CSVReader from .io import SqliteWriter, SqliteReader -from .io import TRCReader, TRCWriter +from .io import TRCReader, TRCWriter, TRCFileVersion from .util import set_logging_level diff --git a/can/io/__init__.py b/can/io/__init__.py index c514c7176..752158f4a 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -14,4 +14,4 @@ from .csv import CSVWriter, CSVReader from .sqlite import SqliteReader, SqliteWriter from .printer import Printer -from .trc import TRCReader, TRCWriter +from .trc import TRCReader, TRCWriter, TRCFileVersion From 808a4074494eba8617eccb2dcfd035d31e99a5ad Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 16:07:43 +0100 Subject: [PATCH 090/120] Add test for wrong file version --- test/logformats_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/logformats_test.py b/test/logformats_test.py index 7c11c9cc8..13f99388d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -827,6 +827,12 @@ def test_can_message(self): actual = self._read_log_file("test_CanMessage.trc") self.assertMessagesEqual(actual, expected_messages) + def test_not_supported_version(self): + with self.assertRaises(NotImplementedError): + writer = can.TRCWriter("test.trc") + writer.file_version = can.TRCFileVersion.UNKNOWN + writer.on_message_received(can.Message()) + # this excludes the base class from being executed as a test case itself del ReaderWriterTest From 1fd5f5a27f6deb9a6970c58ef642b2f445251761 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 16:24:15 +0100 Subject: [PATCH 091/120] Add format and header for version 1 trace file format --- can/io/trc.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index dfb95fbe4..6a8c5be07 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -118,6 +118,9 @@ class TRCWriter(BaseIOHandler, Listener): FORMAT_MESSAGE = ( "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" ) + FORMAT_MESSAGE_V1_0 = ( + "{msgnr:>6}) {time:7.0f} {id:>8} {dlc:<1} {data}" + ) def __init__(self, file, channel: int = 1): """ @@ -158,6 +161,26 @@ def _write_lines(self, lines: list): for line in lines: self._write_line(line) + def _write_header_V1_0(self, starttime): + self._write_line( + ";##########################################################################" + ) + self._write_line(f"; {self.filepath}") + self._write_line(";") + self._write_line("; Generated by python-can TRCWriter") + self._write_line(f"; Start time: {starttime}") + self._write_line("; PCAN-Net: N/A") + self._write_line(";") + self._write_line("; Columns description:") + self._write_line("; ~~~~~~~~~~~~~~~~~~~~~") + self._write_line("; +-current number in actual sample") + self._write_line("; | +time offset of message (ms)") + self._write_line("; | | +ID of message (hex)") + self._write_line("; | | | +data length code") + self._write_line("; | | | | +data bytes (hex) ...") + self._write_line("; | | | | |") + self._write_line(";----+- ---+--- ----+--- + -+ -- -- ...") + def _write_header_V2_1(self, header_time, starttime): milliseconds = int( (header_time.seconds * 1000) + (header_time.microseconds / 1000) @@ -197,7 +220,9 @@ def write_header(self, timestamp): starttime = datetime.now() + timedelta(seconds=timestamp) header_time = starttime - reftime - if self.file_version == TRCFileVersion.V2_1: + if self.file_version == TRCFileVersion.V1_0: + self._write_header_V1_0(header_time) + elif self.file_version == TRCFileVersion.V2_1: self._write_header_V2_1(header_time, starttime) else: raise NotImplementedError("File format is not supported") From 90de8cb0524783256ef9b2f57146e9db14481585 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 16:39:00 +0100 Subject: [PATCH 092/120] Use correct format according to selected version --- can/io/trc.py | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 6a8c5be07..185cf5e12 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -150,6 +150,7 @@ def __init__(self, file, channel: int = 1): self.msgnr = 0 self.first_timestamp = 0.0 self.file_version = TRCFileVersion.V2_1 + self._format_message = self._format_message_init def _write_line_binary(self, line): self.file.write((line + "\r\n").encode("ascii")) @@ -213,6 +214,37 @@ def _write_header_V2_1(self, header_time, starttime): ] ) + def _format_message_by_format(self, msg, channel): + if msg.is_extended_id: + arb_id = f"{msg.arbitration_id:07X}" + else: + arb_id = f"{msg.arbitration_id:04X}" + + data = [f"{byte:02X}" for byte in msg.data] + + serialized = self._msg_fmt_string.format( + msgnr=self.msgnr, + time=(msg.timestamp - self.first_timestamp) * 1000, + channel=channel, + id=arb_id, + dir="Rx" if msg.is_rx else "Tx", + dlc=msg.dlc, + data=" ".join(data), + ) + return serialized + + def _format_message_init(self, msg, channel): + if self.file_version == TRCFileVersion.V1_0: + self._format_message = self._format_message_by_format + self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 + elif self.file_version == TRCFileVersion.V2_1: + self._format_message = self._format_message_by_format + self._msg_fmt_string = self.FORMAT_MESSAGE + else: + raise NotImplementedError("File format is not supported") + + return self._format_message(msg, channel) + def write_header(self, timestamp): # write start of file header reftime = datetime(year=1899, month=12, day=30) @@ -243,13 +275,6 @@ def on_message_received(self, msg: Message) -> None: if msg.is_remote_frame: logger.warning("TRCWriter: Logging remote frames is not implemented") return - else: - data = [f"{byte:02X}" for byte in msg.data] - - if msg.is_extended_id: - arb_id = f"{msg.arbitration_id:07X}" - else: - arb_id = f"{msg.arbitration_id:04X}" channel = channel2int(msg.channel) if channel is None: @@ -262,14 +287,6 @@ def on_message_received(self, msg: Message) -> None: logger.warning("TRCWriter: Logging CAN FD is not implemented") return else: - serialized = self.FORMAT_MESSAGE.format( - msgnr=self.msgnr, - time=(msg.timestamp - self.first_timestamp) * 1000, - channel=channel, - id=arb_id, - dir="Rx" if msg.is_rx else "Tx", - dlc=msg.dlc, - data=" ".join(data), - ) + serialized = self._format_message(msg, channel) self.msgnr += 1 self.log_event(serialized, msg.timestamp) From 71ca2be6bc15d977ef4a540dbc0ef8d7043f5006 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 18 Dec 2021 15:39:38 +0000 Subject: [PATCH 093/120] Format code with black --- can/io/trc.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 185cf5e12..2223e5789 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -118,9 +118,7 @@ class TRCWriter(BaseIOHandler, Listener): FORMAT_MESSAGE = ( "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" ) - FORMAT_MESSAGE_V1_0 = ( - "{msgnr:>6}) {time:7.0f} {id:>8} {dlc:<1} {data}" - ) + FORMAT_MESSAGE_V1_0 = "{msgnr:>6}) {time:7.0f} {id:>8} {dlc:<1} {data}" def __init__(self, file, channel: int = 1): """ @@ -223,14 +221,14 @@ def _format_message_by_format(self, msg, channel): data = [f"{byte:02X}" for byte in msg.data] serialized = self._msg_fmt_string.format( - msgnr=self.msgnr, - time=(msg.timestamp - self.first_timestamp) * 1000, - channel=channel, - id=arb_id, - dir="Rx" if msg.is_rx else "Tx", - dlc=msg.dlc, - data=" ".join(data), - ) + msgnr=self.msgnr, + time=(msg.timestamp - self.first_timestamp) * 1000, + channel=channel, + id=arb_id, + dir="Rx" if msg.is_rx else "Tx", + dlc=msg.dlc, + data=" ".join(data), + ) return serialized def _format_message_init(self, msg, channel): From be2b1cb376661c64a4f69956d58620e5be9f8b18 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:12:13 +0100 Subject: [PATCH 094/120] Introduce handler method _parse_line --- can/io/trc.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 2223e5789..d9818c729 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -68,18 +68,21 @@ def _extract_header(self): else: return line - def _parse_msg(self, line): - cols = line.split() + def _parse_msg(self, cols): + msg = Message() + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(cols[4], 16) + msg.is_extended_id = len(cols[4]) > 4 + msg.channel = int(cols[3]) + msg.dlc = int(cols[7]) + msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) + msg.is_rx = cols[5] == "Rx" + return msg + + def _parse_line(self, line): try: - msg = Message() - msg.timestamp = float(cols[1]) / 1000 - msg.arbitration_id = int(cols[4], 16) - msg.is_extended_id = len(cols[4]) > 4 - msg.channel = int(cols[3]) - msg.dlc = int(cols[7]) - msg.data = bytearray([int(cols[i + 8], 16) for i in range(msg.dlc)]) - msg.is_rx = cols[5] == "Rx" - return msg + cols = line.split() + self._parse_msg(cols) except IndexError: logger.warning(f"TRCReader: Failed to parse message '{line}'") @@ -89,7 +92,7 @@ def __iter__(self) -> Generator[Message, None, None]: first_line = self._extract_header() if first_line is not None: - msg = self._parse_msg(first_line) + msg = self._parse_line(first_line) if msg is not None: yield msg @@ -99,7 +102,7 @@ def __iter__(self) -> Generator[Message, None, None]: # Comment line continue - msg = self._parse_msg(temp) + msg = self._parse_line(temp) if msg is not None: yield msg From be849d4554ad3c71035738becb187b3901495801 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:12:54 +0100 Subject: [PATCH 095/120] Add debug print --- can/io/trc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/io/trc.py b/can/io/trc.py index d9818c729..2b4f306af 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -80,6 +80,7 @@ def _parse_msg(self, cols): return msg def _parse_line(self, line): + logger.debug(f"TRCReader: Parse '{line}'") try: cols = line.split() self._parse_msg(cols) From 9f18311f7c8aaa4a49bc28a04b6283437ee0644a Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:13:22 +0100 Subject: [PATCH 096/120] Skip empty lines --- can/io/trc.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/can/io/trc.py b/can/io/trc.py index 2b4f306af..717639d39 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -103,6 +103,10 @@ def __iter__(self) -> Generator[Message, None, None]: # Comment line continue + if len(temp) == 0: + # Empty line + continue + msg = self._parse_line(temp) if msg is not None: yield msg From aadfa2bb6dc281a456fb27582be6a117634728d3 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:14:10 +0100 Subject: [PATCH 097/120] Add check for type before eval message --- can/io/trc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index 717639d39..a3b27f728 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -83,9 +83,14 @@ def _parse_line(self, line): logger.debug(f"TRCReader: Parse '{line}'") try: cols = line.split() - self._parse_msg(cols) + dtype = cols[2] + if dtype == 'DT': + return self._parse_msg(cols) + else: + return None except IndexError: logger.warning(f"TRCReader: Failed to parse message '{line}'") + return None def __iter__(self) -> Generator[Message, None, None]: # This is guaranteed to not be None since we raise ValueError in __init__ From 75cf8608a67322156a16512847e312198d63c31c Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:15:56 +0100 Subject: [PATCH 098/120] Print info on unsupported types --- can/io/trc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/io/trc.py b/can/io/trc.py index a3b27f728..5e4848f48 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -87,6 +87,7 @@ def _parse_line(self, line): if dtype == 'DT': return self._parse_msg(cols) else: + logger.info(f"TRCReader: Unsupported type '{dtype}'") return None except IndexError: logger.warning(f"TRCReader: Failed to parse message '{line}'") From 723d7ee2d893a812c50e7aefe1d1786e64689480 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:23:23 +0100 Subject: [PATCH 099/120] Add test for new test data files trc format --- test/logformats_test.py | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/logformats_test.py b/test/logformats_test.py index 13f99388d..44611287d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -14,6 +14,7 @@ import gzip import logging import unittest +from parameterized import parameterized import tempfile import os from abc import abstractmethod, ABCMeta @@ -827,6 +828,50 @@ def test_can_message(self): actual = self._read_log_file("test_CanMessage.trc") self.assertMessagesEqual(actual, expected_messages) + @parameterized.expand( + [ + ("V2_1", "test_CanMessage_V2_1.trc"), + ] + ) + def test_can_message_versions(self, name, filename): + with self.subTest(name): + def msg_std(timestamp): + return can.Message( + timestamp=timestamp, + arbitration_id=0x000, + is_extended_id=False, + is_rx=False, + channel=1, + dlc=8, + data=[0, 0, 0, 0, 0, 0, 0, 0], + ) + + def msg_ext(timestamp): + return can.Message( + timestamp=timestamp, + arbitration_id=0x100, + is_extended_id=True, + is_rx=False, + channel=1, + dlc=8, + data=[0, 0, 0, 0, 0, 0, 0, 0], + ) + + expected_messages = [ + msg_ext(17.5354), + msg_ext(17.7003), + msg_ext(17.8738), + msg_std(19.2954), + msg_std(19.5006), + msg_std(19.7052), + msg_ext(20.5927), + msg_ext(20.7986), + msg_ext(20.9560), + msg_ext(21.0971), + ] + actual = self._read_log_file(filename) + self.assertMessagesEqual(actual, expected_messages) + def test_not_supported_version(self): with self.assertRaises(NotImplementedError): writer = can.TRCWriter("test.trc") From e0a8be61c76f3548cc67f70f6bfa2eec36581208 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:34:26 +0100 Subject: [PATCH 100/120] Implement file version reading --- can/io/trc.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 5e4848f48..621c21324 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -52,15 +52,17 @@ def __init__(self, file): if not self.file: raise ValueError("The given file cannot be None") - self.file_version = None - def _extract_header(self): for line in self.file: line = line.strip() if line.startswith(";$FILEVERSION"): logger.debug(f"TRCReader: Found file version '{line}'") try: - self.file_version = line.split("=")[1] + file_version = line.split("=")[1] + if file_version == "2.1": + self.file_version = TRCFileVersion.V2_1 + else: + self.file_version = TRCFileVersion.UNKNOWN except IndexError: logger.debug("TRCReader: Failed to parse version") elif line.startswith(";"): From a09f4d75f4d4b16e910bccabe2716e43cbcf94fa Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:46:35 +0100 Subject: [PATCH 101/120] More flexible implementation of line parsing for different versions --- can/io/trc.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 621c21324..e034af7b0 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -68,9 +68,18 @@ def _extract_header(self): elif line.startswith(";"): continue else: - return line + break - def _parse_msg(self, cols): + if self.file_version == TRCFileVersion.UNKNOWN: + raise NotImplementedError("File version not fully implemented for reading") + elif self.file_version == TRCFileVersion.V2_1: + self._parse_cols = self._parse_cols_V2_1 + else: + raise NotImplementedError("File version not fully implemented for reading") + + return line + + def _parse_msg_V2_1(self, cols): msg = Message() msg.timestamp = float(cols[1]) / 1000 msg.arbitration_id = int(cols[4], 16) @@ -81,16 +90,19 @@ def _parse_msg(self, cols): msg.is_rx = cols[5] == "Rx" return msg + def _parse_cols_V2_1(self, cols): + dtype = cols[2] + if dtype == 'DT': + return self._parse_msg_V2_1(cols) + else: + logger.info(f"TRCReader: Unsupported type '{dtype}'") + return None + def _parse_line(self, line): logger.debug(f"TRCReader: Parse '{line}'") try: cols = line.split() - dtype = cols[2] - if dtype == 'DT': - return self._parse_msg(cols) - else: - logger.info(f"TRCReader: Unsupported type '{dtype}'") - return None + return self._parse_cols(cols) except IndexError: logger.warning(f"TRCReader: Failed to parse message '{line}'") return None From 5f40f377aca823fd7c71ce438f1cdf7f7feae5c1 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:51:45 +0100 Subject: [PATCH 102/120] Implement Version 1 trace file parsing --- can/io/trc.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index e034af7b0..444117e27 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -71,7 +71,12 @@ def _extract_header(self): break if self.file_version == TRCFileVersion.UNKNOWN: - raise NotImplementedError("File version not fully implemented for reading") + logger.info( + "TRCReader: No file version was found, so version 1.0 is assumed" + ) + self._parse_cols = self._parse_msg_V1_0 + elif self.file_version == TRCFileVersion.V1_0: + self._parse_cols = self._parse_msg_V1_0 elif self.file_version == TRCFileVersion.V2_1: self._parse_cols = self._parse_cols_V2_1 else: @@ -79,6 +84,21 @@ def _extract_header(self): return line + def _parse_msg_V1_0(self, cols): + arbit_id = cols[2] + if arbit_id == "FFFFFFFF": + logger.info("TRCReader: Dropping bus info line") + return None + + msg = Message() + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(arbit_id, 16) + msg.is_extended_id = len(arbit_id) > 4 + msg.channel = 1 + msg.dlc = int(cols[3]) + msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) + return msg + def _parse_msg_V2_1(self, cols): msg = Message() msg.timestamp = float(cols[1]) / 1000 From 84dfc8815534cd44f3383207c82f6c9077c8cefa Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 13:56:57 +0100 Subject: [PATCH 103/120] Add test case for Version 1.0 trc file --- test/logformats_test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 44611287d..7ed24bf2d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -165,6 +165,7 @@ def tearDown(self): os.remove(self.test_file_name) del self.test_file_name + @unittest.skip("not implemented") def test_path_like_explicit_stop(self): """testing with path-like and explicit stop() call""" @@ -830,32 +831,37 @@ def test_can_message(self): @parameterized.expand( [ - ("V2_1", "test_CanMessage_V2_1.trc"), + ("V1_0", "test_CanMessage_V1_0_BUS1.trc", False), + ("V2_1", "test_CanMessage_V2_1.trc", True), ] ) - def test_can_message_versions(self, name, filename): + def test_can_message_versions(self, name, filename, is_rx_support): with self.subTest(name): def msg_std(timestamp): - return can.Message( + msg = can.Message( timestamp=timestamp, arbitration_id=0x000, is_extended_id=False, - is_rx=False, channel=1, dlc=8, data=[0, 0, 0, 0, 0, 0, 0, 0], ) + if is_rx_support: + msg.is_rx = False + return msg def msg_ext(timestamp): - return can.Message( + msg = can.Message( timestamp=timestamp, arbitration_id=0x100, is_extended_id=True, - is_rx=False, channel=1, dlc=8, data=[0, 0, 0, 0, 0, 0, 0, 0], ) + if is_rx_support: + msg.is_rx = False + return msg expected_messages = [ msg_ext(17.5354), From c21da85cd4fcddf2fb6dc2d8202a61d41cd1ec89 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 12:57:30 +0000 Subject: [PATCH 104/120] Format code with black --- can/io/trc.py | 2 +- test/logformats_test.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index 444117e27..a70144038 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -112,7 +112,7 @@ def _parse_msg_V2_1(self, cols): def _parse_cols_V2_1(self, cols): dtype = cols[2] - if dtype == 'DT': + if dtype == "DT": return self._parse_msg_V2_1(cols) else: logger.info(f"TRCReader: Unsupported type '{dtype}'") diff --git a/test/logformats_test.py b/test/logformats_test.py index 7ed24bf2d..67ac47ecb 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -837,6 +837,7 @@ def test_can_message(self): ) def test_can_message_versions(self, name, filename, is_rx_support): with self.subTest(name): + def msg_std(timestamp): msg = can.Message( timestamp=timestamp, From b857239f4c4a472187cc5bed365fa17f73c8ccef Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 14:07:57 +0100 Subject: [PATCH 105/120] Implement parsing for version 1.1 trace files --- can/io/trc.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index a70144038..b1e18d00c 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -59,7 +59,9 @@ def _extract_header(self): logger.debug(f"TRCReader: Found file version '{line}'") try: file_version = line.split("=")[1] - if file_version == "2.1": + if file_version == "1.1": + self.file_version = TRCFileVersion.V1_1 + elif file_version == "2.1": self.file_version = TRCFileVersion.V2_1 else: self.file_version = TRCFileVersion.UNKNOWN @@ -77,6 +79,8 @@ def _extract_header(self): self._parse_cols = self._parse_msg_V1_0 elif self.file_version == TRCFileVersion.V1_0: self._parse_cols = self._parse_msg_V1_0 + elif self.file_version == TRCFileVersion.V1_1: + self._parse_cols = self._parse_cols_V1_1 elif self.file_version == TRCFileVersion.V2_1: self._parse_cols = self._parse_cols_V2_1 else: @@ -99,6 +103,19 @@ def _parse_msg_V1_0(self, cols): msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) return msg + def _parse_msg_V1_1(self, cols): + arbit_id = cols[3] + + msg = Message() + msg.timestamp = float(cols[1]) / 1000 + msg.arbitration_id = int(arbit_id, 16) + msg.is_extended_id = len(arbit_id) > 4 + msg.channel = 1 + msg.dlc = int(cols[4]) + msg.data = bytearray([int(cols[i + 5], 16) for i in range(msg.dlc)]) + msg.is_rx = cols[2] == "Rx" + return msg + def _parse_msg_V2_1(self, cols): msg = Message() msg.timestamp = float(cols[1]) / 1000 @@ -110,6 +127,14 @@ def _parse_msg_V2_1(self, cols): msg.is_rx = cols[5] == "Rx" return msg + def _parse_cols_V1_1(self, cols): + dtype = cols[2] + if dtype == "Tx" or dtype == "Rx": + return self._parse_msg_V1_1(cols) + else: + logger.info(f"TRCReader: Unsupported type '{dtype}'") + return None + def _parse_cols_V2_1(self, cols): dtype = cols[2] if dtype == "DT": From e23ac7e32ae13bf6d06932e2b80bec2fdab9975e Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 14:08:58 +0100 Subject: [PATCH 106/120] Add test for version 1.1 reading --- test/logformats_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/logformats_test.py b/test/logformats_test.py index 67ac47ecb..9bdc478af 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -832,6 +832,7 @@ def test_can_message(self): @parameterized.expand( [ ("V1_0", "test_CanMessage_V1_0_BUS1.trc", False), + ("V1_1", "test_CanMessage_V1_1.trc", True), ("V2_1", "test_CanMessage_V2_1.trc", True), ] ) From 6545ee374c2fd6badc39d0144e9a7f1fa5906359 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 14:17:45 +0100 Subject: [PATCH 107/120] Add test case for version 1.0 trace files --- test/logformats_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/logformats_test.py b/test/logformats_test.py index 67ac47ecb..f3ff80584 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -886,6 +886,20 @@ def test_not_supported_version(self): writer.on_message_received(can.Message()) +class TestTrcFileFormatV1_0(TestTrcFileFormat): + """Tests can.TRCWriter and can.TRCReader with file version 1.0""" + + @staticmethod + def Writer(filename): + writer = can.TRCWriter(filename) + writer.file_version = can.TRCFileVersion.V1_0 + return writer + + def _setup_instance(self): + super()._setup_instance() + self.writer_constructor = TestTrcFileFormatV1_0.Writer + + # this excludes the base class from being executed as a test case itself del ReaderWriterTest From 695002761e3bbbbd2319533a21d7536bc963e2c8 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 19 Dec 2021 14:24:38 +0100 Subject: [PATCH 108/120] Avoid multi test runs with same input and output by separate generic tests from file version tests --- test/logformats_test.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index f3ff80584..a4b020a7d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -785,8 +785,14 @@ def test_not_crashes_with_file(self): printer(message) -class TestTrcFileFormat(ReaderWriterTest): - """Tests can.TRCWriter and can.TRCReader""" +class TestTrcFileFormatBase(ReaderWriterTest): + """ + Base class for Tests with can.TRCWriter and can.TRCReader + + .. note:: + This class is prevented from being executed as a test + case itself by a *del* statement in at the end of the file. + """ def _setup_instance(self): super()._setup_instance_helper( @@ -806,6 +812,10 @@ def _read_log_file(self, filename, **kwargs): with can.TRCReader(logfile, **kwargs) as reader: return list(reader) + +class TestTrcFileFormatGen(TestTrcFileFormatBase): + """Generic tests for can.TRCWriter and can.TRCReader with different file versions""" + def test_can_message(self): expected_messages = [ can.Message( @@ -886,7 +896,7 @@ def test_not_supported_version(self): writer.on_message_received(can.Message()) -class TestTrcFileFormatV1_0(TestTrcFileFormat): +class TestTrcFileFormatV1_0(TestTrcFileFormatBase): """Tests can.TRCWriter and can.TRCReader with file version 1.0""" @staticmethod @@ -902,6 +912,7 @@ def _setup_instance(self): # this excludes the base class from being executed as a test case itself del ReaderWriterTest +del TestTrcFileFormatBase if __name__ == "__main__": From bce0ed23bffb5cb3d3130219f5f47558db9913c7 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sat, 15 Jan 2022 23:12:24 +0100 Subject: [PATCH 109/120] Correct first timestamp --- can/io/trc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index b1e18d00c..fb6d03332 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -219,7 +219,7 @@ def __init__(self, file, channel: int = 1): self.header_written = False self.msgnr = 0 - self.first_timestamp = 0.0 + self.first_timestamp = None self.file_version = TRCFileVersion.V2_1 self._format_message = self._format_message_init @@ -319,7 +319,6 @@ def _format_message_init(self, msg, channel): def write_header(self, timestamp): # write start of file header reftime = datetime(year=1899, month=12, day=30) - self.first_timestamp = timestamp starttime = datetime.now() + timedelta(seconds=timestamp) header_time = starttime - reftime @@ -338,6 +337,8 @@ def log_event(self, message: str, timestamp: Optional[float] = None) -> None: self._write_line(message) def on_message_received(self, msg: Message) -> None: + if self.first_timestamp is None: + self.first_timestamp = msg.timestamp if msg.is_error_frame: logger.warning("TRCWriter: Logging error frames is not implemented") From fe982ef282431d40a2b5917a41d581f11f14ad4c Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 23 Jan 2022 20:57:25 +0100 Subject: [PATCH 110/120] Add type information for file attribute --- can/io/trc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index fb6d03332..688941dbe 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -9,7 +9,7 @@ Version 1.1 will be implemented as it is most commonly used """ # noqa -from typing import cast, Any, Generator, IO, Optional +from typing import cast, Any, Generator, IO, Optional, TextIO from datetime import datetime, timedelta from enum import Enum from io import TextIOWrapper @@ -40,6 +40,8 @@ class TRCReader(BaseIOHandler): Iterator of CAN messages from a TRC logging file. """ + file: TextIO + def __init__(self, file): """ :param file: a path-like object or as file-like object to read from @@ -188,6 +190,8 @@ class TRCWriter(BaseIOHandler, Listener): If the first message does not have a timestamp, it is set to zero. """ + file: TextIO + FORMAT_MESSAGE = ( "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" ) From 220d2825f7f6aa7937e35a05fc49a83596a66bf8 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 23 Jan 2022 21:00:26 +0100 Subject: [PATCH 111/120] Add type definitions for init function --- can/io/trc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 688941dbe..8f00d0cd1 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -20,6 +20,7 @@ from ..listener import Listener from ..util import channel2int from .generic import BaseIOHandler +from ..typechecking import AcceptedIOType logger = logging.getLogger("can.io.trc") @@ -42,7 +43,7 @@ class TRCReader(BaseIOHandler): file: TextIO - def __init__(self, file): + def __init__(self, file: AcceptedIOType) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text @@ -197,7 +198,7 @@ class TRCWriter(BaseIOHandler, Listener): ) FORMAT_MESSAGE_V1_0 = "{msgnr:>6}) {time:7.0f} {id:>8} {dlc:<1} {data}" - def __init__(self, file, channel: int = 1): + def __init__(self, file: AcceptedIOType, channel: int = 1) -> None: """ :param file: a path-like object or as file-like object to write to If this is a file-like object, is has to opened in binary From c43cbb8960c27b4c315b61233e0221ab5c8cad22 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 23 Jan 2022 21:07:36 +0100 Subject: [PATCH 112/120] Add type for first_timestamp --- can/io/trc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/io/trc.py b/can/io/trc.py index 8f00d0cd1..dc5d2e8f6 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -192,6 +192,7 @@ class TRCWriter(BaseIOHandler, Listener): """ file: TextIO + first_timestamp: Optional[float] FORMAT_MESSAGE = ( "{msgnr:>7} {time:13.3f} DT {channel:>2} {id:>8} {dir:>2} - {dlc:<4} {data}" From d7f605a78b7c45c46f9fb2bbe67393549cee077d Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 23 Jan 2022 21:15:15 +0100 Subject: [PATCH 113/120] Add info for return types --- can/io/trc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index dc5d2e8f6..855c2f3b4 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -229,17 +229,17 @@ def __init__(self, file: AcceptedIOType, channel: int = 1) -> None: self.file_version = TRCFileVersion.V2_1 self._format_message = self._format_message_init - def _write_line_binary(self, line): + def _write_line_binary(self, line) -> None: self.file.write((line + "\r\n").encode("ascii")) - def _write_line_text(self, line): + def _write_line_text(self, line) -> None: self.file.write(line + "\r\n") - def _write_lines(self, lines: list): + def _write_lines(self, lines: list) -> None: for line in lines: self._write_line(line) - def _write_header_V1_0(self, starttime): + def _write_header_V1_0(self, starttime) -> None: self._write_line( ";##########################################################################" ) @@ -259,7 +259,7 @@ def _write_header_V1_0(self, starttime): self._write_line("; | | | | |") self._write_line(";----+- ---+--- ----+--- + -+ -- -- ...") - def _write_header_V2_1(self, header_time, starttime): + def _write_header_V2_1(self, header_time, starttime) -> None: milliseconds = int( (header_time.seconds * 1000) + (header_time.microseconds / 1000) ) @@ -322,7 +322,7 @@ def _format_message_init(self, msg, channel): return self._format_message(msg, channel) - def write_header(self, timestamp): + def write_header(self, timestamp) -> None: # write start of file header reftime = datetime(year=1899, month=12, day=30) starttime = datetime.now() + timedelta(seconds=timestamp) From 52641596b2aa5ba3ba5668d70d9a35de009dcfa6 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 23 Jan 2022 21:15:25 +0100 Subject: [PATCH 114/120] Drop type casting for file. Should already be done in init --- can/io/trc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 855c2f3b4..05d934984 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -156,8 +156,6 @@ def _parse_line(self, line): return None def __iter__(self) -> Generator[Message, None, None]: - # This is guaranteed to not be None since we raise ValueError in __init__ - self.file = cast(IO[Any], self.file) first_line = self._extract_header() if first_line is not None: From b38674211004ed7f2f3b4f65a820acd403158d56 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Sun, 23 Jan 2022 21:24:34 +0100 Subject: [PATCH 115/120] Add type info --- can/io/trc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 05d934984..ff924a800 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -227,17 +227,17 @@ def __init__(self, file: AcceptedIOType, channel: int = 1) -> None: self.file_version = TRCFileVersion.V2_1 self._format_message = self._format_message_init - def _write_line_binary(self, line) -> None: + def _write_line_binary(self, line: str) -> None: self.file.write((line + "\r\n").encode("ascii")) - def _write_line_text(self, line) -> None: + def _write_line_text(self, line: str) -> None: self.file.write(line + "\r\n") def _write_lines(self, lines: list) -> None: for line in lines: self._write_line(line) - def _write_header_V1_0(self, starttime) -> None: + def _write_header_V1_0(self, starttime: float) -> None: self._write_line( ";##########################################################################" ) @@ -257,7 +257,7 @@ def _write_header_V1_0(self, starttime) -> None: self._write_line("; | | | | |") self._write_line(";----+- ---+--- ----+--- + -+ -- -- ...") - def _write_header_V2_1(self, header_time, starttime) -> None: + def _write_header_V2_1(self, header_time: float, starttime: float) -> None: milliseconds = int( (header_time.seconds * 1000) + (header_time.microseconds / 1000) ) @@ -320,7 +320,7 @@ def _format_message_init(self, msg, channel): return self._format_message(msg, channel) - def write_header(self, timestamp) -> None: + def write_header(self, timestamp: float) -> None: # write start of file header reftime = datetime(year=1899, month=12, day=30) starttime = datetime.now() + timedelta(seconds=timestamp) From f1bcd6073f66d2bfa449da7a58fe0ab6e5dcc76c Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 27 Jan 2022 19:58:07 +0100 Subject: [PATCH 116/120] fix some type hints --- can/io/trc.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index ff924a800..e0cfcc8fb 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -9,7 +9,7 @@ Version 1.1 will be implemented as it is most commonly used """ # noqa -from typing import cast, Any, Generator, IO, Optional, TextIO +from typing import Generator, Optional, TextIO from datetime import datetime, timedelta from enum import Enum from io import TextIOWrapper @@ -17,9 +17,8 @@ import logging from ..message import Message -from ..listener import Listener from ..util import channel2int -from .generic import BaseIOHandler +from .generic import FileIOMessageWriter, MessageReader from ..typechecking import AcceptedIOType @@ -36,7 +35,7 @@ class TRCFileVersion(Enum): V2_1 = 201 -class TRCReader(BaseIOHandler): +class TRCReader(MessageReader): """ Iterator of CAN messages from a TRC logging file. """ @@ -180,7 +179,7 @@ def __iter__(self) -> Generator[Message, None, None]: self.stop() -class TRCWriter(BaseIOHandler, Listener): +class TRCWriter(FileIOMessageWriter): """Logs CAN data to text file (.trc). The measurement starts with the timestamp of the first registered message. @@ -237,14 +236,14 @@ def _write_lines(self, lines: list) -> None: for line in lines: self._write_line(line) - def _write_header_V1_0(self, starttime: float) -> None: + def _write_header_V1_0(self, start_time: timedelta) -> None: self._write_line( ";##########################################################################" ) self._write_line(f"; {self.filepath}") self._write_line(";") self._write_line("; Generated by python-can TRCWriter") - self._write_line(f"; Start time: {starttime}") + self._write_line(f"; Start time: {start_time}") self._write_line("; PCAN-Net: N/A") self._write_line(";") self._write_line("; Columns description:") @@ -257,7 +256,7 @@ def _write_header_V1_0(self, starttime: float) -> None: self._write_line("; | | | | |") self._write_line(";----+- ---+--- ----+--- + -+ -- -- ...") - def _write_header_V2_1(self, header_time: float, starttime: float) -> None: + def _write_header_V2_1(self, header_time: timedelta, start_time: datetime) -> None: milliseconds = int( (header_time.seconds * 1000) + (header_time.microseconds / 1000) ) @@ -268,7 +267,7 @@ def _write_header_V2_1(self, header_time: float, starttime: float) -> None: self._write_line(";") self._write_line(f"; {self.filepath}") self._write_line(";") - self._write_line(f"; Start time: {starttime}") + self._write_line(f"; Start time: {start_time}") self._write_line("; Generated by python-can TRCWriter") self._write_line( ";-------------------------------------------------------------------------------" @@ -322,19 +321,19 @@ def _format_message_init(self, msg, channel): def write_header(self, timestamp: float) -> None: # write start of file header - reftime = datetime(year=1899, month=12, day=30) - starttime = datetime.now() + timedelta(seconds=timestamp) - header_time = starttime - reftime + ref_time = datetime(year=1899, month=12, day=30) + start_time = datetime.now() + timedelta(seconds=timestamp) + header_time = start_time - ref_time if self.file_version == TRCFileVersion.V1_0: self._write_header_V1_0(header_time) elif self.file_version == TRCFileVersion.V2_1: - self._write_header_V2_1(header_time, starttime) + self._write_header_V2_1(header_time, start_time) else: raise NotImplementedError("File format is not supported") self.header_written = True - def log_event(self, message: str, timestamp: Optional[float] = None) -> None: + def log_event(self, message: str, timestamp: float) -> None: if not self.header_written: self.write_header(timestamp) From e6bf1c57c720f08c81d8a1c351d253ad043824ea Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Thu, 27 Jan 2022 20:24:38 +0100 Subject: [PATCH 117/120] Always use text read write --- can/io/trc.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index e0cfcc8fb..53b749271 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -199,8 +199,8 @@ class TRCWriter(FileIOMessageWriter): def __init__(self, file: AcceptedIOType, channel: int = 1) -> None: """ :param file: a path-like object or as file-like object to write to - If this is a file-like object, is has to opened in binary - write mode, not text write mode. + If this is a file-like object, is has to opened in text + write mode, not binary write mode. :param channel: a default channel to use when the message does not have a channel set """ @@ -208,17 +208,14 @@ def __init__(self, file: AcceptedIOType, channel: int = 1) -> None: self.channel = channel if type(file) is str: self.filepath = os.path.abspath(file) - self._write_line = self._write_line_binary elif type(file) is TextIOWrapper: self.filepath = "Unknown" - self._write_line = self._write_line_text logger.warning("TRCWriter: Text mode io can result in wrong line endings") logger.debug( f"TRCWriter: Text mode io line ending setting: {file.newlines}" ) else: self.filepath = "Unknown" - self._write_line = self._write_line_binary self.header_written = False self.msgnr = 0 @@ -226,10 +223,7 @@ def __init__(self, file: AcceptedIOType, channel: int = 1) -> None: self.file_version = TRCFileVersion.V2_1 self._format_message = self._format_message_init - def _write_line_binary(self, line: str) -> None: - self.file.write((line + "\r\n").encode("ascii")) - - def _write_line_text(self, line: str) -> None: + def _write_line(self, line: str) -> None: self.file.write(line + "\r\n") def _write_lines(self, lines: list) -> None: From 5c21862fba2b6fb572b4595589b39a5567c817cc Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 31 Jan 2022 19:48:15 +0100 Subject: [PATCH 118/120] Update types for initialization --- can/io/trc.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/can/io/trc.py b/can/io/trc.py index 53b749271..2ec4cb5af 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -9,7 +9,7 @@ Version 1.1 will be implemented as it is most commonly used """ # noqa -from typing import Generator, Optional, TextIO +from typing import Generator, Optional, Union, TextIO from datetime import datetime, timedelta from enum import Enum from io import TextIOWrapper @@ -19,7 +19,7 @@ from ..message import Message from ..util import channel2int from .generic import FileIOMessageWriter, MessageReader -from ..typechecking import AcceptedIOType +from ..typechecking import StringPathLike logger = logging.getLogger("can.io.trc") @@ -42,7 +42,10 @@ class TRCReader(MessageReader): file: TextIO - def __init__(self, file: AcceptedIOType) -> None: + def __init__( + self, + file: Union[StringPathLike, TextIO], + ) -> None: """ :param file: a path-like object or as file-like object to read from If this is a file-like object, is has to opened in text @@ -196,7 +199,11 @@ class TRCWriter(FileIOMessageWriter): ) FORMAT_MESSAGE_V1_0 = "{msgnr:>6}) {time:7.0f} {id:>8} {dlc:<1} {data}" - def __init__(self, file: AcceptedIOType, channel: int = 1) -> None: + def __init__( + self, + file: Union[StringPathLike, TextIO], + channel: int = 1, + ) -> None: """ :param file: a path-like object or as file-like object to write to If this is a file-like object, is has to opened in text From 02e190dcbf7c4343911a94a7e247e65bced12004 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 31 Jan 2022 19:48:35 +0100 Subject: [PATCH 119/120] Use text io mode by default --- can/io/trc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/trc.py b/can/io/trc.py index 2ec4cb5af..25ba0f5c6 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -211,7 +211,7 @@ def __init__( :param channel: a default channel to use when the message does not have a channel set """ - super(TRCWriter, self).__init__(file, mode="wb") + super(TRCWriter, self).__init__(file, mode="w") self.channel = channel if type(file) is str: self.filepath = os.path.abspath(file) From ed9bb835e00427b0e3a48aa71ff2f277d90acca1 Mon Sep 17 00:00:00 2001 From: Peter Kessen Date: Mon, 31 Jan 2022 19:48:52 +0100 Subject: [PATCH 120/120] Enable test again --- test/logformats_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 865a30a36..b47ca8c87 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -165,7 +165,6 @@ def tearDown(self): os.remove(self.test_file_name) del self.test_file_name - @unittest.skip("not implemented") def test_path_like_explicit_stop(self): """testing with path-like and explicit stop() call"""