diff --git a/.appveyor.yml b/.appveyor.yml index 500c71320..aa7c12293 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -15,7 +15,7 @@ install: - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% # We need to install the python-can library itself including the dependencies - - "python -m pip install .[test,neovi]" + - "python -m pip install .[test,neovi,mf4]" build: off diff --git a/.travis.yml b/.travis.yml index 8ecb4abd5..bdf79c016 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,12 +16,9 @@ python: # PyPy: - pypy3 -env: - - install: - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - - travis_retry pip install .[test] + - travis_retry pip install .[test,mf4] script: - | @@ -74,6 +71,7 @@ jobs: # -a Write all files # -n nitpicky - python -m sphinx -an doc build + - stage: linter name: "Linter Checks" python: "3.7" @@ -109,6 +107,7 @@ jobs: - travis_retry pip install -r requirements-lint.txt script: - black --check --verbose . + - stage: deploy name: "PyPi Deployment" python: "3.7" diff --git a/README.rst b/README.rst index 0214f2d4b..539267a37 100644 --- a/README.rst +++ b/README.rst @@ -67,7 +67,7 @@ Features - receiving, sending, and periodically sending messages - normal and extended arbitration IDs - limited `CAN FD `__ support -- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), CSV, SQLite and Canutils log +- many different loggers and readers supporting playback: ASC (CANalyzer format), BLF (Binary Logging Format by Vector), CSV, SQLite, Canutils log and MF4 (Measurement Data Format v4 by ASAM) - efficient in-kernel or in-hardware filtering of messages on supported interfaces - bus configuration reading from file or environment variables - CLI tools for working with CAN buses (see the `docs `__) diff --git a/can/__init__.py b/can/__init__.py index 3c1ac8d75..a12a19c1f 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -29,6 +29,10 @@ class CanError(IOError): from .io import CanutilsLogReader, CanutilsLogWriter from .io import CSVWriter, CSVReader from .io import SqliteWriter, SqliteReader +try: + from .io import MF4Writer, MF4Reader +except ImportError: + pass from .util import set_logging_level diff --git a/can/io/__init__.py b/can/io/__init__.py index 3797d4b5d..dd2224c93 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -16,3 +16,7 @@ from .csv import CSVWriter, CSVReader from .sqlite import SqliteReader, SqliteWriter from .printer import Printer +try: + from .mf4 import MF4Writer, MF4Reader +except ImportError: + pass diff --git a/can/io/mf4.py b/can/io/mf4.py new file mode 100644 index 000000000..28d9bba1e --- /dev/null +++ b/can/io/mf4.py @@ -0,0 +1,420 @@ +# coding: utf-8 + +""" +Contains handling of MF4 logging files. + +MF4 files represent Measurement Data Format (MDF) version 4 as specified by +the ASAM MDF standard (see https://www.asam.net/standards/detail/mdf/) + +""" + +from __future__ import absolute_import + +from datetime import datetime +from pathlib import Path +import logging + +from ..message import Message +from ..listener import Listener +from ..util import channel2int +from .generic import BaseIOHandler + +try: + from asammdf import MDF, Signal + import numpy as np + + CAN_MSG_EXT = 0x80000000 + CAN_ID_MASK = 0x1FFFFFFF + + STD_DTYPE = np.dtype( + [ + ('CAN_DataFrame.BusChannel', '= 3.6 and has to be installed as an extra with for example ``pip install python-can[mf4]``. + +.. note:: Channels will be converted to integers. + +.. note:: MF4Writer does not suppport append mode. + + +.. autoclass:: can.MF4Writer + :members: + +The MDF format is very flexible regarding the internal structure and it is used to handle data from multiple sources, not just CAN bus logging. +MDF4Writer will always create a fixed internal file structure where there will be three channel groups (for standard, error and remote frames). +Using this fixed file structure allows for a simple implementation of MDF4Writer and MF4Reader classes. +Therefor MF4Reader can only replay files created with MF4Writer. + +The following class can be used to read messages from MF4 file: + +.. autoclass:: can.MF4Reader + :members: diff --git a/examples/receive_all.py b/examples/receive_all.py index ced8841bc..544b086ef 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -8,7 +8,7 @@ def receive_all(): - bus = can.interface.Bus(bustype="pcan", channel="PCAN_USBBUS1", bitrate=250000) + bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) diff --git a/setup.py b/setup.py index 2b1bf2296..3982bfbd9 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ # Dependencies extras_require = { "seeedstudio": ["pyserial>=3.0"], + "mf4": ["asammdf>=5.5.0", "numpy>=1.16.0", "Cython"], "serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12", "filelock"], } diff --git a/test/logformats_test.py b/test/logformats_test.py index 9983b0ecb..23ca62e21 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -142,6 +142,7 @@ def test_path_like_explicit_stop(self): writer = self.writer_constructor(self.test_file_name) self._write_all(writer) self._ensure_fsync(writer) + writer.stop() if hasattr(writer.file, "closed"): self.assertTrue(writer.file.closed) @@ -412,6 +413,27 @@ def _setup_instance(self): ) +try: + from can import MF4Writer, MF4Reader + + class TestMF4FileFormat(ReaderWriterTest): + """Tests can.MF4Writer and can.MF4Reader""" + + __test__ = True + + def _setup_instance(self): + super(TestMF4FileFormat, self)._setup_instance_helper( + MF4Writer, MF4Reader, + binary_file=True, + check_comments=False, + preserves_channel=False, + adds_default_channel=0, + ) + +except ImportError: + pass + + class TestSqliteDatabaseFormat(ReaderWriterTest): """Tests can.SqliteWriter and can.SqliteReader"""