From a43d32237d8490a83873fe076eac5653fbda4f79 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 11 Feb 2018 11:51:59 +1100 Subject: [PATCH 001/168] Handle error frame messages in CanutilsLogWriter and CanutilsLogReader. Closes #217 (cherry picked from commit 61f27e1) --- test/logformats_test.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 3cc494adb..64f27cc2f 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -112,7 +112,7 @@ def _test_writer_and_reader(test_case, writer_constructor, reader_constructor, s for i, (read, original) in enumerate(zip(read_messages, original_messages)): try: test_case.assertEqual(read, original) - test_case.assertAlmostEqual(read.timestamp, original.timestamp) + test_case.assertAlmostEqual(read.timestamp, original.timestamp, places=6) except Exception as exception: # attach the index exception.args += ("test failed at index #{}".format(i), ) @@ -133,7 +133,6 @@ class TestCanutilsLog(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.CanutilsLogWriter, can.CanutilsLogReader, - check_error_frames=False, # TODO this should get fixed, see Issue #217 check_comments=False) @@ -142,7 +141,6 @@ class TestAscFileFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.ASCWriter, can.ASCReader, - check_error_frames=False, # TODO this should get fixed, see Issue #218 check_comments=True) @@ -192,7 +190,6 @@ class TestBlfFileFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.BLFWriter, can.BLFReader, - sleep_time=None, check_comments=False) def test_reader(self): From 7af1021f9617b12b92f2600ddd593aa04ae2341f Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 17 Feb 2018 14:05:12 +1100 Subject: [PATCH 002/168] Fix issues in USB2CAN with Python3 Closes #219 --- can/interfaces/usb2can/usb2canInterface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 9d9a68fc6..57c2b330c 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -114,14 +114,14 @@ def __init__(self, channel, *args, **kwargs): br = kwargs["bitrate"] # max rate is 1000 kbps - baudrate = max(1000, int(br/1000)) + baudrate = min(1000, int(br/1000)) # set default value else: baudrate = 500 connector = format_connection_string(deviceID, baudrate) - self.handle = self.can.open(connector, enable_flags) + self.handle = self.can.open(connector.encode('utf-8'), enable_flags) def send(self, msg, timeout=None): tx = message_convert_tx(msg) From 8e30e6097af1e5eda7435a18b38d26a0596d0a4a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 11 Feb 2018 11:39:48 +1100 Subject: [PATCH 003/168] Support error frame messages in ASCWriter and ASCReader. Closes #218 (cherry picked from commit e17207c) --- can/io/asc.py | 22 +++++++++++++--------- test/data/example_data.py | 10 ++++------ test/logformats_test.py | 4 ++-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index d69436cf5..9ebecb96d 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -93,8 +93,8 @@ def __iter__(self): class ASCWriter(Listener): """Logs CAN data to an ASCII log file (.asc)""" - LOG_STRING = "{time: 9.4f} {channel} {id:<15} Rx {dtype} {data}\n" - EVENT_STRING = "{time: 9.4f} {message}\n" + LOG_STRING = "{time: 9.4f} {channel} {id:<15} Rx {dtype} {data}\n" + EVENT_STRING = "{time: 9.4f} {message}\n" def __init__(self, filename, channel=1): now = datetime.now().strftime("%a %b %m %I:%M:%S %p %Y") @@ -120,15 +120,19 @@ def log_event(self, message, timestamp=None): logger.debug("ASCWriter: ignoring empty message") return - timestamp = (timestamp or time.time()) + if timestamp is None: + timestamp = time.time() + if timestamp >= self.started: timestamp -= self.started line = self.EVENT_STRING.format(time=timestamp, message=message) + if not self.log_file.closed: self.log_file.write(line) def on_message_received(self, msg): + if msg.is_error_frame: self.log_event("{} ErrorFrame".format(self.channel), msg.timestamp) return @@ -139,18 +143,18 @@ def on_message_received(self, msg): else: dtype = "d {}".format(msg.dlc) data = ["{:02X}".format(byte) for byte in msg.data] + arb_id = "{:X}".format(msg.arbitration_id) - if msg.id_type: - arb_id = arb_id + "x" - timestamp = msg.timestamp - if timestamp >= self.started: - timestamp -= self.started + if msg.is_extended_id: + arb_id += "x" channel = msg.channel if isinstance(msg.channel, int) else self.channel - line = self.LOG_STRING.format(time=timestamp, + + line = self.LOG_STRING.format(time=msg.timestamp, channel=channel, id=arb_id, dtype=dtype, data=" ".join(data)) + if not self.log_file.closed: self.log_file.write(line) diff --git a/test/data/example_data.py b/test/data/example_data.py index bc097f755..6d54d683f 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -9,7 +9,6 @@ from can import Message - # make tests more reproducible random.seed(13339115) @@ -73,12 +72,12 @@ TEST_MESSAGES_REMOTE_FRAMES = [ Message( - arbitration_id=0xDADADA, extended_id=True, is_remote_frame=False, + arbitration_id=0xDADADA, extended_id=True, is_remote_frame=True, timestamp=TEST_TIME + .165, data=[1, 2, 3, 4, 5, 6, 7, 8] ), Message( - arbitration_id=0x123, extended_id=False, is_remote_frame=False, + arbitration_id=0x123, extended_id=False, is_remote_frame=True, timestamp=TEST_TIME + .365, data=[254, 255] ), @@ -124,6 +123,5 @@ def generate_message(arbitration_id): Generates a new message with the given ID, some random data and a non-extended ID. """ - data = [random.randrange(0, 2 ** 8 - 1) for _ in range(8)] - msg = Message(arbitration_id=arbitration_id, data=data, extended_id=False) - return msg + data = bytes([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) + return Message(arbitration_id=arbitration_id, data=data, extended_id=False) diff --git a/test/logformats_test.py b/test/logformats_test.py index 3cc494adb..82c472243 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -112,7 +112,7 @@ def _test_writer_and_reader(test_case, writer_constructor, reader_constructor, s for i, (read, original) in enumerate(zip(read_messages, original_messages)): try: test_case.assertEqual(read, original) - test_case.assertAlmostEqual(read.timestamp, original.timestamp) + test_case.assertAlmostEqual(read.timestamp, original.timestamp, places=6) except Exception as exception: # attach the index exception.args += ("test failed at index #{}".format(i), ) @@ -142,7 +142,7 @@ class TestAscFileFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.ASCWriter, can.ASCReader, - check_error_frames=False, # TODO this should get fixed, see Issue #218 + check_error_frames=True, check_comments=True) From d8942ce75b3ebaf207bad6ea6da759a166ad8d65 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 11 Feb 2018 11:06:49 +1100 Subject: [PATCH 004/168] Use configured bitrate in pcan. Closes #196 (cherry picked from commit bd26d99) --- can/interfaces/pcan/pcan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 8ff8b162c..8647222d9 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -9,6 +9,7 @@ from can.bus import BusABC from can.message import Message from can import CanError +import can import time boottimeEpoch = 0 @@ -82,7 +83,7 @@ def __init__(self, channel, *args, **kwargs): else: self.channel_info = channel - bitrate = kwargs.get('bitrate', 500000) + bitrate = kwargs.get('bitrate', can.rc.get('bitrate', 500000)) pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) hwtype = PCAN_TYPE_ISA From 1cb8ebc9f3e62c555c0b03bbb1ca8d3442b0c802 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 18 Feb 2018 19:45:09 +1100 Subject: [PATCH 005/168] Update version to "2.1.1-dev" --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 71bc0f442..574c2ca38 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -5,7 +5,7 @@ import logging -__version__ = "2.1.0" +__version__ = "2.1.1-dev" log = logging.getLogger('can') From 8fe1b5a74d90e703c6b0a4332fd154e6cc47d027 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 20 Feb 2018 17:16:01 +0100 Subject: [PATCH 006/168] general cleanups of the main modules --- can/CAN.py | 6 +++++- can/__init__.py | 4 ++++ can/broadcastmanager.py | 5 +++-- can/bus.py | 6 ++++-- can/ctypesutil.py | 17 ++++++++++------- can/interface.py | 9 +++++++++ can/listener.py | 9 +++++++++ can/logger.py | 4 ++++ can/message.py | 7 +++++++ can/notifier.py | 7 +++++++ can/player.py | 2 ++ can/util.py | 5 ++++- 12 files changed, 68 insertions(+), 13 deletions(-) diff --git a/can/CAN.py b/can/CAN.py index 8e07e3dac..000b7f8c2 100644 --- a/can/CAN.py +++ b/can/CAN.py @@ -1,9 +1,13 @@ +#!/usr/bin/env python +# coding: utf-8 + """ This module was once the core of python-can, containing implementations of all the major classes in the library, now -however all functionality has been refactored out. This api +however all functionality has been refactored out. This API is left intact for version 2.0 to aide with migration. """ + from __future__ import absolute_import from can.message import Message diff --git a/can/__init__.py b/can/__init__.py index 574c2ca38..05ad4b444 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -1,6 +1,10 @@ +#!/usr/bin/env python +# coding: utf-8 + """ can is an object-orient Controller Area Network interface module. """ + from __future__ import absolute_import import logging diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index f7ff2f2d8..689f2777d 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -1,7 +1,8 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python +# coding: utf-8 + """ Exposes several methods for transmitting cyclic messages. -20/09/13 """ import can diff --git a/can/bus.py b/can/bus.py index f42a4a149..05423c9c7 100644 --- a/can/bus.py +++ b/can/bus.py @@ -1,15 +1,17 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 """ Contains the ABC bus implementation. """ from __future__ import print_function, absolute_import + import abc import logging import threading -from can.broadcastmanager import ThreadBasedCyclicSendTask +from can.broadcastmanager import ThreadBasedCyclicSendTask logger = logging.getLogger(__name__) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index d9166c582..5525f56a8 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -1,6 +1,9 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 -" Common ctypes utils " +""" +This module contains common `ctypes` utils. +""" import binascii import ctypes @@ -11,6 +14,11 @@ __all__ = ['CLibrary', 'HANDLE', 'PHANDLE'] +try: + _LibBase = ctypes.WinDLL +except AttributeError: + _LibBase = ctypes.CDLL + class LibraryMixin: def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): @@ -46,11 +54,6 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None): return symbol -try: - _LibBase = ctypes.WinDLL -except AttributeError: - _LibBase = ctypes.CDLL - class CLibrary_Win32(_LibBase, LibraryMixin): " Basic ctypes.WinDLL derived class + LibraryMixin " diff --git a/can/interface.py b/can/interface.py index 174f03b58..96a22c780 100644 --- a/can/interface.py +++ b/can/interface.py @@ -1,3 +1,12 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module contains the base implementation of `can.Bus` as well +as a list of all avalibale backends and some implemented +CyclicSendTasks. +""" + from __future__ import absolute_import import can diff --git a/can/listener.py b/can/listener.py index daf90827e..d7e8e7ced 100644 --- a/can/listener.py +++ b/can/listener.py @@ -1,6 +1,15 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module contains the implementation of `can.Listener` and some readers. +""" + try: + # Python 3 import queue except ImportError: + # Python 2 import Queue as queue diff --git a/can/logger.py b/can/logger.py index ea5424ee7..bacc0b66f 100644 --- a/can/logger.py +++ b/can/logger.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# coding: utf-8 + """ logger.py logs CAN traffic to the terminal and to a file on disk. @@ -14,7 +16,9 @@ Dynamic Controls 2010 """ + from __future__ import print_function + import datetime import argparse import socket diff --git a/can/message.py b/can/message.py index 4f02476dd..e9806c12f 100644 --- a/can/message.py +++ b/can/message.py @@ -1,3 +1,10 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module contains the implementation of `can.Message`. +""" + import logging logger = logging.getLogger(__name__) diff --git a/can/notifier.py b/can/notifier.py index 4c2c59604..a3eaad6e5 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -1,3 +1,10 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module contains the implementation of `can.Notifier`. +""" + import threading import logging diff --git a/can/player.py b/can/player.py index 8b02a896d..582e5196c 100644 --- a/can/player.py +++ b/can/player.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# coding: utf-8 + """ Replays CAN traffic saved with can.logger back to a CAN bus. diff --git a/can/util.py b/can/util.py index dd1bf67a1..9eeb11e24 100644 --- a/can/util.py +++ b/can/util.py @@ -1,7 +1,10 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python +# coding: utf-8 + """ Utilities and configuration file parsing. """ + from __future__ import absolute_import import can From cdc88cbd7f58d9ed0cb4a899e8671ac753d3cc93 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 20 Feb 2018 17:35:35 +0100 Subject: [PATCH 007/168] general cleanup of the can/io/* modules --- can/io/__init__.py | 3 +++ can/io/asc.py | 9 ++++++++- can/io/blf.py | 8 ++++++-- can/io/csv.py | 25 ++++++++++++++++++++----- can/io/log.py | 39 ++++++++++++++++++++++++--------------- can/io/logger.py | 7 +++++++ can/io/player.py | 7 +++++++ can/io/sqlite.py | 4 ++++ can/io/stdout.py | 20 ++++++++++++++------ 9 files changed, 93 insertions(+), 29 deletions(-) diff --git a/can/io/__init__.py b/can/io/__init__.py index fd2738567..79f187ceb 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# coding: utf-8 + """ Read and Write CAN bus messages using a range of Readers and Writers based off the file extension. diff --git a/can/io/asc.py b/can/io/asc.py index 9ebecb96d..de3edf807 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -1,3 +1,10 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +Contains handling of ASC logging files. +""" + from datetime import datetime import time import logging @@ -13,7 +20,7 @@ class ASCReader(object): """ - Iterator of CAN messages from a ASC Logging File. + Iterator of CAN messages from a ASC logging file. """ def __init__(self, filename): diff --git a/can/io/blf.py b/can/io/blf.py index bdd994d17..3b96a621a 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -1,10 +1,14 @@ +#!/usr/bin/env python +# coding: utf-8 + """ Implements support for BLF (Binary Logging Format) which is a proprietary -CAN log format from Vector Informatik GmbH. +CAN log format from Vector Informatik GmbH (Germany). No official specification of the binary logging format is available. This implementation is based on Toby Lorenz' C++ library "Vector BLF" which is -licenced under GPLv3. https://bitbucket.org/tobylorenz/vector_blf. +licensed under GPLv3. https://bitbucket.org/tobylorenz/vector_blf. + The file starts with a header. The rest is one or more "log containers" which consists of a header and some zlib compressed data, usually up to 128 kB of uncompressed data each. This data contains the actual CAN messages and other diff --git a/can/io/csv.py b/can/io/csv.py index ba3b29a75..768a7a666 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -1,12 +1,28 @@ -from can.listener import Listener +#!/usr/bin/env python +# coding: utf-8 + +""" +This modules contains the handler for CSV (comma seperated values) files. +""" import base64 +from can.listener import Listener + + +# TODO allow other seperators in CSVWriter +# TODO add a CSVReader class CSVWriter(Listener): """Writes a comma separated text file of - timestamp, arbitration id, flags, dlc, data - for each messages received. + + * timestamp, + * arbitration id, + * flags (extended, remote, error), + * dlc and + * data + + for each messages received. Each line is terminated with a '\\n'. """ def __init__(self, filename): @@ -24,10 +40,9 @@ def on_message_received(self, msg): '1' if msg.is_error_frame else '0', str(msg.dlc), base64.b64encode(msg.data).decode('utf8') - ]) + ]) self.csv_file.write(row + '\n') def stop(self): self.csv_file.flush() self.csv_file.close() - diff --git a/can/io/log.py b/can/io/log.py index 3a7816e56..427b96a3b 100644 --- a/can/io/log.py +++ b/can/io/log.py @@ -1,12 +1,23 @@ -from can.listener import Listener -import datetime +#!/usr/bin/env python +# coding: utf-8 + +""" +This module works with CAN data in ASCII log files (*.log). +It is is compatible with "candump -L" from the canutils program +(https://github.com/linux-can/can-utils). +""" + import time +import datetime + from can.message import Message +from can.listener import Listener + -CAN_MSG_EXT = 0x80000000 -CAN_ERR_FLAG = 0x20000000 -CAN_ERR_BUSERROR = 0x00000080 -CAN_ERR_DLC = 8 +CAN_MSG_EXT = 0x80000000 +CAN_ERR_FLAG = 0x20000000 +CAN_ERR_BUSERROR = 0x00000080 +CAN_ERR_DLC = 8 class CanutilsLogReader(object): @@ -18,7 +29,7 @@ class CanutilsLogReader(object): """ def __init__(self, filename): - self.fp = open(filename, "r") + self.fp = open(filename, 'r') def __iter__(self): for line in self.fp: @@ -26,13 +37,13 @@ def __iter__(self): if len(temp) > 0: (timestamp, bus, frame) = temp.split() timestamp = float(timestamp[1:-1]) - (canId, data) = frame.split("#") + (canId, data) = frame.split('#') if len(canId) > 3: isExtended = True else: isExtended = False canId = int(canId, 16) - if len(data) > 0 and data[0].lower() == "r": + if len(data) > 0 and data[0].lower() == 'r': isRemoteFrame = True if len(data) > 1: dlc = int(data[1:]) @@ -56,13 +67,14 @@ def __iter__(self): class CanutilsLogWriter(Listener): - """Logs CAN data to an ASCII log file (.log) - compatible to candump -L """ + """Logs CAN data to an ASCII log file (.log). + This class is is compatible with "candump -L". + """ def __init__(self, filename, channel="vcan0"): self.channel = channel self.started = time.time() - self.log_file = open(filename, "w") + self.log_file = open(filename, 'w') def stop(self): """Stops logging and closes the file.""" @@ -93,6 +105,3 @@ def on_message_received(self, msg): self.log_file.write("(%f) vcan0 %08X#%s\n" % (msg.timestamp, msg.arbitration_id, "".join(data))) else: self.log_file.write("(%f) vcan0 %03X#%s\n" % (msg.timestamp, msg.arbitration_id, "".join(data))) - - - diff --git a/can/io/logger.py b/can/io/logger.py index 450f33c1c..78c93f07b 100755 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -1,3 +1,10 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +See the `Logger` class. +""" + from .asc import ASCWriter from .blf import BLFWriter from .csv import CSVWriter diff --git a/can/io/player.py b/can/io/player.py index a9f3c07c7..22668f4f6 100755 --- a/can/io/player.py +++ b/can/io/player.py @@ -1,4 +1,11 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from __future__ import print_function + import time import logging diff --git a/can/io/sqlite.py b/can/io/sqlite.py index fbb0895a2..454a09f2a 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# coding: utf-8 + """ Implements an SQL database writer and reader for storing CAN messages. @@ -64,6 +67,7 @@ def close(self): # Backward compatibility +# TODO remove in later releases? SqlReader = SqliteReader diff --git a/can/io/stdout.py b/can/io/stdout.py index a85bbe04b..701a77c22 100644 --- a/can/io/stdout.py +++ b/can/io/stdout.py @@ -1,31 +1,39 @@ -from can.listener import Listener +#!/usr/bin/env python +# coding: utf-8 + +""" +This Listener simply prints to stdout / the terminal or a file. +""" + +from __future__ import print_function import logging +from can.listener import Listener + log = logging.getLogger('can.io.stdout') class Printer(Listener): """ The Printer class is a subclass of :class:`~can.Listener` which simply prints - any messages it receives to the terminal. + any messages it receives to the terminal (stdout). :param output_file: An optional file to "print" to. """ def __init__(self, output_file=None): if output_file is not None: - log.info("Creating log file '{}' ".format(output_file)) + log.info('Creating log file "{}"'.format(output_file)) output_file = open(output_file, 'wt') self.output_file = output_file def on_message_received(self, msg): if self.output_file is not None: - self.output_file.write(str(msg) + "\n") + self.output_file.write(str(msg) + '\n') else: print(msg) def stop(self): if self.output_file: - self.output_file.write("\n") + self.output_file.write('\n') self.output_file.close() - From 102be0d5e7877ee61f95b5cb323e6e9c3e432145 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 20 Feb 2018 17:43:40 +0100 Subject: [PATCH 008/168] general cleanup of the can/interfaces/ modules (not yes the ones in child folders) --- can/interfaces/__init__.py | 6 +++-- can/interfaces/interface.py | 0 can/interfaces/iscan.py | 12 ++++++---- can/interfaces/nican.py | 48 ++++++++++++++++++++----------------- can/interfaces/slcan.py | 6 ++++- can/interfaces/virtual.py | 8 +++---- 6 files changed, 47 insertions(+), 33 deletions(-) delete mode 100644 can/interfaces/interface.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 1bd132731..89ff2cdb7 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -1,7 +1,10 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ Interfaces contain low level implementations that interact with CAN hardware. """ + from pkg_resources import iter_entry_points VALID_INTERFACES = set(['kvaser', 'serial', 'pcan', 'socketcan_native', @@ -9,7 +12,6 @@ 'nican', 'iscan', 'vector', 'virtual', 'neovi', 'slcan']) - VALID_INTERFACES.update(set([ interface.name for interface in iter_entry_points('python_can.interface') ])) diff --git a/can/interfaces/interface.py b/can/interfaces/interface.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index bc28818d3..6fd18b49b 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -1,17 +1,21 @@ +#!/usr/bin/env python +# coding: utf-8 + """ Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH. """ + import ctypes import time import logging from can import CanError, BusABC, Message - logger = logging.getLogger(__name__) CanData = ctypes.c_ubyte * 8 + class MessageExStruct(ctypes.Structure): _fields_ = [ ("message_id", ctypes.c_ulong), @@ -148,11 +152,11 @@ class IscanError(CanError): def __init__(self, function, error_code, arguments): super(IscanError, self).__init__() - #: Status code + # Status code self.error_code = error_code - #: Function that failed + # Function that failed self.function = function - #: Arguments passed to function + # Arguments passed to function self.arguments = arguments def __str__(self): diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index e4c7f9844..7d3a59fb3 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# coding: utf-8 + """ NI-CAN interface module. @@ -5,6 +8,7 @@ * http://www.ni.com/pdf/manuals/370289c.pdf * https://github.com/buendiya/NicanPython """ + import ctypes import logging import sys @@ -13,39 +17,39 @@ logger = logging.getLogger(__name__) -NC_SUCCESS = 0 -NC_ERR_TIMEOUT = 1 -TIMEOUT_ERROR_CODE = -1074388991 +NC_SUCCESS = 0 +NC_ERR_TIMEOUT = 1 +TIMEOUT_ERROR_CODE = -1074388991 -NC_DURATION_INFINITE = 0xFFFFFFFF +NC_DURATION_INFINITE = 0xFFFFFFFF -NC_OP_START = 0x80000001 -NC_OP_STOP = 0x80000002 -NC_OP_RESET = 0x80000003 +NC_OP_START = 0x80000001 +NC_OP_STOP = 0x80000002 +NC_OP_RESET = 0x80000003 -NC_FRMTYPE_REMOTE = 1 -NC_FRMTYPE_COMM_ERR = 2 +NC_FRMTYPE_REMOTE = 1 +NC_FRMTYPE_COMM_ERR = 2 -NC_ST_READ_AVAIL = 0x00000001 -NC_ST_WRITE_SUCCESS = 0x00000002 -NC_ST_ERROR = 0x00000010 -NC_ST_WARNING = 0x00000020 +NC_ST_READ_AVAIL = 0x00000001 +NC_ST_WRITE_SUCCESS = 0x00000002 +NC_ST_ERROR = 0x00000010 +NC_ST_WARNING = 0x00000020 -NC_ATTR_BAUD_RATE = 0x80000007 +NC_ATTR_BAUD_RATE = 0x80000007 NC_ATTR_START_ON_OPEN = 0x80000006 -NC_ATTR_READ_Q_LEN = 0x80000013 -NC_ATTR_WRITE_Q_LEN = 0x80000014 -NC_ATTR_CAN_COMP_STD = 0x80010001 -NC_ATTR_CAN_MASK_STD = 0x80010002 -NC_ATTR_CAN_COMP_XTD = 0x80010003 -NC_ATTR_CAN_MASK_XTD = 0x80010004 +NC_ATTR_READ_Q_LEN = 0x80000013 +NC_ATTR_WRITE_Q_LEN = 0x80000014 +NC_ATTR_CAN_COMP_STD = 0x80010001 +NC_ATTR_CAN_MASK_STD = 0x80010002 +NC_ATTR_CAN_COMP_XTD = 0x80010003 +NC_ATTR_CAN_MASK_XTD = 0x80010004 NC_ATTR_LOG_COMM_ERRS = 0x8001000A -NC_FL_CAN_ARBID_XTD = 0x20000000 - +NC_FL_CAN_ARBID_XTD = 0x20000000 CanData = ctypes.c_ubyte * 8 + class RxMessageStruct(ctypes.Structure): _pack_ = 1 _fields_ = [ diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 4f8499028..bd5990eac 100755 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -1,7 +1,10 @@ +#!/usr/bin/env python +# coding: utf-8 + """ Interface for slcan compatible interfaces (win32/linux). -Note Linux users can use slcand/socketcan as well. +Note: Linux users can use slcand/socketcan as well. """ from __future__ import absolute_import @@ -85,6 +88,7 @@ def __init__(self, channel, ttyBaudrate=115200, timeout=1, bitrate=None, **kwarg raise ValueError("Invalid bitrate, choose one of " + (', '.join(self._BITRATES)) + '.') self.open() + super(slcanBus, self).__init__(channel, **kwargs) def recv(self, timeout=None): diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index a51dd10d0..2a7186b77 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -1,11 +1,12 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 """ This module implements an OS and hardware independent virtual CAN interface for testing purposes. Any VirtualBus instances connecting to the same channel -will get the same messages. +and reside in the same process will receive the same messages. """ import logging @@ -14,11 +15,10 @@ import queue except ImportError: import Queue as queue -from can.bus import BusABC +from can.bus import BusABC logger = logging.getLogger(__name__) -#logger.setLevel(logging.DEBUG) # Channels are lists of queues, one for each connection From 23d928e2f89119a538411c70ded15a0d1a8cf263 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 20 Feb 2018 18:36:33 +0100 Subject: [PATCH 009/168] general cleanup of the can/interfaces/*/* modules --- can/interfaces/ics_neovi/__init__.py | 6 + can/interfaces/ics_neovi/neovi_bus.py | 3 + can/interfaces/ixxat/__init__.py | 5 +- can/interfaces/ixxat/canlib.py | 31 +-- can/interfaces/ixxat/constants.py | 91 ++++---- can/interfaces/ixxat/exceptions.py | 5 +- can/interfaces/ixxat/structures.py | 5 +- can/interfaces/kvaser/__init__.py | 6 + can/interfaces/kvaser/argument_parser.py | 6 + can/interfaces/kvaser/canlib.py | 4 +- can/interfaces/kvaser/constants.py | 6 +- can/interfaces/pcan/PCANBasic.py | 215 +++++++----------- can/interfaces/pcan/__init__.py | 6 + can/interfaces/pcan/pcan.py | 19 +- can/interfaces/serial/__init__.py | 6 + can/interfaces/serial/serial_can.py | 7 +- can/interfaces/socketcan/__init__.py | 8 +- can/interfaces/socketcan/socketcan_common.py | 6 +- .../socketcan/socketcan_constants.py | 35 +-- can/interfaces/socketcan/socketcan_ctypes.py | 7 +- can/interfaces/socketcan/socketcan_native.py | 3 +- can/interfaces/usb2can/__init__.py | 6 + can/interfaces/usb2can/serial_selector.py | 15 +- can/interfaces/usb2can/usb2canInterface.py | 8 +- .../usb2can/usb2canabstractionlayer.py | 9 +- can/interfaces/vector/__init__.py | 6 + can/interfaces/vector/canlib.py | 9 +- can/interfaces/vector/exceptions.py | 6 + can/interfaces/vector/vxlapi.py | 8 +- 29 files changed, 307 insertions(+), 240 deletions(-) diff --git a/can/interfaces/ics_neovi/__init__.py b/can/interfaces/ics_neovi/__init__.py index 5b1aa2052..9e9f2b0ba 100644 --- a/can/interfaces/ics_neovi/__init__.py +++ b/can/interfaces/ics_neovi/__init__.py @@ -1 +1,7 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from can.interfaces.ics_neovi.neovi_bus import NeoViBus diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 5e889091c..dd49b04ce 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# coding: utf-8 + """ ICS NeoVi interface module. diff --git a/can/interfaces/ixxat/__init__.py b/can/interfaces/ixxat/__init__.py index 266ddbb35..ab4e1f08c 100644 --- a/can/interfaces/ixxat/__init__.py +++ b/can/interfaces/ixxat/__init__.py @@ -1,6 +1,9 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems + Copyright (C) 2016 Giuseppe Corbelli """ diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 1b098200a..cb0ea9176 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,6 +1,9 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems + Copyright (C) 2016 Giuseppe Corbelli """ @@ -12,11 +15,12 @@ from can import CanError, BusABC from can import Message -from can.interfaces.ixxat import constants, structures from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) from can.ctypesutil import CLibrary, HANDLE, PHANDLE +from can.interfaces.ixxat import constants, structures + from .constants import VCI_MAX_ERRSTRLEN from .exceptions import * @@ -24,9 +28,10 @@ log = logging.getLogger('can.ixxat') -if ((sys.version_info.major == 3) and (sys.version_info.minor >= 3)): +try: + # since Python 3.3 _timer_function = time.perf_counter -else: +except AttributeError: _timer_function = time.clock # Hack to have vciFormatError as a free function, see below @@ -203,18 +208,18 @@ def __check_status(result, function, arguments): CAN_INFO_MESSAGES = { - constants.CAN_INFO_START: "CAN started", - constants.CAN_INFO_STOP: "CAN stopped", - constants.CAN_INFO_RESET: "CAN reset", + constants.CAN_INFO_START: "CAN started", + constants.CAN_INFO_STOP: "CAN stopped", + constants.CAN_INFO_RESET: "CAN reset", } CAN_ERROR_MESSAGES = { - constants.CAN_ERROR_STUFF: "CAN bit stuff error", - constants.CAN_ERROR_FORM: "CAN form error", - constants.CAN_ERROR_ACK: "CAN acknowledgment error", - constants.CAN_ERROR_BIT: "CAN bit error", - constants.CAN_ERROR_CRC: "CAN CRC error", - constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", + constants.CAN_ERROR_STUFF: "CAN bit stuff error", + constants.CAN_ERROR_FORM: "CAN form error", + constants.CAN_ERROR_ACK: "CAN acknowledgment error", + constants.CAN_ERROR_BIT: "CAN bit error", + constants.CAN_ERROR_CRC: "CAN CRC error", + constants.CAN_ERROR_OTHER: "Other (unknown) CAN error", } #---------------------------------------------------------------------------- diff --git a/can/interfaces/ixxat/constants.py b/can/interfaces/ixxat/constants.py index 966a77f18..62505dcc5 100644 --- a/can/interfaces/ixxat/constants.py +++ b/can/interfaces/ixxat/constants.py @@ -1,45 +1,48 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems + Copyright (C) 2016 Giuseppe Corbelli """ -FALSE = 0 -TRUE = 1 +FALSE = 0 +TRUE = 1 -INFINITE = 0xFFFFFFFF +INFINITE = 0xFFFFFFFF VCI_MAX_ERRSTRLEN = 256 # Bitrates -CAN_BT0_10KB = 0x31 -CAN_BT1_10KB = 0x1C -CAN_BT0_20KB = 0x18 -CAN_BT1_20KB = 0x1C -CAN_BT0_50KB = 0x09 -CAN_BT1_50KB = 0x1C -CAN_BT0_100KB = 0x04 -CAN_BT1_100KB = 0x1C -CAN_BT0_125KB = 0x03 -CAN_BT1_125KB = 0x1C -CAN_BT0_250KB = 0x01 -CAN_BT1_250KB = 0x1C -CAN_BT0_500KB = 0x00 -CAN_BT1_500KB = 0x1C -CAN_BT0_800KB = 0x00 -CAN_BT1_800KB = 0x16 -CAN_BT0_1000KB = 0x00 -CAN_BT1_1000KB = 0x14 +CAN_BT0_10KB = 0x31 +CAN_BT1_10KB = 0x1C +CAN_BT0_20KB = 0x18 +CAN_BT1_20KB = 0x1C +CAN_BT0_50KB = 0x09 +CAN_BT1_50KB = 0x1C +CAN_BT0_100KB = 0x04 +CAN_BT1_100KB = 0x1C +CAN_BT0_125KB = 0x03 +CAN_BT1_125KB = 0x1C +CAN_BT0_250KB = 0x01 +CAN_BT1_250KB = 0x1C +CAN_BT0_500KB = 0x00 +CAN_BT1_500KB = 0x1C +CAN_BT0_800KB = 0x00 +CAN_BT1_800KB = 0x16 +CAN_BT0_1000KB = 0x00 +CAN_BT1_1000KB = 0x14 # Facilities/severities -SEV_INFO = 0x40000000 -SEV_WARN = 0x80000000 -SEV_ERROR = 0xC0000000 -SEV_MASK = 0xC0000000 -SEV_SUCCESS = 0x00000000 +SEV_INFO = 0x40000000 +SEV_WARN = 0x80000000 +SEV_ERROR = 0xC0000000 +SEV_MASK = 0xC0000000 +SEV_SUCCESS = 0x00000000 -RESERVED_FLAG = 0x10000000 -CUSTOMER_FLAG = 0x20000000 +RESERVED_FLAG = 0x10000000 +CUSTOMER_FLAG = 0x20000000 STATUS_MASK = 0x0000FFFF FACILITY_MASK = 0x0FFF0000 @@ -102,12 +105,12 @@ VCI_E_WRONG_FLASHFWVERSION = SEV_VCI_ERROR | 0x001A # Controller status -CAN_STATUS_TXPEND = 0x01 -CAN_STATUS_OVRRUN = 0x02 -CAN_STATUS_ERRLIM = 0x04 -CAN_STATUS_BUSOFF = 0x08 -CAN_STATUS_ININIT = 0x10 -CAN_STATUS_BUSCERR = 0x20 +CAN_STATUS_TXPEND = 0x01 +CAN_STATUS_OVRRUN = 0x02 +CAN_STATUS_ERRLIM = 0x04 +CAN_STATUS_BUSOFF = 0x08 +CAN_STATUS_ININIT = 0x10 +CAN_STATUS_BUSCERR = 0x20 # Controller operating modes CAN_OPMODE_UNDEFINED = 0x00 @@ -128,18 +131,18 @@ # Information supplied in the abData[0] field of info frames # (CANMSGINFO.Bytes.bType = CAN_MSGTYPE_INFO). -CAN_INFO_START = 1 -CAN_INFO_STOP = 2 -CAN_INFO_RESET = 3 +CAN_INFO_START = 1 +CAN_INFO_STOP = 2 +CAN_INFO_RESET = 3 # Information supplied in the abData[0] field of info frames # (CANMSGINFO.Bytes.bType = CAN_MSGTYPE_ERROR). -CAN_ERROR_STUFF = 1 # stuff error -CAN_ERROR_FORM = 2 # form error -CAN_ERROR_ACK = 3 # acknowledgment error -CAN_ERROR_BIT = 4 # bit error -CAN_ERROR_CRC = 6 # CRC error -CAN_ERROR_OTHER = 7 # other (unspecified) error +CAN_ERROR_STUFF = 1 # stuff error +CAN_ERROR_FORM = 2 # form error +CAN_ERROR_ACK = 3 # acknowledgment error +CAN_ERROR_BIT = 4 # bit error +CAN_ERROR_CRC = 6 # CRC error +CAN_ERROR_OTHER = 7 # other (unspecified) error # acceptance code and mask to reject all CAN IDs CAN_ACC_MASK_NONE = 0xFFFFFFFF diff --git a/can/interfaces/ixxat/exceptions.py b/can/interfaces/ixxat/exceptions.py index db5de5445..9ac5b8f80 100644 --- a/can/interfaces/ixxat/exceptions.py +++ b/can/interfaces/ixxat/exceptions.py @@ -1,6 +1,9 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems + Copyright (C) 2016 Giuseppe Corbelli """ diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index bfbacae1c..93eadd37c 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -1,6 +1,9 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems + Copyright (C) 2016 Giuseppe Corbelli """ diff --git a/can/interfaces/kvaser/__init__.py b/can/interfaces/kvaser/__init__.py index c9d291dde..c55ce39ed 100644 --- a/can/interfaces/kvaser/__init__.py +++ b/can/interfaces/kvaser/__init__.py @@ -1 +1,7 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from can.interfaces.kvaser.canlib import * diff --git a/can/interfaces/kvaser/argument_parser.py b/can/interfaces/kvaser/argument_parser.py index 230648aaf..63b11cb48 100644 --- a/can/interfaces/kvaser/argument_parser.py +++ b/can/interfaces/kvaser/argument_parser.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +TODO: Where is this used? Is this used? +""" def add_to_parser(parser): parser.add_argument("-c", "--channel", type=str, dest="channel", diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index f430356e7..51cd84d55 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -1,4 +1,6 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ Contains Python equivalents of the function and constant definitions in CANLIB's canlib.h, with some supporting functionality diff --git a/can/interfaces/kvaser/constants.py b/can/interfaces/kvaser/constants.py index 20ca5204e..1c658dce0 100644 --- a/can/interfaces/kvaser/constants.py +++ b/can/interfaces/kvaser/constants.py @@ -1,17 +1,21 @@ +#!/usr/bin/env python +# coding: utf-8 + """ Contains Python equivalents of the function and constant definitions in CANLIB's canstat.h, with some supporting functionality specific to Python. Copyright (C) 2010 Dynamic Controls - """ + import ctypes class c_canStatus(ctypes.c_int): pass +# TODO better formatting canOK = 0 canERR_PARAM = -1 canERR_NOMSG = -2 diff --git a/can/interfaces/pcan/PCANBasic.py b/can/interfaces/pcan/PCANBasic.py index 8f023adc2..2fc442627 100644 --- a/can/interfaces/pcan/PCANBasic.py +++ b/can/interfaces/pcan/PCANBasic.py @@ -1,29 +1,19 @@ -# PCANBasic.py -# -# ~~~~~~~~~~~~ -# -# PCAN-Basic API -# -# ~~~~~~~~~~~~ -# -# ------------------------------------------------------------------ -# Author : Keneth Wagner -# Last change: 18.05.2016 Wagner -# -# Language: Python 2.7 -# ------------------------------------------------------------------ -# -# Copyright (C) 1999-2016 PEAK-System Technik GmbH, Darmstadt -# more Info at http://www.peak-system.com -# +#!/usr/bin/env python +# coding: utf-8 + +""" +PCAN-Basic API + +Author: Keneth Wagner + +Copyright (C) 1999-2016 PEAK-System Technik GmbH, Darmstadt, Germany +http://www.peak-system.com +""" -# Module Imports -# from ctypes import * import platform import logging -# Patched for python-can: use logger instead of print() logger = logging.getLogger('can.pcan') #/////////////////////////////////////////////////////////// @@ -46,7 +36,7 @@ #/////////////////////////////////////////////////////////// # Currently defined and supported PCAN channels -# + PCAN_NONEBUS = TPCANHandle(0x00) # Undefined/default value for a PCAN bus PCAN_ISABUS1 = TPCANHandle(0x21) # PCAN-ISA interface, channel 1 @@ -65,17 +55,17 @@ PCAN_PCIBUS3 = TPCANHandle(0x43) # PCAN-PCI interface, channel 3 PCAN_PCIBUS4 = TPCANHandle(0x44) # PCAN-PCI interface, channel 4 PCAN_PCIBUS5 = TPCANHandle(0x45) # PCAN-PCI interface, channel 5 -PCAN_PCIBUS6 = TPCANHandle(0x46) # PCAN-PCI interface, channel 6 -PCAN_PCIBUS7 = TPCANHandle(0x47) # PCAN-PCI interface, channel 7 -PCAN_PCIBUS8 = TPCANHandle(0x48) # PCAN-PCI interface, channel 8 -PCAN_PCIBUS9 = TPCANHandle(0x409) # PCAN-PCI interface, channel 9 -PCAN_PCIBUS10 = TPCANHandle(0x40A) # PCAN-PCI interface, channel 10 -PCAN_PCIBUS11 = TPCANHandle(0x40B) # PCAN-PCI interface, channel 11 -PCAN_PCIBUS12 = TPCANHandle(0x40C) # PCAN-PCI interface, channel 12 -PCAN_PCIBUS13 = TPCANHandle(0x40D) # PCAN-PCI interface, channel 13 -PCAN_PCIBUS14 = TPCANHandle(0x40E) # PCAN-PCI interface, channel 14 -PCAN_PCIBUS15 = TPCANHandle(0x40F) # PCAN-PCI interface, channel 15 -PCAN_PCIBUS16 = TPCANHandle(0x410) # PCAN-PCI interface, channel 16 +PCAN_PCIBUS6 = TPCANHandle(0x46) # PCAN-PCI interface, channel 6 +PCAN_PCIBUS7 = TPCANHandle(0x47) # PCAN-PCI interface, channel 7 +PCAN_PCIBUS8 = TPCANHandle(0x48) # PCAN-PCI interface, channel 8 +PCAN_PCIBUS9 = TPCANHandle(0x409) # PCAN-PCI interface, channel 9 +PCAN_PCIBUS10 = TPCANHandle(0x40A) # PCAN-PCI interface, channel 10 +PCAN_PCIBUS11 = TPCANHandle(0x40B) # PCAN-PCI interface, channel 11 +PCAN_PCIBUS12 = TPCANHandle(0x40C) # PCAN-PCI interface, channel 12 +PCAN_PCIBUS13 = TPCANHandle(0x40D) # PCAN-PCI interface, channel 13 +PCAN_PCIBUS14 = TPCANHandle(0x40E) # PCAN-PCI interface, channel 14 +PCAN_PCIBUS15 = TPCANHandle(0x40F) # PCAN-PCI interface, channel 15 +PCAN_PCIBUS16 = TPCANHandle(0x410) # PCAN-PCI interface, channel 16 PCAN_USBBUS1 = TPCANHandle(0x51) # PCAN-USB interface, channel 1 PCAN_USBBUS2 = TPCANHandle(0x52) # PCAN-USB interface, channel 2 @@ -85,37 +75,36 @@ PCAN_USBBUS6 = TPCANHandle(0x56) # PCAN-USB interface, channel 6 PCAN_USBBUS7 = TPCANHandle(0x57) # PCAN-USB interface, channel 7 PCAN_USBBUS8 = TPCANHandle(0x58) # PCAN-USB interface, channel 8 -PCAN_USBBUS9 = TPCANHandle(0x509) # PCAN-USB interface, channel 9 -PCAN_USBBUS10 = TPCANHandle(0x50A) # PCAN-USB interface, channel 10 -PCAN_USBBUS11 = TPCANHandle(0x50B) # PCAN-USB interface, channel 11 -PCAN_USBBUS12 = TPCANHandle(0x50C) # PCAN-USB interface, channel 12 -PCAN_USBBUS13 = TPCANHandle(0x50D) # PCAN-USB interface, channel 13 -PCAN_USBBUS14 = TPCANHandle(0x50E) # PCAN-USB interface, channel 14 -PCAN_USBBUS15 = TPCANHandle(0x50F) # PCAN-USB interface, channel 15 -PCAN_USBBUS16 = TPCANHandle(0x510) # PCAN-USB interface, channel 16 +PCAN_USBBUS9 = TPCANHandle(0x509) # PCAN-USB interface, channel 9 +PCAN_USBBUS10 = TPCANHandle(0x50A) # PCAN-USB interface, channel 10 +PCAN_USBBUS11 = TPCANHandle(0x50B) # PCAN-USB interface, channel 11 +PCAN_USBBUS12 = TPCANHandle(0x50C) # PCAN-USB interface, channel 12 +PCAN_USBBUS13 = TPCANHandle(0x50D) # PCAN-USB interface, channel 13 +PCAN_USBBUS14 = TPCANHandle(0x50E) # PCAN-USB interface, channel 14 +PCAN_USBBUS15 = TPCANHandle(0x50F) # PCAN-USB interface, channel 15 +PCAN_USBBUS16 = TPCANHandle(0x510) # PCAN-USB interface, channel 16 PCAN_PCCBUS1 = TPCANHandle(0x61) # PCAN-PC Card interface, channel 1 PCAN_PCCBUS2 = TPCANHandle(0x62) # PCAN-PC Card interface, channel 2 -PCAN_LANBUS1 = TPCANHandle(0x801) # PCAN-LAN interface, channel 1 -PCAN_LANBUS2 = TPCANHandle(0x802) # PCAN-LAN interface, channel 2 -PCAN_LANBUS3 = TPCANHandle(0x803) # PCAN-LAN interface, channel 3 -PCAN_LANBUS4 = TPCANHandle(0x804) # PCAN-LAN interface, channel 4 -PCAN_LANBUS5 = TPCANHandle(0x805) # PCAN-LAN interface, channel 5 -PCAN_LANBUS6 = TPCANHandle(0x806) # PCAN-LAN interface, channel 6 -PCAN_LANBUS7 = TPCANHandle(0x807) # PCAN-LAN interface, channel 7 -PCAN_LANBUS8 = TPCANHandle(0x808) # PCAN-LAN interface, channel 8 -PCAN_LANBUS9 = TPCANHandle(0x809) # PCAN-LAN interface, channel 9 -PCAN_LANBUS10 = TPCANHandle(0x80A) # PCAN-LAN interface, channel 10 -PCAN_LANBUS11 = TPCANHandle(0x80B) # PCAN-LAN interface, channel 11 -PCAN_LANBUS12 = TPCANHandle(0x80C) # PCAN-LAN interface, channel 12 -PCAN_LANBUS13 = TPCANHandle(0x80D) # PCAN-LAN interface, channel 13 -PCAN_LANBUS14 = TPCANHandle(0x80E) # PCAN-LAN interface, channel 14 -PCAN_LANBUS15 = TPCANHandle(0x80F) # PCAN-LAN interface, channel 15 -PCAN_LANBUS16 = TPCANHandle(0x810) # PCAN-LAN interface, channel 16 +PCAN_LANBUS1 = TPCANHandle(0x801) # PCAN-LAN interface, channel 1 +PCAN_LANBUS2 = TPCANHandle(0x802) # PCAN-LAN interface, channel 2 +PCAN_LANBUS3 = TPCANHandle(0x803) # PCAN-LAN interface, channel 3 +PCAN_LANBUS4 = TPCANHandle(0x804) # PCAN-LAN interface, channel 4 +PCAN_LANBUS5 = TPCANHandle(0x805) # PCAN-LAN interface, channel 5 +PCAN_LANBUS6 = TPCANHandle(0x806) # PCAN-LAN interface, channel 6 +PCAN_LANBUS7 = TPCANHandle(0x807) # PCAN-LAN interface, channel 7 +PCAN_LANBUS8 = TPCANHandle(0x808) # PCAN-LAN interface, channel 8 +PCAN_LANBUS9 = TPCANHandle(0x809) # PCAN-LAN interface, channel 9 +PCAN_LANBUS10 = TPCANHandle(0x80A) # PCAN-LAN interface, channel 10 +PCAN_LANBUS11 = TPCANHandle(0x80B) # PCAN-LAN interface, channel 11 +PCAN_LANBUS12 = TPCANHandle(0x80C) # PCAN-LAN interface, channel 12 +PCAN_LANBUS13 = TPCANHandle(0x80D) # PCAN-LAN interface, channel 13 +PCAN_LANBUS14 = TPCANHandle(0x80E) # PCAN-LAN interface, channel 14 +PCAN_LANBUS15 = TPCANHandle(0x80F) # PCAN-LAN interface, channel 15 +PCAN_LANBUS16 = TPCANHandle(0x810) # PCAN-LAN interface, channel 16 # Represent the PCAN error and status codes -# PCAN_ERROR_OK = TPCANStatus(0x00000) # No error PCAN_ERROR_XMTFULL = TPCANStatus(0x00001) # Transmit buffer in CAN controller is full PCAN_ERROR_OVERRUN = TPCANStatus(0x00002) # CAN controller was read too late @@ -146,19 +135,17 @@ PCAN_ERROR_ILLOPERATION = TPCANStatus(0x8000000)# Invalid operation [Value was changed from 0x80000 to 0x8000000] # PCAN devices -# -PCAN_NONE = TPCANDevice(0x00) # Undefined, unknown or not selected PCAN device value -PCAN_PEAKCAN = TPCANDevice(0x01) # PCAN Non-Plug&Play devices. NOT USED WITHIN PCAN-Basic API -PCAN_ISA = TPCANDevice(0x02) # PCAN-ISA, PCAN-PC/104, and PCAN-PC/104-Plus -PCAN_DNG = TPCANDevice(0x03) # PCAN-Dongle -PCAN_PCI = TPCANDevice(0x04) # PCAN-PCI, PCAN-cPCI, PCAN-miniPCI, and PCAN-PCI Express -PCAN_USB = TPCANDevice(0x05) # PCAN-USB and PCAN-USB Pro -PCAN_PCC = TPCANDevice(0x06) # PCAN-PC Card -PCAN_VIRTUAL = TPCANDevice(0x07) # PCAN Virtual hardware. NOT USED WITHIN PCAN-Basic API -PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices +PCAN_NONE = TPCANDevice(0x00) # Undefined, unknown or not selected PCAN device value +PCAN_PEAKCAN = TPCANDevice(0x01) # PCAN Non-Plug&Play devices. NOT USED WITHIN PCAN-Basic API +PCAN_ISA = TPCANDevice(0x02) # PCAN-ISA, PCAN-PC/104, and PCAN-PC/104-Plus +PCAN_DNG = TPCANDevice(0x03) # PCAN-Dongle +PCAN_PCI = TPCANDevice(0x04) # PCAN-PCI, PCAN-cPCI, PCAN-miniPCI, and PCAN-PCI Express +PCAN_USB = TPCANDevice(0x05) # PCAN-USB and PCAN-USB Pro +PCAN_PCC = TPCANDevice(0x06) # PCAN-PC Card +PCAN_VIRTUAL = TPCANDevice(0x07) # PCAN Virtual hardware. NOT USED WITHIN PCAN-Basic API +PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices # PCAN parameters -# PCAN_DEVICE_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 5-Volt power parameter PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter @@ -190,7 +177,6 @@ PCAN_LAN_SERVICE_STATUS = TPCANParameter(0x1D) # Status of the Virtual PCAN-Gateway Service # PCAN parameter values -# PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) PCAN_PARAMETER_ON = int(0x01) # The PCAN parameter is set (active) PCAN_FILTER_CLOSE = int(0x00) # The PCAN filter is closed. No messages will be received @@ -201,13 +187,13 @@ PCAN_CHANNEL_OCCUPIED = int(0x02) # The PCAN-Channel handle is valid, and is already being used PCAN_CHANNEL_PCANVIEW = PCAN_CHANNEL_AVAILABLE | PCAN_CHANNEL_OCCUPIED # The PCAN-Channel handle is already being used by a PCAN-View application, but is available to connect -LOG_FUNCTION_DEFAULT = int(0x00) # Logs system exceptions / errors -LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions -LOG_FUNCTION_PARAMETERS = int(0x02) # Logs the parameters passed to the PCAN-Basic API functions -LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions -LOG_FUNCTION_WRITE = int(0x08) # Logs the CAN messages passed to the CAN_Write function -LOG_FUNCTION_READ = int(0x10) # Logs the CAN messages received within the CAN_Read function -LOG_FUNCTION_ALL = int(0xFFFF) # Logs all possible information within the PCAN-Basic API functions +LOG_FUNCTION_DEFAULT = int(0x00) # Logs system exceptions / errors +LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions +LOG_FUNCTION_PARAMETERS = int(0x02) # Logs the parameters passed to the PCAN-Basic API functions +LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions +LOG_FUNCTION_WRITE = int(0x08) # Logs the CAN messages passed to the CAN_Write function +LOG_FUNCTION_READ = int(0x10) # Logs the CAN messages received within the CAN_Read function +LOG_FUNCTION_ALL = int(0xFFFF)# Logs all possible information within the PCAN-Basic API functions TRACE_FILE_SINGLE = int(0x00) # A single file is written until it size reaches PAN_TRACE_SIZE TRACE_FILE_SEGMENTED = int(0x01) # Traced data is distributed in several files with size PAN_TRACE_SIZE @@ -221,7 +207,6 @@ SERVICE_STATUS_RUNNING = int(0x04) # The service is running # PCAN message types -# PCAN_MESSAGE_STANDARD = TPCANMessageType(0x00) # The PCAN message is a CAN Standard Frame (11-bit identifier) PCAN_MESSAGE_RTR = TPCANMessageType(0x01) # The PCAN message is a CAN Remote-Transfer-Request Frame PCAN_MESSAGE_EXTENDED = TPCANMessageType(0x02) # The PCAN message is a CAN Extended Frame (29-bit identifier) @@ -231,7 +216,6 @@ PCAN_MESSAGE_STATUS = TPCANMessageType(0x80) # The PCAN message represents a PCAN status message # Frame Type / Initialization Mode -# PCAN_MODE_STANDARD = PCAN_MESSAGE_STANDARD PCAN_MODE_EXTENDED = PCAN_MESSAGE_EXTENDED @@ -239,31 +223,30 @@ # You can define your own Baud rate with the BTROBTR1 register. # Take a look at www.peak-system.com for our free software "BAUDTOOL" # to calculate the BTROBTR1 register for every bit rate and sample point. -# -PCAN_BAUD_1M = TPCANBaudrate(0x0014) # 1 MBit/s -PCAN_BAUD_800K = TPCANBaudrate(0x0016) # 800 kBit/s -PCAN_BAUD_500K = TPCANBaudrate(0x001C) # 500 kBit/s -PCAN_BAUD_250K = TPCANBaudrate(0x011C) # 250 kBit/s -PCAN_BAUD_125K = TPCANBaudrate(0x031C) # 125 kBit/s -PCAN_BAUD_100K = TPCANBaudrate(0x432F) # 100 kBit/s + +PCAN_BAUD_1M = TPCANBaudrate(0x0014) # 1 MBit/s +PCAN_BAUD_800K = TPCANBaudrate(0x0016) # 800 kBit/s +PCAN_BAUD_500K = TPCANBaudrate(0x001C) # 500 kBit/s +PCAN_BAUD_250K = TPCANBaudrate(0x011C) # 250 kBit/s +PCAN_BAUD_125K = TPCANBaudrate(0x031C) # 125 kBit/s +PCAN_BAUD_100K = TPCANBaudrate(0x432F) # 100 kBit/s PCAN_BAUD_95K = TPCANBaudrate(0xC34E) # 95,238 kBit/s PCAN_BAUD_83K = TPCANBaudrate(0x852B) # 83,333 kBit/s -PCAN_BAUD_50K = TPCANBaudrate(0x472F) # 50 kBit/s +PCAN_BAUD_50K = TPCANBaudrate(0x472F) # 50 kBit/s PCAN_BAUD_47K = TPCANBaudrate(0x1414) # 47,619 kBit/s PCAN_BAUD_33K = TPCANBaudrate(0x8B2F) # 33,333 kBit/s -PCAN_BAUD_20K = TPCANBaudrate(0x532F) # 20 kBit/s -PCAN_BAUD_10K = TPCANBaudrate(0x672F) # 10 kBit/s -PCAN_BAUD_5K = TPCANBaudrate(0x7F7F) # 5 kBit/s +PCAN_BAUD_20K = TPCANBaudrate(0x532F) # 20 kBit/s +PCAN_BAUD_10K = TPCANBaudrate(0x672F) # 10 kBit/s +PCAN_BAUD_5K = TPCANBaudrate(0x7F7F) # 5 kBit/s # Represents the configuration for a CAN bit rate -# Note: +# Note: # * Each parameter and its value must be separated with a '='. # * Each pair of parameter/value must be separated using ','. # # Example: # f_clock=80000000,nom_brp=0,nom_tseg1=13,nom_tseg2=0,nom_sjw=0,data_brp=0,data_tseg1=13,data_tseg2=0,data_sjw=0 # -# Patched for python-can: use bytes to fix incompatibility with Python 3 PCAN_BR_CLOCK = TPCANBitrateFD(b"f_clock") PCAN_BR_CLOCK_MHZ = TPCANBitrateFD(b"f_clock_mhz") PCAN_BR_NOM_BRP = TPCANBitrateFD(b"nom_brp") @@ -278,7 +261,6 @@ PCAN_BR_DATA_SAMPLE = TPCANBitrateFD(b"data_ssp_offset") # Supported No-Plug-And-Play Hardware types -# PCAN_TYPE_ISA = TPCANType(0x01) # PCAN-ISA 82C200 PCAN_TYPE_ISA_SJA = TPCANType(0x09) # PCAN-ISA SJA1000 PCAN_TYPE_ISA_PHYTEC = TPCANType(0x04) # PHYTEC ISA @@ -287,36 +269,29 @@ PCAN_TYPE_DNG_SJA = TPCANType(0x05) # PCAN-Dongle SJA1000 PCAN_TYPE_DNG_SJA_EPP = TPCANType(0x06) # PCAN-Dongle EPP SJA1000 -# Represents a PCAN message -# class TPCANMsg (Structure): """ Represents a PCAN message """ - _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier + _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier ("MSGTYPE", TPCANMessageType), # Type of the message ("LEN", c_ubyte), # Data Length Code of the message (0..8) ("DATA", c_ubyte * 8) ] # Data of the message (DATA[0]..DATA[7]) -# Represents a timestamp of a received PCAN message -# Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow -# class TPCANTimestamp (Structure): """ Represents a timestamp of a received PCAN message Total Microseconds = micros + 1000 * millis + 0x100000000 * 1000 * millis_overflow """ - _fields_ = [ ("millis", c_uint), # Base-value: milliseconds: 0.. 2^32-1 + _fields_ = [ ("millis", c_uint), # Base-value: milliseconds: 0.. 2^32-1 ("millis_overflow", c_ushort), # Roll-arounds of millis ("micros", c_ushort) ] # Microseconds: 0..999 -# Represents a PCAN message from a FD capable hardware -# class TPCANMsgFD (Structure): """ - Represents a PCAN message + Represents a PCAN message from a FD capable hardware """ - _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier + _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier ("MSGTYPE", TPCANMessageType), # Type of the message ("DLC", c_ubyte), # Data Length Code of the message (0..15) ("DATA", c_ubyte * 64) ] # Data of the message (DATA[0]..DATA[63]) @@ -325,15 +300,12 @@ class TPCANMsgFD (Structure): # PCAN-Basic API function declarations #/////////////////////////////////////////////////////////// -# PCAN-Basic API class implementation -# class PCANBasic: + """PCAN-Basic API class implementation """ - PCAN-Basic API class implementation - """ + def __init__(self): # Loads the PCANBasic.dll - # if platform.system() == 'Windows': self.__m_dllBasic = windll.LoadLibrary("PCANBasic") else: @@ -341,8 +313,6 @@ def __init__(self): if self.__m_dllBasic == None: logger.error("Exception: The PCAN-Basic DLL couldn't be loaded!") - # Initializes a PCAN Channel - # def Initialize( self, Channel, @@ -371,8 +341,6 @@ def Initialize( logger.error("Exception on PCANBasic.Initialize") raise - # Initializes a FD capable PCAN Channel - # def InitializeFD( self, Channel, @@ -406,8 +374,6 @@ def InitializeFD( logger.error("Exception on PCANBasic.InitializeFD") raise - # Uninitializes one or all PCAN Channels initialized by CAN_Initialize - # def Uninitialize( self, Channel): @@ -431,8 +397,6 @@ def Uninitialize( logger.error("Exception on PCANBasic.Uninitialize") raise - # Resets the receive and transmit queues of the PCAN Channel - # def Reset( self, Channel): @@ -456,8 +420,6 @@ def Reset( logger.error("Exception on PCANBasic.Reset") raise - # Gets the current status of a PCAN Channel - # def GetStatus( self, Channel): @@ -478,8 +440,6 @@ def GetStatus( logger.error("Exception on PCANBasic.GetStatus") raise - # Reads a CAN message from the receive queue of a PCAN Channel - # def Read( self, Channel): @@ -510,8 +470,6 @@ def Read( logger.error("Exception on PCANBasic.Read") raise - # Reads a CAN message from the receive queue of a FD capable PCAN Channel - # def ReadFD( self, Channel): @@ -542,8 +500,6 @@ def ReadFD( logger.error("Exception on PCANBasic.ReadFD") raise - # Transmits a CAN message - # def Write( self, Channel, @@ -566,8 +522,6 @@ def Write( logger.error("Exception on PCANBasic.Write") raise - # Transmits a CAN message over a FD capable PCAN Channel - # def WriteFD( self, Channel, @@ -590,8 +544,6 @@ def WriteFD( logger.error("Exception on PCANBasic.WriteFD") raise - # Configures the reception filter - # def FilterMessages( self, Channel, @@ -623,8 +575,6 @@ def FilterMessages( logger.error("Exception on PCANBasic.FilterMessages") raise - # Retrieves a PCAN Channel value - # def GetValue( self, Channel, @@ -661,9 +611,6 @@ def GetValue( logger.error("Exception on PCANBasic.GetValue") raise - # Returns a descriptive text of a given TPCANStatus - # error code, in any desired language - # def SetValue( self, Channel, diff --git a/can/interfaces/pcan/__init__.py b/can/interfaces/pcan/__init__.py index 618c182ce..8dbcfd0f9 100644 --- a/can/interfaces/pcan/__init__.py +++ b/can/interfaces/pcan/__init__.py @@ -1 +1,7 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from can.interfaces.pcan.pcan import PcanBus diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 8647222d9..c46d06895 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -1,16 +1,19 @@ -""" -Enable basic can over a PCAN USB device. +#!/usr/bin/env python +# coding: utf-8 """ +Enable basic CAN over a PCAN USB device. +""" + import logging import sys +import time -from can.interfaces.pcan.PCANBasic import * +import can +from can import CanError from can.bus import BusABC from can.message import Message -from can import CanError -import can -import time +from can.interfaces.pcan.PCANBasic import * boottimeEpoch = 0 try: @@ -35,10 +38,10 @@ # Use polling instead HAS_EVENTS = False -if sys.version_info >= (3, 3): +try: # new in 3.3 timeout_clock = time.perf_counter -else: +except AttributeError: # deprecated in 3.3 timeout_clock = time.clock diff --git a/can/interfaces/serial/__init__.py b/can/interfaces/serial/__init__.py index 7dded7aff..6746fda0b 100644 --- a/can/interfaces/serial/__init__.py +++ b/can/interfaces/serial/__init__.py @@ -1 +1,7 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from can.interfaces.serial.serial_can import SerialBus as Bus diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 7e5a81d0b..b7c2f7c0e 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -1,14 +1,16 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ A text based interface. For example use over serial ports like "/dev/ttyS1" or "/dev/ttyUSB0" on Linux machines or "COM1" on Windows. The interface is a simple implementation that has been used for recording CAN traces. - """ import logging import struct + from can.bus import BusABC from can.message import Message @@ -141,4 +143,3 @@ def recv(self, timeout=None): arbitration_id=arb_id, dlc=dlc, data=data) - diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index 2de902ea9..2e0a9ad0f 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + +from can.interfaces.socketcan import socketcan_constants as constants from can.interfaces.socketcan.socketcan_ctypes import SocketcanCtypes_Bus from can.interfaces.socketcan.socketcan_native import SocketcanNative_Bus -from can.interfaces.socketcan import socketcan_constants as constants diff --git a/can/interfaces/socketcan/socketcan_common.py b/can/interfaces/socketcan/socketcan_common.py index 7a86822bc..05ce48b6c 100644 --- a/can/interfaces/socketcan/socketcan_common.py +++ b/can/interfaces/socketcan/socketcan_common.py @@ -1,7 +1,10 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ Defines common socketcan functions. """ + import struct from can.interfaces.socketcan.socketcan_constants import CAN_EFF_FLAG @@ -27,4 +30,5 @@ def pack_filters(can_filters=None): can_id |= CAN_EFF_FLAG filter_data.append(can_id) filter_data.append(can_mask) + return struct.pack(can_filter_fmt, *filter_data) diff --git a/can/interfaces/socketcan/socketcan_constants.py b/can/interfaces/socketcan/socketcan_constants.py index b3a7447fa..fb9eb3cad 100644 --- a/can/interfaces/socketcan/socketcan_constants.py +++ b/can/interfaces/socketcan/socketcan_constants.py @@ -1,22 +1,23 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ -Defines CAN constants. +Defines shared CAN constants. """ -canMSG_EXT = 0x0004 +canMSG_EXT = 0x0004 -CAN_ERR_FLAG = 0x20000000 -CAN_RTR_FLAG = 0x40000000 -CAN_EFF_FLAG = 0x80000000 +CAN_ERR_FLAG = 0x20000000 +CAN_RTR_FLAG = 0x40000000 +CAN_EFF_FLAG = 0x80000000 # BCM opcodes -CAN_BCM_TX_SETUP = 1 -CAN_BCM_TX_DELETE = 2 - -CAN_BCM_TX_EXPIRED = 9 +CAN_BCM_TX_SETUP = 1 +CAN_BCM_TX_DELETE = 2 -CAN_BCM_RX_TIMEOUT = 11 +CAN_BCM_TX_EXPIRED = 9 +CAN_BCM_RX_TIMEOUT = 11 # BCM flags SETTIMER = 0x0001 @@ -32,11 +33,11 @@ RX_RTR_FRAME = 0x0400 CAN_FD_FRAME = 0x0800 -CAN_RAW = 1 -CAN_BCM = 2 +CAN_RAW = 1 +CAN_BCM = 2 -SOL_CAN_BASE = 100 -SOL_CAN_RAW = SOL_CAN_BASE + CAN_RAW +SOL_CAN_BASE = 100 +SOL_CAN_RAW = SOL_CAN_BASE + CAN_RAW CAN_RAW_FILTER = 1 CAN_RAW_ERR_FILTER = 2 @@ -44,8 +45,8 @@ CAN_RAW_RECV_OWN_MSGS = 4 CAN_RAW_FD_FRAMES = 5 -MSK_ARBID = 0x1FFFFFFF -MSK_FLAGS = 0xE0000000 +MSK_ARBID = 0x1FFFFFFF +MSK_FLAGS = 0xE0000000 PF_CAN = 29 SOCK_RAW = 3 diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index 8d9b2e60d..d9e7f225b 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from __future__ import print_function import ctypes @@ -527,4 +533,3 @@ def __init__(self, channel, message, count, initial_period, subsequent_period): bytes_sent = libc.send(self.bcm_socket, ctypes.byref(frame), ctypes.sizeof(frame)) if bytes_sent == -1: log.debug("Error sending frame :-/") - diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 10933d79e..82e49511a 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 """ This implementation is for versions of Python that have native diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index d3834495a..6cf4660f0 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -1,2 +1,8 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from can.interfaces.usb2can.usb2canInterface import Usb2canBus from can.interfaces.usb2can.usb2canabstractionlayer import Usb2CanAbstractionLayer diff --git a/can/interfaces/usb2can/serial_selector.py b/can/interfaces/usb2can/serial_selector.py index 46808852a..3e9490a01 100644 --- a/can/interfaces/usb2can/serial_selector.py +++ b/can/interfaces/usb2can/serial_selector.py @@ -1,4 +1,11 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + import logging + try: import win32com.client except ImportError: @@ -16,13 +23,13 @@ def WMIDateStringToDate(dtmDate): strDateTime = strDateTime + dtmDate[7] + '/' else: strDateTime = strDateTime + dtmDate[6] + dtmDate[7] + '/' - strDateTime = strDateTime + dtmDate[0] + dtmDate[1] + dtmDate[2] + dtmDate[3] + " " + dtmDate[8] + dtmDate[ - 9] + ":" + dtmDate[10] + dtmDate[11] + ':' + dtmDate[12] + dtmDate[13] + strDateTime = strDateTime + dtmDate[0] + dtmDate[1] + dtmDate[2] + dtmDate[3] + ' ' + dtmDate[8] + dtmDate[ + 9] + ':' + dtmDate[10] + dtmDate[11] + ':' + dtmDate[12] + dtmDate[13] return strDateTime def serial(): - strComputer = "." + strComputer = '.' objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") objSWbemServices = objWMIService.ConnectServer(strComputer, "root\cimv2") colItems = objSWbemServices.ExecQuery("SELECT * FROM Win32_USBControllerDevice") @@ -30,7 +37,7 @@ def serial(): for objItem in colItems: string = objItem.Dependent # find based on beginning of serial - if "ED" in string: + if 'ED' in string: # print "Dependent:" + ` objItem.Dependent` string = string[len(string) - 9:len(string) - 1] diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 57c2b330c..03f09ee8c 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -1,4 +1,9 @@ -# this interface is for windows only, otherwise use socketCAN +#!/usr/bin/env python +# coding: utf-8 + +""" +This interface is for windows only, otherwise use socketCAN. +""" import logging @@ -26,7 +31,6 @@ def format_connection_string(deviceID, baudrate='500'): return "%s; %s" % (deviceID, baudrate) -# TODO: Issue 36 with data being zeros or anything other than 8 must be fixed def message_convert_tx(msg): messagetx = CanalMsg() diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index c88b1fe65..608c1dca8 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -1,4 +1,11 @@ -# This wrapper is for windows or direct access via CANAL API. Socket CAN is recommended under Unix/Linux systems +#!/usr/bin/env python +# coding: utf-8 + +""" +This wrapper is for windows or direct access via CANAL API. +Socket CAN is recommended under Unix/Linux systems. +""" + import can from ctypes import * from struct import * diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index 36c368b57..9342e6d60 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,2 +1,8 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from .canlib import VectorBus from .exceptions import VectorError diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 19e2c78da..a5093b1a8 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -1,8 +1,12 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ -Ctypes wrapper module for Vector CAN Interface on win32/win64 systems +Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. + Authors: Julien Grave , Christian Sandberg """ + # Import Standard Python Modules # ============================== import ctypes @@ -179,4 +183,3 @@ def reset(self): vxlapi.xlDeactivateChannel(self.port_handle, self.mask) vxlapi.xlActivateChannel(self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0) - diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 64fef0824..1cbaddccd 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from can import CanError diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index 5b49faca8..cf08c4b2a 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -1,8 +1,12 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 + """ -Ctypes wrapper module for Vector CAN Interface on win32/win64 systems +Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. + Authors: Julien Grave , Christian Sandberg """ + # Import Standard Python Modules # ============================== import ctypes From 78e4834ea2362583b8dffa57ff6f16d554b1b501 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 20 Feb 2018 18:37:38 +0100 Subject: [PATCH 010/168] general cleanup of the tests --- test/back2back_test.py | 10 ++++++++++ test/data/__init__.py | 2 ++ test/data/example_data.py | 3 ++- test/listener_test.py | 6 ++++++ test/logformats_test.py | 5 +++++ test/network_test.py | 3 +++ test/serial_test.py | 26 ++++++-------------------- test/simplecyclic_test.py | 10 ++++++++++ test/test_kvaser.py | 9 +++++++-- test/zero_dlc_test.py | 12 ++++++++++-- 10 files changed, 61 insertions(+), 25 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 202a5365e..85c4c2e4a 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -1,3 +1,13 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module tests two virtual busses attached to each other. + +Some tests are skipped when run on Travis CI because they are not +reproducible, see #243 (https://github.com/hardbyte/python-can/issues/243). +""" + import os import unittest import time diff --git a/test/data/__init__.py b/test/data/__init__.py index e69de29bb..394a0a067 100644 --- a/test/data/__init__.py +++ b/test/data/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# coding: utf-8 diff --git a/test/data/example_data.py b/test/data/example_data.py index 6d54d683f..a66da1f36 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 """ This module contains some example data, like messages of different diff --git a/test/listener_test.py b/test/listener_test.py index be795eaf4..4f6d9baea 100755 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -1,3 +1,9 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from time import sleep import unittest import random diff --git a/test/logformats_test.py b/test/logformats_test.py index 82c472243..019ea7147 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# coding: utf-8 + """ This test module test the separate reader/writer combinations of the can.io.* modules by writing some messages to a temporary file and reading it again. @@ -8,6 +11,8 @@ comments. """ +from __future__ import print_function + import unittest import tempfile from time import sleep diff --git a/test/network_test.py b/test/network_test.py index 310aef54b..ae6c9a81a 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# coding: utf-8 + from __future__ import print_function import unittest diff --git a/test/serial_test.py b/test/serial_test.py index 63f8b8400..1c189e4fb 100644 --- a/test/serial_test.py +++ b/test/serial_test.py @@ -1,31 +1,17 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 """ -Name: serial_test -Purpose: Testing the serial interface +This module is testing the serial interface. -Copyright: 2017 Boris Wenzlaff - -This file is part of python-can . - -python-can is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -any later version. - -python-can is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with python-can. If not, see . +Copyright: 2017 Boris Wenzlaff """ import unittest +from mock import patch + import can from can.interfaces.serial.serial_can import SerialBus -from mock import patch class SerialDummy: diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 980fe1eee..362e89c2c 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -1,3 +1,13 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module tests cyclic send tasks. + +Some tests are skipped when run on Travis CI because they are not +reproducible, see #243 (https://github.com/hardbyte/python-can/issues/243). +""" + import os from time import sleep import unittest diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 459b9978b..eb0a73ac3 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -1,9 +1,14 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + import ctypes import unittest import time import logging -logging.basicConfig(level=logging.DEBUG) + import can from can.interfaces.kvaser import canlib from can.interfaces.kvaser import constants diff --git a/test/zero_dlc_test.py b/test/zero_dlc_test.py index 74c8d7a06..83a073dc4 100644 --- a/test/zero_dlc_test.py +++ b/test/zero_dlc_test.py @@ -1,6 +1,13 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + from time import sleep import unittest import logging + import can logging.getLogger(__file__).setLevel(logging.DEBUG) @@ -11,7 +18,8 @@ class ZeroDLCTest(unittest.TestCase): def test_recv_non_zero_dlc(self): bus_send = can.interface.Bus(bustype='virtual') bus_recv = can.interface.Bus(bustype='virtual') - msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=[0,1,2,3,4,5,6,7]) + data = [0, 1, 2, 3, 4, 5, 6, 7] + msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=data) bus_send.send(msg_send) msg_recv = bus_recv.recv() @@ -27,7 +35,6 @@ def test_recv_none(self): # Receiving nothing should evaluate msg_recv to False self.assertFalse(msg_recv) - def test_recv_zero_dlc(self): bus_send = can.interface.Bus(bustype='virtual') bus_recv = can.interface.Bus(bustype='virtual') @@ -39,5 +46,6 @@ def test_recv_zero_dlc(self): # Receiving a frame without data (dlc == 0) should evaluate msg_recv to True self.assertTrue(msg_recv) + if __name__ == '__main__': unittest.main() From fab6f23a8ea5dc325e560bc6e0ba0f98a90aacef Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 20 Feb 2018 18:37:53 +0100 Subject: [PATCH 011/168] general cleanup of the examples --- examples/cyclic.py | 7 +++++-- examples/send_one.py | 15 +++++++++++++-- examples/serial_com.py | 9 +++++++-- examples/simpleLogConvert.py | 10 ++++++++-- examples/vcan_filtered.py | 32 +++++++++++++++++++++----------- examples/virtual_can_demo.py | 4 ++++ 6 files changed, 58 insertions(+), 19 deletions(-) diff --git a/examples/cyclic.py b/examples/cyclic.py index 662e299a5..281b7c43e 100755 --- a/examples/cyclic.py +++ b/examples/cyclic.py @@ -1,4 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python +# coding: utf-8 + """ This example exercises the periodic sending capabilities. @@ -8,6 +10,8 @@ """ +from __future__ import print_function + import logging import time @@ -125,5 +129,4 @@ def test_periodic_send_with_modifying_data(bus): bus.shutdown() - time.sleep(2) diff --git a/examples/send_one.py b/examples/send_one.py index 46ae20980..c147cd858 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -1,20 +1,31 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This example shows how sending a single message works. +""" + from __future__ import print_function -import can +import can def send_one(): bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) + + # Other buses work similar: + #bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000) #bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) #bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) msg = can.Message(arbitration_id=0xc0ffee, data=[0, 25, 0, 1, 3, 1, 4, 1], extended_id=True) + try: bus.send(msg) print("Message sent on {}".format(bus.channel_info)) except can.CanError: print("Message NOT sent") -if __name__ == "__main__": +if __name__ == '__main__': send_one() diff --git a/examples/serial_com.py b/examples/serial_com.py index dbceb8436..efa0bcdb5 100644 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -1,4 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python +# coding: utf-8 + """ This example sends every second a messages over the serial interface and also receives incoming messages. @@ -17,10 +19,13 @@ com0com: http://com0com.sourceforge.net/ """ +from __future__ import print_function + import time -import can import threading +import can + def send_cyclic(bus, msg, stop_event): print("Start to send a message every 1s") diff --git a/examples/simpleLogConvert.py b/examples/simpleLogConvert.py index 9736ac5b8..aeb2830e7 100755 --- a/examples/simpleLogConvert.py +++ b/examples/simpleLogConvert.py @@ -1,8 +1,14 @@ #!/usr/bin/env python -# use it to convert .can-log files -# usage: simpleLogConvert.py sourceLog.asc targetLog.log +# coding: utf-8 + +""" +Use this to convert .can log files. + +Usage: simpleLogConvert.py sourceLog.asc targetLog.log +""" import sys + import can.io.logger import can.io.player diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index 99fed9fb8..86ee7f5ed 100644 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -1,14 +1,24 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This shows how message filtering works. +""" + import time + import can -bus = can.interface.Bus(bustype='socketcan', - channel='vcan0', - receive_own_messages=True) - -can_filters = [{"can_id": 1, "can_mask": 0xf, "extended": True}] -bus.set_filters(can_filters) -notifier = can.Notifier(bus, [can.Printer()]) -bus.send(can.Message(arbitration_id=1, extended_id=True)) -bus.send(can.Message(arbitration_id=2, extended_id=True)) -bus.send(can.Message(arbitration_id=1, extended_id=False)) -time.sleep(10) +if __name__ == '__main__': + bus = can.interface.Bus(bustype='socketcan', + channel='vcan0', + receive_own_messages=True) + + can_filters = [{"can_id": 1, "can_mask": 0xf, "extended": True}] + bus.set_filters(can_filters) + notifier = can.Notifier(bus, [can.Printer()]) + bus.send(can.Message(arbitration_id=1, extended_id=True)) + bus.send(can.Message(arbitration_id=2, extended_id=True)) + bus.send(can.Message(arbitration_id=1, extended_id=False)) + + time.sleep(10) diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index 8f7c499ee..8845fc8d2 100644 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# coding: utf-8 + """ This demo creates multiple processes of Producers to spam a socketcan bus. """ @@ -16,6 +19,7 @@ def producer(id): for i in range(16): msg = can.Message(arbitration_id=0x0cf02200+id, data=[id, i, 0, 1, 3, 1, 4, 1]) bus.send(msg) + # TODO Issue #3: Need to keep running to ensure the writing threads stay alive. ? time.sleep(2) From 434aa045b1135fffa1565ab1b4af311affa1cb80 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 20 Feb 2018 18:39:17 +0100 Subject: [PATCH 012/168] general cleanup of misc files & closing opened file in setup.py --- MANIFEST.in | 2 +- setup.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 2ebdd3cda..05cd56c0d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt -recursive-include doc *.rst \ No newline at end of file +recursive-include doc *.rst diff --git a/setup.py b/setup.py index 98bf4a871..339daa2e3 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# coding: utf-8 """ python-can requires the setuptools package to be installed. @@ -12,6 +13,8 @@ version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE).group(1) +with open('README.rst', 'r') as f: + long_description = f.read() logging.basicConfig(level=logging.WARNING) @@ -23,7 +26,7 @@ author="Brian Thorne", author_email="brian@thorne.link", description="Controller Area Network interface module for Python", - long_description=open('README.rst').read(), + long_description=long_description, license="LGPL v3", package_data={ "": ["CONTRIBUTORS.txt", "LICENSE.txt"], From 1fbc398933b628cce2c338656f826210c2f36306 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 21 Feb 2018 16:07:24 +0100 Subject: [PATCH 013/168] removed unused file in kvaser interface --- can/interfaces/kvaser/argument_parser.py | 34 ------------------------ 1 file changed, 34 deletions(-) delete mode 100644 can/interfaces/kvaser/argument_parser.py diff --git a/can/interfaces/kvaser/argument_parser.py b/can/interfaces/kvaser/argument_parser.py deleted file mode 100644 index 63b11cb48..000000000 --- a/can/interfaces/kvaser/argument_parser.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -""" -TODO: Where is this used? Is this used? -""" - -def add_to_parser(parser): - parser.add_argument("-c", "--channel", type=str, dest="channel", - help=""" - If the CAN interface supports multiple channels, select which one - you are after here. For example on linux this might be 1 - """, default='0') - - parser.add_argument("-b", "--bitrate", type=int, dest="bitrate", - help="CAN bus bitrate", default=1000000) - - parser.add_argument("--tseg1", type=int, dest="tseg1", - help="CAN bus tseg1", default=4) - - parser.add_argument("--tseg2", type=int, dest="tseg2", - help="CAN bus tseg2", default=3) - - parser.add_argument("--sjw", type=int, dest="sjw", - help="Synchronisation Jump Width decides the maximum number of time quanta that the controller can resynchronise every bit.", - default=1) - - parser.add_argument("-n", "--num_samples", type=int, dest="no_samp", - help="""Some CAN controllers can also sample each bit three times. - In this case, the bit will be sampled three quanta in a row, - with the last sample being taken in the edge between TSEG1 and TSEG2. - - Three samples should only be used for relatively slow baudrates.""", - default=1) From ea8e107f4ab241606717a6f0ebe172874b996fd7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 21 Feb 2018 16:21:34 +0100 Subject: [PATCH 014/168] better send_one example --- examples/send_one.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/send_one.py b/examples/send_one.py index c147cd858..ebf0d1790 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -10,12 +10,17 @@ import can def send_one(): - bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) - # Other buses work similar: - #bus = can.interface.Bus(bustype='socketcan', channel='vcan0', 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) + # this uses the default configuration (for example from the config file) + # see http://python-can.readthedocs.io/en/latest/configuration.html + bus = can.interface.Bus() + + # Using specific buses works similar: + # bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000) + # bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) + # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + # ... msg = can.Message(arbitration_id=0xc0ffee, data=[0, 25, 0, 1, 3, 1, 4, 1], From cd003889aeb8429ce3d33be1a169be0ed84add76 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 11 Feb 2018 11:51:59 +1100 Subject: [PATCH 015/168] Handle error frame messages in CanutilsLogWriter and CanutilsLogReader. Closes #217 (cherry picked from commit 61f27e1) --- test/logformats_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 019ea7147..f27ecdf1c 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -138,7 +138,6 @@ class TestCanutilsLog(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.CanutilsLogWriter, can.CanutilsLogReader, - check_error_frames=False, # TODO this should get fixed, see Issue #217 check_comments=False) @@ -147,7 +146,6 @@ class TestAscFileFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.ASCWriter, can.ASCReader, - check_error_frames=True, check_comments=True) @@ -197,7 +195,6 @@ class TestBlfFileFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.BLFWriter, can.BLFReader, - sleep_time=None, check_comments=False) def test_reader(self): From 012768358daa29c29f84ca52c63f121bca10ac1e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Feb 2018 18:36:41 +0100 Subject: [PATCH 016/168] small changes in can/io/log.py --- can/io/log.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/can/io/log.py b/can/io/log.py index 427b96a3b..3d1fccf0b 100644 --- a/can/io/log.py +++ b/can/io/log.py @@ -34,16 +34,20 @@ def __init__(self, filename): def __iter__(self): for line in self.fp: temp = line.strip() - if len(temp) > 0: + + if temp: + (timestamp, bus, frame) = temp.split() timestamp = float(timestamp[1:-1]) (canId, data) = frame.split('#') + if len(canId) > 3: isExtended = True else: isExtended = False canId = int(canId, 16) - if len(data) > 0 and data[0].lower() == 'r': + + if data and data[0].lower() == 'r': isRemoteFrame = True if len(data) > 1: dlc = int(data[1:]) @@ -57,12 +61,12 @@ def __iter__(self): for i in range(0, 2 * dlc, 2): dataBin.append(int(data[i:(i + 2)], 16)) - if canId & CAN_ERR_FLAG and canId & CAN_ERR_BUSERROR: msg = Message(timestamp=timestamp, is_error_frame=True) else: msg = Message(timestamp=timestamp, arbitration_id=canId & 0x1FFFFFFF, - extended_id=isExtended, is_remote_frame=isRemoteFrame, dlc=dlc, data=dataBin) + extended_id=isExtended, is_remote_frame=isRemoteFrame, + dlc=dlc, data=dataBin) yield msg From 40d63c9656da860652402c49c41717b2e1b736f0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Feb 2018 18:54:42 +0100 Subject: [PATCH 017/168] make ASCWriter's output consistent with the other loggers --- can/io/asc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index de3edf807..ea8d99ed6 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -128,9 +128,9 @@ def log_event(self, message, timestamp=None): return if timestamp is None: - timestamp = time.time() + timestamp = 0 - if timestamp >= self.started: + elif timestamp >= self.started: timestamp -= self.started line = self.EVENT_STRING.format(time=timestamp, message=message) From 0a6f9e5b2d97b7d8ac37f9ec631bdcded9e1bac1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Feb 2018 20:53:37 +0100 Subject: [PATCH 018/168] more info about building the docs with Sphinx --- doc/development.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/development.rst b/doc/development.rst index f7e09b671..03dbd0d0c 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -21,7 +21,8 @@ The following assumes that the commands are executed from the root of the reposi ``python setup.py install``. - The unit tests can be run with ``python setup.py test``. The tests can be run with ``python2``, ``python3``, ``pypy`` or ``pypy3`` to test with other python versions, if they are installed. -- The docs can be built with ``sphinx-build doc/ doc/_build``. +- The docs can be built with ``sphinx-build doc/ doc/_build``. Appending ``-n`` to the command + makes Sphinx complain about more subtle problems. Creating a Release From eb4f8663e241cc05810dba55c23845e963aac74c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 00:15:28 +0100 Subject: [PATCH 019/168] removed unnessesary call to abs() in SqliteReader --- can/io/sqlite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 454a09f2a..215341bee 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -54,7 +54,7 @@ def __iter__(self): def __len__(self): # this might not run in constant time result = self.cursor.execute("SELECT COUNT(*) FROM messages") - return abs(int(result.fetchone()[0])) + return int(result.fetchone()[0]) def read_all(self): """Fetches all messages in the database.""" From 4a17a5f954bdaf79afe65fa38947905931e08cc2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 01:30:52 +0100 Subject: [PATCH 020/168] renamed example file --- examples/{simpleLogConvert.py => simple_log_converter.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename examples/{simpleLogConvert.py => simple_log_converter.py} (85%) diff --git a/examples/simpleLogConvert.py b/examples/simple_log_converter.py similarity index 85% rename from examples/simpleLogConvert.py rename to examples/simple_log_converter.py index aeb2830e7..782ac9b7c 100755 --- a/examples/simpleLogConvert.py +++ b/examples/simple_log_converter.py @@ -2,7 +2,7 @@ # coding: utf-8 """ -Use this to convert .can log files. +Use this to convert .can/.asc files to .log files. Usage: simpleLogConvert.py sourceLog.asc targetLog.log """ From b0b4a75e9654ea060ff26306e9c1417dc7ca559d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 01:45:22 +0100 Subject: [PATCH 021/168] added small TODO in sqlite.py --- can/io/sqlite.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 215341bee..875044eb4 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -18,6 +18,7 @@ log = logging.getLogger('can.io.sql') +# TODO comment on this if sys.version_info > (3,): buffer = memoryview From cc069010247c77eac5c1e4e0d9a3d54ad756a437 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Sat, 18 Nov 2017 19:55:52 +0100 Subject: [PATCH 022/168] Vector: Reduce CPU usage by using Win32 events --- can/interfaces/vector/canlib.py | 51 ++++++++++++++++++++++++++--- can/interfaces/vector/exceptions.py | 5 +-- can/interfaces/vector/vxlapi.py | 17 +++++++++- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index a5093b1a8..44b72be62 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -14,6 +14,19 @@ import sys import time +try: + # Try builtin Python 3 Windows API + from _winapi import WaitForSingleObject, INFINITE + HAS_EVENTS = True +except ImportError: + try: + # Try pywin32 package + from win32event import WaitForSingleObject, INFINITE + HAS_EVENTS = True + except ImportError: + # Use polling instead + HAS_EVENTS = False + # Import Modules # ============== from can import BusABC, Message @@ -35,6 +48,7 @@ class VectorBus(BusABC): """The CAN Bus implemented for the Vector interface.""" def __init__(self, channel, can_filters=None, poll_interval=0.01, + receive_own_messages=False, bitrate=None, rx_queue_size=256, app_name="CANalyzer", **config): """ :param list channel: @@ -92,19 +106,33 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.port_handle.value, permission_mask.value) if bitrate: if permission_mask.value != self.mask: - LOG.warning('Can not set bitrate since no init access') + LOG.info('Can not set bitrate since no init access') vxlapi.xlCanSetChannelBitrate(self.port_handle, permission_mask, bitrate) + + # Enable/disable TX receipts + tx_receipts = 1 if receive_own_messages else 0 + vxlapi.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) + + if HAS_EVENTS: + self.event_handle = vxlapi.XLhandle() + vxlapi.xlSetNotification(self.port_handle, self.event_handle, 1) + else: + LOG.info('Install pywin32 to avoid polling') + self.set_filters(can_filters) + try: vxlapi.xlActivateChannel(self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0) except VectorError: self.shutdown() raise + # Calculate time offset for absolute timestamps offset = vxlapi.XLuint64() vxlapi.xlGetSyncTime(self.port_handle, offset) - self._time_offset = time.time() - offset.value / 1000000000.0 + self._time_offset = time.time() - offset.value * 1e-9 + super(VectorBus, self).__init__() def set_filters(self, can_filters=None): @@ -127,8 +155,9 @@ def set_filters(self, can_filters=None): def recv(self, timeout=None): end_time = time.time() + timeout if timeout is not None else None event = vxlapi.XLevent(0) + event_count = ctypes.c_uint() while True: - event_count = ctypes.c_uint(1) + event_count.value = 1 try: vxlapi.xlReceive(self.port_handle, event_count, event) except VectorError as exc: @@ -139,7 +168,7 @@ def recv(self, timeout=None): msg_id = event.tagData.msg.id dlc = event.tagData.msg.dlc flags = event.tagData.msg.flags - timestamp = event.timeStamp / 1000000000.0 + timestamp = event.timeStamp * 1e-9 msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, @@ -150,9 +179,21 @@ def recv(self, timeout=None): data=event.tagData.msg.data[:dlc], channel=event.chanIndex) return msg + if end_time is not None and time.time() > end_time: return None - time.sleep(self.poll_interval) + + if HAS_EVENTS: + # Wait for receive event to occur + if timeout is None: + time_left_ms = INFINITE + else: + time_left = end_time - time.time() + time_left_ms = max(0, int(time_left * 1000)) + WaitForSingleObject(self.event_handle.value, time_left_ms) + else: + # Wait a short time until we try again + time.sleep(self.poll_interval) def send(self, msg, timeout=None): message_count = ctypes.c_uint(1) diff --git a/can/interfaces/vector/exceptions.py b/can/interfaces/vector/exceptions.py index 1cbaddccd..ab50ff60d 100644 --- a/can/interfaces/vector/exceptions.py +++ b/can/interfaces/vector/exceptions.py @@ -9,6 +9,7 @@ class VectorError(CanError): - def __init__(self, error_code, error_string): + def __init__(self, error_code, error_string, function): self.error_code = error_code - super(VectorError, self).__init__(error_string) + text = "%s failed (%s)" % (function, error_string) + super(VectorError, self).__init__(text) diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index cf08c4b2a..6612351b3 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -34,12 +34,14 @@ XL_CAN_EXT_MSG_ID = 0x80000000 XL_CAN_MSG_FLAG_ERROR_FRAME = 0x01 XL_CAN_MSG_FLAG_REMOTE_FRAME = 0x10 +XL_CAN_MSG_FLAG_TX_COMPLETED = 0x40 XL_CAN_STD = 1 XL_CAN_EXT = 2 XLuint64 = ctypes.c_ulonglong XLaccess = XLuint64 +XLhandle = ctypes.c_void_p MAX_MSG_LEN = 8 @@ -75,7 +77,7 @@ class XLevent(ctypes.Structure): def check_status(result, function, arguments): if result > 0: - raise VectorError(result, xlGetErrorString(result).decode()) + raise VectorError(result, xlGetErrorString(result).decode(), function.__name__) return result @@ -123,6 +125,19 @@ def check_status(result, function, arguments): xlClosePort.restype = XLstatus xlClosePort.errcheck = check_status +xlSetNotification = _xlapi_dll.xlSetNotification +xlSetNotification.argtypes = [XLportHandle, ctypes.POINTER(XLhandle), + ctypes.c_int] +xlSetNotification.restype = XLstatus +xlSetNotification.errcheck = check_status + +xlCanSetChannelMode = _xlapi_dll.xlCanSetChannelMode +xlCanSetChannelMode.argtypes = [ + XLportHandle, XLaccess, ctypes.c_int, ctypes.c_int +] +xlCanSetChannelMode.restype = XLstatus +xlCanSetChannelMode.errcheck = check_status + xlActivateChannel = _xlapi_dll.xlActivateChannel xlActivateChannel.argtypes = [ XLportHandle, XLaccess, ctypes.c_uint, ctypes.c_uint From 190f94946d1a0435326c4f250725318e3e2fbf06 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 20 Feb 2018 12:03:52 +0100 Subject: [PATCH 023/168] Add note in documentation about pywin32 --- doc/interfaces/vector.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index c8aeb0158..a936e693e 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -19,6 +19,9 @@ application named "python-can":: channel = 0, 1 app_name = python-can +If you are using Python 2.7 it is recommended to install pywin32_, otherwise a +slow and CPU intensive polling will be used when waiting for new messages. + Bus --- @@ -29,3 +32,4 @@ Bus .. _Vector: https://vector.com/ +.. _pywin32: https://sourceforge.net/projects/pywin32/ From 782c2abb488a0f12f61dda871669a83df5ed0468 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 14:12:24 +0100 Subject: [PATCH 024/168] simpler usage of abc.* --- can/bus.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/bus.py b/can/bus.py index 05423c9c7..ede8a74d0 100644 --- a/can/bus.py +++ b/can/bus.py @@ -7,7 +7,7 @@ from __future__ import print_function, absolute_import -import abc +from abc import ABCMeta, abstractmethod import logging import threading @@ -30,7 +30,7 @@ class BusABC(object): #: a string describing the underlying bus channel channel_info = 'unknown' - @abc.abstractmethod + @abstractmethod def __init__(self, channel=None, can_filters=None, **config): """ :param channel: @@ -52,7 +52,7 @@ def __init__(self, channel=None, can_filters=None, **config): def __str__(self): return self.channel_info - @abc.abstractmethod + @abstractmethod def recv(self, timeout=None): """Block waiting for a message from the Bus. @@ -63,7 +63,7 @@ def recv(self, timeout=None): """ raise NotImplementedError("Trying to read from a write only bus?") - @abc.abstractmethod + @abstractmethod def send(self, msg, timeout=None): """Transmit a message to CAN bus. Override this method to enable the transmit path. @@ -146,4 +146,4 @@ def shutdown(self): """ self.flush_tx_buffer() - __metaclass__ = abc.ABCMeta + __metaclass__ = ABCMeta From 0bdd978b7ba3dc853845373709f94ebe5f74704d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 16:54:36 +0100 Subject: [PATCH 025/168] fix inport in util.py --- can/util.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/can/util.py b/can/util.py index 9eeb11e24..c78b9d674 100644 --- a/can/util.py +++ b/can/util.py @@ -5,21 +5,21 @@ Utilities and configuration file parsing. """ -from __future__ import absolute_import +from __future__ import absolute_import, print_function -import can -from can.interfaces import VALID_INTERFACES - -try: - from configparser import ConfigParser -except ImportError: - from ConfigParser import SafeConfigParser as ConfigParser import os import os.path import sys import platform import re import logging +try: + from configparser import ConfigParser +except ImportError: + from ConfigParser import SafeConfigParser as ConfigParser + +import can +from can.interfaces import VALID_INTERFACES log = logging.getLogger('can.util') From 72cc66cc1a4ef2d0ee5b4fd0d60eea1acac5c128 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Feb 2018 23:17:18 +0100 Subject: [PATCH 026/168] structural changes to setup.py & added 'Deprecated' library --- requirements.txt | 3 ++- setup.py | 34 +++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index f6c1a1f57..2765eba2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -pyserial +pyserial >= 3.0 +Deprecated >= 1.1.0 diff --git a/setup.py b/setup.py index 339daa2e3..c358e85bb 100644 --- a/setup.py +++ b/setup.py @@ -19,24 +19,44 @@ logging.basicConfig(level=logging.WARNING) setup( + + # Description name="python-can", url="https://github.com/hardbyte/python-can", + description="Controller Area Network interface module for Python", + long_description=long_description, + + # Code version=version, packages=find_packages(), + + # Author author="Brian Thorne", author_email="brian@thorne.link", - description="Controller Area Network interface module for Python", - long_description=long_description, + + # License license="LGPL v3", + + # Package data package_data={ "": ["CONTRIBUTORS.txt", "LICENSE.txt"], "doc": ["*.*"] }, - # Tests can be run using `python setup.py test` - test_suite="nose.collector", - tests_require=['mock', 'nose', 'pyserial'], + + # Installation + install_requires=[ + 'Deprecated >= 1.1.0', + ], extras_require={ - 'serial': ['pyserial'], + 'serial': ['pyserial >= 3.0'], 'neovi': ['python-ics'], - } + }, + + # Testing + test_suite="nose.collector", + tests_require=[ + 'mock', + 'nose', + 'pyserial >= 3.0' + ], ) From a2127dac89cf3fe6b625e955cf85ea1b8c728555 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Feb 2018 23:57:20 +0100 Subject: [PATCH 027/168] @deprecated the name SqlReader --- can/io/sqlite.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 875044eb4..463927730 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -13,6 +13,8 @@ import logging import sqlite3 +from deprecated import deprecated + from can.listener import BufferedReader from can.message import Message @@ -23,7 +25,8 @@ buffer = memoryview -class SqliteReader: +@deprecated(version='2.1', reason="Use the name SqliteReader instead") +class SqlReader: """ Reads recorded CAN messages from a simple SQL database. @@ -66,10 +69,8 @@ def close(self): """Closes the connection to the database.""" self.conn.close() - -# Backward compatibility -# TODO remove in later releases? -SqlReader = SqliteReader +# SqliteReader is the newer name +SqliteReader = SqlReader class SqliteWriter(BufferedReader): From be39d1cfbc71774423900943fd94453739af79a8 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 00:05:40 +0100 Subject: [PATCH 028/168] removed the version attribute from the deprecated decorator --- can/io/sqlite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 463927730..ecb315de1 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -25,7 +25,7 @@ buffer = memoryview -@deprecated(version='2.1', reason="Use the name SqliteReader instead") +@deprecated(reason="Use the name SqliteReader instead. (Replaced in v2.1)") class SqlReader: """ Reads recorded CAN messages from a simple SQL database. From 071152c7bc4f29313462d35969289b7906d7c0eb Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 00:13:02 +0100 Subject: [PATCH 029/168] fix wrong method call --- can/io/sqlite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index ecb315de1..f0d13ec60 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -53,7 +53,7 @@ def _create_frame_from_db_tuple(frame_data): def __iter__(self): log.debug("Iterating through messages from sql db") for frame_data in self.cursor.execute(self._SELECT_ALL_COMMAND): - yield SqliteReader._create_frame_from_db_tuple(frame_data) + yield SqlReader._create_frame_from_db_tuple(frame_data) def __len__(self): # this might not run in constant time From 67a71fc7b0bf50b598db3defca131873f067972d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 00:33:17 +0100 Subject: [PATCH 030/168] try to enable useful tests with appveyor --- .appveyor.yml | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 000000000..aab83f930 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,45 @@ +environment: + + matrix: + + # For Python versions available on Appveyor, see + # http://www.appveyor.com/docs/installed-software#python + + - PYTHON: "C:\\Python27" + - PYTHON: "C:\\Python33" + - PYTHON: "C:\\Python34" + - PYTHON: "C:\\Python35" + - PYTHON: "C:\\Python36" + - PYTHON: "C:\\Python27-x64" + - PYTHON: "C:\\Python33-x64" + - PYTHON: "C:\\Python34-x64" + - PYTHON: "C:\\Python35-x64" + - PYTHON: "C:\\Python36-x64" + +install: + # We need our usual libraries + - "%PYTHON%\\python.exe -m pip install -r requirements.txt" + # We need wheel installed to build wheels + - "%PYTHON%\\python.exe -m pip install wheel" + +build: off + +test_script: + # Put your test command here. + # Note that you must use the environment variable %PYTHON% to refer to + # the interpreter you're using - Appveyor does not do anything special + # to put the Python version you want to use on PATH. + - "%PYTHON%\\python.exe setup.py test" + +#after_test: +# # This step builds your wheels. +# - "%PYTHON%\\python.exe setup.py bdist_wheel" + +#artifacts: +# # bdist_wheel puts your built wheel in the dist directory +# - path: dist\* + +#on_success: +# You can use this step to upload your artifacts to a public website. +# See Appveyor's documentation for more details. Or you can simply +# access your wheels from the Appveyor "artifacts" tab for your build. From 6f1d7395399c3d64c2071aa2318792b7068c31a2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 00:41:32 +0100 Subject: [PATCH 031/168] try simplifying .appveyor.yml --- .appveyor.yml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index aab83f930..caacf9134 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -20,26 +20,12 @@ install: # We need our usual libraries - "%PYTHON%\\python.exe -m pip install -r requirements.txt" # We need wheel installed to build wheels - - "%PYTHON%\\python.exe -m pip install wheel" + #- "%PYTHON%\\python.exe -m pip install wheel" build: off test_script: - # Put your test command here. # Note that you must use the environment variable %PYTHON% to refer to # the interpreter you're using - Appveyor does not do anything special # to put the Python version you want to use on PATH. - "%PYTHON%\\python.exe setup.py test" - -#after_test: -# # This step builds your wheels. -# - "%PYTHON%\\python.exe setup.py bdist_wheel" - -#artifacts: -# # bdist_wheel puts your built wheel in the dist directory -# - path: dist\* - -#on_success: -# You can use this step to upload your artifacts to a public website. -# See Appveyor's documentation for more details. Or you can simply -# access your wheels from the Appveyor "artifacts" tab for your build. From e9aec8d18cb1fcc9b5c037616a81facbc1b03f79 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 01:05:57 +0100 Subject: [PATCH 032/168] factored out common Travis skipping behaviour and replaced it with generic CI skipping behaviour --- .appveyor.yml | 4 ++-- test/__init__.py | 2 ++ test/back2back_test.py | 11 ++++------- test/config.py | 24 ++++++++++++++++++++++++ test/simplecyclic_test.py | 10 ++++------ 5 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 test/__init__.py create mode 100644 test/config.py diff --git a/.appveyor.yml b/.appveyor.yml index caacf9134..58b3eebe2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,8 +19,8 @@ environment: install: # We need our usual libraries - "%PYTHON%\\python.exe -m pip install -r requirements.txt" - # We need wheel installed to build wheels - #- "%PYTHON%\\python.exe -m pip install wheel" + # We need to install the python-can library itself + - "%PYTHON%\\python.exe -m pip install ." build: off diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 000000000..394a0a067 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# coding: utf-8 diff --git a/test/back2back_test.py b/test/back2back_test.py index 85c4c2e4a..2b2f70198 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -3,22 +3,19 @@ """ This module tests two virtual busses attached to each other. - -Some tests are skipped when run on Travis CI because they are not -reproducible, see #243 (https://github.com/hardbyte/python-can/issues/243). """ -import os +from __future__ import absolute_import + import unittest import time import can -IS_TRAVIS = os.environ.get('TRAVIS', 'default') == 'true' +from .config import * BITRATE = 500000 TIMEOUT = 0.1 -TEST_CAN_FD = True INTERFACE_1 = 'virtual' CHANNEL_1 = 'vcan0' @@ -79,7 +76,7 @@ def _send_and_receive(self, msg): def test_no_message(self): self.assertIsNone(self.bus1.recv(0.1)) - @unittest.skipIf(IS_TRAVIS, "skip on Travis CI") + @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_timestamp(self): self.bus2.send(can.Message()) recv_msg1 = self.bus1.recv(TIMEOUT) diff --git a/test/config.py b/test/config.py new file mode 100644 index 000000000..a25aff627 --- /dev/null +++ b/test/config.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module contains some configuration for the tests. + +Some tests are skipped when run on a CI server because they are not +reproducible, see #243 (https://github.com/hardbyte/python-can/issues/243). +""" + +from os import environ as environment + +# see here for the environment variables that are set on the CI servers: +# - https://docs.travis-ci.com/user/environment-variables/ +# - https://www.appveyor.com/docs/environment-variables/ + +IS_TRAVIS = environment.get('TRAVIS', '').lower() == 'true' +IS_APPVEYOR = environment.get('APPVEYOR', '').lower() == 'true' + +IS_CI = IS_TRAVIS or IS_APPVEYOR or \ + environment.get('CI', '').lower() == 'true' or \ + environment.get('CONTINUOUS_INTEGRATION', '').lower() == 'true' + +TEST_CAN_FD = True diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 362e89c2c..f4ab7cab2 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -3,22 +3,20 @@ """ This module tests cyclic send tasks. - -Some tests are skipped when run on Travis CI because they are not -reproducible, see #243 (https://github.com/hardbyte/python-can/issues/243). """ -import os +from __future__ import absolute_import + from time import sleep import unittest import can -IS_TRAVIS = os.environ.get('TRAVIS', 'default') == 'true' +from .config import * class SimpleCyclicSendTaskTest(unittest.TestCase): - @unittest.skipIf(IS_TRAVIS, "skip on Travis CI") + @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_cycle_time(self): msg = can.Message(extended_id=False, arbitration_id=0x100, data=[0,1,2,3,4,5,6,7]) bus1 = can.interface.Bus(bustype='virtual') From 5e0acf5c36c6ac84b4710f5f008c9afa2b285e82 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 01:24:33 +0100 Subject: [PATCH 033/168] add appveyor badge to README.md --- README.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 8002997eb..a61ae9e6e 100644 --- a/README.rst +++ b/README.rst @@ -1,19 +1,23 @@ python-can ========== -|release| |docs| |build| +|release| |docs| |build_travis| |build_appveyor| .. |release| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ - :alt: Latest Version + :alt: Latest Version on PyPi .. |docs| image:: https://readthedocs.org/projects/python-can/badge/?version=stable :target: https://python-can.readthedocs.io/en/stable/ - :alt: Documentation Status + :alt: Documentation build Status -.. |build| image:: https://travis-ci.org/hardbyte/python-can.svg?branch=develop +.. |build_travis| image:: https://travis-ci.org/hardbyte/python-can.svg?branch=develop :target: https://travis-ci.org/hardbyte/python-can/branches - :alt: CI Server for develop branch + :alt: Travis CI Server for develop branch + +.. |build_appveyor| image:: https://ci.appveyor.com/api/projects/status/github/hardbyte/python-can?branch=develop&svg=true + :target: https://ci.appveyor.com/project/hardbyte/python-can/history + :alt: AppVeyor CI Server for develop branch The **C**\ ontroller **A**\ rea **N**\ etwork is a bus standard designed From 347fb18474c88504c032ded327a8f469eb1a493b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 01:57:06 +0100 Subject: [PATCH 034/168] fix problem from another branch so we can test here --- can/io/sqlite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index f0d13ec60..12dce3d8b 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -53,7 +53,7 @@ def _create_frame_from_db_tuple(frame_data): def __iter__(self): log.debug("Iterating through messages from sql db") for frame_data in self.cursor.execute(self._SELECT_ALL_COMMAND): - yield SqlReader._create_frame_from_db_tuple(frame_data) + yield self._create_frame_from_db_tuple(frame_data) def __len__(self): # this might not run in constant time From 4cdea042bbdcf681555435d7328e9b39a40da240 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 02:14:59 +0100 Subject: [PATCH 035/168] fix absolute imports --- test/listener_test.py | 4 +++- test/logformats_test.py | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/listener_test.py b/test/listener_test.py index 4f6d9baea..e9495c066 100755 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -4,6 +4,8 @@ """ """ +from __future__ import absolute_import + from time import sleep import unittest import random @@ -14,7 +16,7 @@ import can -from data.example_data import generate_message +from .data.example_data import generate_message channel = 'vcan0' can.rc['interface'] = 'virtual' diff --git a/test/logformats_test.py b/test/logformats_test.py index 019ea7147..728d997e3 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -12,6 +12,7 @@ """ from __future__ import print_function +from __future__ import absolute_import import unittest import tempfile @@ -28,9 +29,9 @@ import can -from data.example_data import TEST_MESSAGES_BASE, TEST_MESSAGES_REMOTE_FRAMES, \ - TEST_MESSAGES_ERROR_FRAMES, TEST_COMMENTS, \ - generate_message +from .data.example_data import TEST_MESSAGES_BASE, TEST_MESSAGES_REMOTE_FRAMES, \ + TEST_MESSAGES_ERROR_FRAMES, TEST_COMMENTS, \ + generate_message def _test_writer_and_reader(test_case, writer_constructor, reader_constructor, sleep_time=None, From 07be6b5aa0fd6bcf84e24d37b76c8866120920f8 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 23:51:36 +0100 Subject: [PATCH 036/168] warn about usage of the old CAN module. fix #267 --- can/CAN.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/can/CAN.py b/can/CAN.py index 000b7f8c2..e1d0f496f 100644 --- a/can/CAN.py +++ b/can/CAN.py @@ -5,7 +5,11 @@ This module was once the core of python-can, containing implementations of all the major classes in the library, now however all functionality has been refactored out. This API -is left intact for version 2.0 to aide with migration. +is left intact for version 2.0 to 2.3 to aide with migration. + +WARNING: +This module is deprecated an will get removed in version 2.4. +Please use `import can` instead. """ from __future__ import absolute_import @@ -18,4 +22,11 @@ import logging log = logging.getLogger('can') -log.info("Loading python-can via the old CAN api") + +# See #267 +# Version 2.0 - 2.1: Log a Debug message +# Version 2.2: Log a Warning +# Version 2.3: Log an Error +# Version 2.4: Remove the module +log.warning('Loading python-can via the old "CAN" API is deprecated since v2.0 an will get removed in v2.4. ' + 'Please use `import can` instead.') From e4569c4996517e01857a4e1cc1ab81aa819fc459 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Feb 2018 01:19:14 +0100 Subject: [PATCH 037/168] completely remove SqlReader. closes #263 --- can/io/sqlite.py | 6 +----- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 12dce3d8b..7abf4d181 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -25,8 +25,7 @@ buffer = memoryview -@deprecated(reason="Use the name SqliteReader instead. (Replaced in v2.1)") -class SqlReader: +class SqliteReader: """ Reads recorded CAN messages from a simple SQL database. @@ -69,9 +68,6 @@ def close(self): """Closes the connection to the database.""" self.conn.close() -# SqliteReader is the newer name -SqliteReader = SqlReader - class SqliteWriter(BufferedReader): """Logs received CAN data to a simple SQL database. diff --git a/requirements.txt b/requirements.txt index 2765eba2c..74371374e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ pyserial >= 3.0 -Deprecated >= 1.1.0 +#Deprecated >= 1.1.0 diff --git a/setup.py b/setup.py index c358e85bb..68ba1ad4d 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ # Installation install_requires=[ - 'Deprecated >= 1.1.0', + #'Deprecated >= 1.1.0', ], extras_require={ 'serial': ['pyserial >= 3.0'], From 1c6da93be5079bf269deee1e61f323f959bd25d7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 17:18:58 +0100 Subject: [PATCH 038/168] various improvments (e.g. docs) and added CSVReader as a placeholder --- can/io/csv.py | 6 ++++-- can/io/logger.py | 4 ++-- can/io/player.py | 30 +++++++++++++++++++++--------- can/io/stdout.py | 1 + 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/can/io/csv.py b/can/io/csv.py index 768a7a666..f6c6bcc6c 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -2,7 +2,7 @@ # coding: utf-8 """ -This modules contains the handler for CSV (comma seperated values) files. +This module contains handling for CSV (comma seperated values) files. """ import base64 @@ -11,7 +11,6 @@ # TODO allow other seperators in CSVWriter -# TODO add a CSVReader class CSVWriter(Listener): """Writes a comma separated text file of @@ -46,3 +45,6 @@ def on_message_received(self, msg): def stop(self): self.csv_file.flush() self.csv_file.close() + +class CSVReader(): + pass diff --git a/can/io/logger.py b/can/io/logger.py index 78c93f07b..1a1a6683b 100755 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -2,15 +2,15 @@ # coding: utf-8 """ -See the `Logger` class. +See the :class:`Logger` class. """ from .asc import ASCWriter from .blf import BLFWriter from .csv import CSVWriter +from .log import CanutilsLogWriter from .sqlite import SqliteWriter from .stdout import Printer -from .log import CanutilsLogWriter class Logger(object): diff --git a/can/io/player.py b/can/io/player.py index 22668f4f6..3b839a63a 100755 --- a/can/io/player.py +++ b/can/io/player.py @@ -2,6 +2,9 @@ # coding: utf-8 """ +This module contains the generic :class:`LogReader` as +well as :class:`MessageSync` which plays back messages +in the recorded order an time intervals. """ from __future__ import print_function @@ -10,8 +13,9 @@ import logging from .asc import ASCReader -from .log import CanutilsLogReader from .blf import BLFReader +from .csv import CSVReader +from .log import CanutilsLogReader from .sqlite import SqliteReader log = logging.getLogger('can.io.player') @@ -26,6 +30,7 @@ class LogReader(object): * .blf * .csv * .db + * .log Exposes a simple iterator interface, to use simply: @@ -39,22 +44,27 @@ class LogReader(object): @classmethod def __new__(cls, other, filename): - if filename.endswith(".blf"): - return BLFReader(filename) - if filename.endswith(".db"): - return SqliteReader(filename) if filename.endswith(".asc"): return ASCReader(filename) - if filename.endswith(".log"): + elif filename.endswith(".blf"): + return BLFReader(filename) + elif filename.endswith(".csv"): + return CSVReader(filename) + elif filename.endswith(".db"): + return SqliteReader(filename) + elif filename.endswith(".log"): return CanutilsLogReader(filename) - - raise NotImplementedError("No read support for this log format") + else: + raise NotImplementedError("No read support for this log format: {}".format(filename)) class MessageSync(object): + """ + Used to iterate over some given messages in the recorded time. + """ def __init__(self, messages, timestamps=True, gap=0.0001, skip=60): - """ + """Creates an new `MessageSync` instance. :param messages: An iterable of :class:`can.Message` instances. :param timestamps: Use the messages' timestamps. @@ -68,6 +78,7 @@ def __init__(self, messages, timestamps=True, gap=0.0001, skip=60): def __iter__(self): log.debug("Iterating over messages at real speed") + playback_start_time = time.time() recorded_start_time = None @@ -87,4 +98,5 @@ def __iter__(self): sleep_period = self.gap time.sleep(sleep_period) + yield m diff --git a/can/io/stdout.py b/can/io/stdout.py index 701a77c22..c0a82ab5a 100644 --- a/can/io/stdout.py +++ b/can/io/stdout.py @@ -6,6 +6,7 @@ """ from __future__ import print_function + import logging from can.listener import Listener From f6ba6b53d0cc496eb38fd7eebf04fea27fa46924 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 18:45:12 +0100 Subject: [PATCH 039/168] added CSVReader --- can/__init__.py | 2 +- can/io/__init__.py | 2 +- can/io/csv.py | 46 +++++++++++++++++++++++++++++++++++++++------- can/io/log.py | 2 +- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/can/__init__.py b/can/__init__.py index 05ad4b444..f612eeda4 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -25,7 +25,7 @@ class CanError(IOError): from can.io import ASCWriter, ASCReader from can.io import BLFReader, BLFWriter from can.io import CanutilsLogReader, CanutilsLogWriter -from can.io import CSVWriter +from can.io import CSVWriter, CSVReader from can.io import SqliteWriter, SqliteReader from can.util import set_logging_level diff --git a/can/io/__init__.py b/can/io/__init__.py index 79f187ceb..f256fb918 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -11,6 +11,6 @@ from .log import CanutilsLogReader, CanutilsLogWriter from .asc import ASCWriter, ASCReader from .blf import BLFReader, BLFWriter -from .csv import CSVWriter +from .csv import CSVWriter, CSVReader from .sqlite import SqliteReader, SqliteWriter from .stdout import Printer diff --git a/can/io/csv.py b/can/io/csv.py index f6c6bcc6c..547aba774 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -3,15 +3,20 @@ """ This module contains handling for CSV (comma seperated values) files. + +TODO: CAN FD messages are not yet supported. +TODO: This module could use https://docs.python.org/2/library/csv.html#module-csv + to allow different delimiters for writing, special escape chars to circumvent + the base64 encoding and use csv.Sniffer to automatically deduce the delimiters + of a CSV file. """ -import base64 +from base64 import b64encode, b64decode +from can.message import Message from can.listener import Listener -# TODO allow other seperators in CSVWriter - class CSVWriter(Listener): """Writes a comma separated text file of @@ -21,14 +26,15 @@ class CSVWriter(Listener): * dlc and * data - for each messages received. Each line is terminated with a '\\n'. + for each message received. Each line is terminated with a platform + specific line seperator. """ def __init__(self, filename): self.csv_file = open(filename, 'wt') # Write a header row - self.csv_file.write("timestamp, arbitration id, extended, remote, error, dlc, data\n") + self.csv_file.write("timestamp,arbitration_id,extended,remote,error,dlc,data\n") def on_message_received(self, msg): row = ','.join([ @@ -38,7 +44,7 @@ def on_message_received(self, msg): '1' if msg.is_remote_frame else '0', '1' if msg.is_error_frame else '0', str(msg.dlc), - base64.b64encode(msg.data).decode('utf8') + b64encode(msg.data).decode('utf8') ]) self.csv_file.write(row + '\n') @@ -47,4 +53,30 @@ def stop(self): self.csv_file.close() class CSVReader(): - pass + """Iterator over CAN messages from a .csv file that was + generated by :class:`~can.CSVWriter` or that uses the same + format that is described there. + """ + + def __init__(self, filename): + self.csv_file = open(filename, 'rt') + + # skip the header line + self.header_line = next(self.csv_file).split(',') + + def __iter__(self): + for line in self.csv_file: + + timestamp, arbitration_id, extended, remote, error, dlc, data = line.split(',') + + yield Message( + timestamp=float(timestamp), + is_remote_frame=(remote == '1'), + extended_id=(extended == '1'), + is_error_frame=(error == '1'), + arbitration_id=int(arbitration_id, base=16), + dlc=int(dlc), + data=b64decode(data), + ) + + self.csv_file.close() diff --git a/can/io/log.py b/can/io/log.py index 427b96a3b..f8e50c157 100644 --- a/can/io/log.py +++ b/can/io/log.py @@ -22,7 +22,7 @@ class CanutilsLogReader(object): """ - Iterator of CAN messages from a .log Logging File (candump -L). + Iterator over CAN messages from a .log Logging File (candump -L). .log-format looks like this: (0.0) vcan0 001#8d00100100820100 From bcc6164fbb5bd71527f7e5c31f57e2358aa7d8ce Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 18:46:10 +0100 Subject: [PATCH 040/168] added tests for the CSVReader & CSVWriter pair --- test/logformats_test.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 728d997e3..43ec1c4c9 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -9,6 +9,8 @@ is correct. The types of messages that are tested differs between the different writer/reader pairs - e.g., some don't handle error frames and comments. + +TODO: implement CAN FD support testing """ from __future__ import print_function @@ -148,11 +150,18 @@ class TestAscFileFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.ASCWriter, can.ASCReader, - check_error_frames=True, check_comments=True) -class TestSqlFileFormat(unittest.TestCase): +class TestCsvFileFormat(unittest.TestCase): + """Tests can.ASCWriter and can.ASCReader""" + + def test_writer_and_reader(self): + _test_writer_and_reader(self, can.CSVWriter, can.CSVReader, + check_comments=False) + + +class TestSqliteDatabaseFormat(unittest.TestCase): """Tests can.SqliteWriter and can.SqliteReader""" def test_writer_and_reader(self): @@ -198,7 +207,6 @@ class TestBlfFileFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.BLFWriter, can.BLFReader, - sleep_time=None, check_comments=False) def test_reader(self): From 2eae2992c9c9b49e2dbdfbd4ff139d6103c10c04 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 18:46:59 +0100 Subject: [PATCH 041/168] fix a rounding problem in CSVWriter --- can/io/csv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/io/csv.py b/can/io/csv.py index 547aba774..fa45bd196 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -38,7 +38,7 @@ def __init__(self, filename): def on_message_received(self, msg): row = ','.join([ - str(msg.timestamp), + repr(msg.timestamp), # cannot use str() here because that is rounding hex(msg.arbitration_id), '1' if msg.id_type else '0', '1' if msg.is_remote_frame else '0', From bd09a72d5eb0cbba85e53c10459f2f55be04e481 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 23 Feb 2018 19:35:25 +0100 Subject: [PATCH 042/168] described the CSV format --- can/io/csv.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/can/io/csv.py b/can/io/csv.py index fa45bd196..1933648ac 100644 --- a/can/io/csv.py +++ b/can/io/csv.py @@ -18,16 +18,24 @@ class CSVWriter(Listener): - """Writes a comma separated text file of - - * timestamp, - * arbitration id, - * flags (extended, remote, error), - * dlc and - * data - - for each message received. Each line is terminated with a platform - specific line seperator. + """Writes a comma separated text file with a line for + each message. + + The columns are as follows: + + ================ ======================= =============== + name of column format description example + ================ ======================= =============== + timestamp decimal float 1483389946.197 + arbitration_id hex 0x00dadada + extended 1 == True, 0 == False 1 + remote 1 == True, 0 == False 0 + error 1 == True, 0 == False 0 + dlc int 6 + data base64 encoded WzQyLCA5XQ== + ================ ======================= =============== + + Each line is terminated with a platform specific line seperator. """ def __init__(self, filename): From 6cb060a5ff5d986cef9b89fd3d395824bc04b61a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 24 Feb 2018 01:35:19 +0100 Subject: [PATCH 043/168] removed problematic import of removed library --- can/io/sqlite.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 7abf4d181..5f3255729 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -13,8 +13,6 @@ import logging import sqlite3 -from deprecated import deprecated - from can.listener import BufferedReader from can.message import Message From 1a34e0180d2cb229b7a7b9238e49f177d0fd888f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 26 Feb 2018 11:45:51 +0100 Subject: [PATCH 044/168] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a61ae9e6e..f8b1bca26 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ Python developers; providing `common abstractions to different hardware devices`, and a suite of utilities for sending and receiving messages on a can bus. -The library supports Python 2.7, Python 3.3+ as well as PyPy and runs on Mac, Linux and Windows. +The library supports Python 2.7, Python 3.3+ as well as PyPy 2 & 3 and runs on Mac, Linux and Windows. You can find more information in the documentation, online at `python-can.readthedocs.org `__. From 088423df84de599c3d1b6844ca519d883e5ebe69 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 2 Mar 2018 22:05:54 +0100 Subject: [PATCH 045/168] factored out some common import functionality --- can/interface.py | 76 ++++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/can/interface.py b/can/interface.py index 96a22c780..e983a2b78 100644 --- a/can/interface.py +++ b/can/interface.py @@ -39,6 +39,49 @@ for interface in iter_entry_points('python_can.interface') }) +def _get_class_for_configuration(channel, *args, **kwargs): + """ + Returns the main bus class for the given interface/configuration. + + :raises: TODO + """ + + # Figure out the configuration + config = load_config(config={ + 'interface': kwargs.get('bustype'), + 'channel': channel + }) + + if 'bustype' in kwargs: + # remove the bustype so it doesn't get passed to the backend + del kwargs['bustype'] + interface = config['interface'] + channel = config['channel'] + + # Find the correct backend + try: + (module_name, class_name) = BACKENDS[interface] + except KeyError: + raise NotImplementedError("CAN interface '{}' not supported".format(interface)) + + # Import the correct interface module + try: + module = importlib.import_module(module_name) + except Exception as e: + raise ImportError( + "Cannot import module {} for CAN interface '{}': {}".format(module_name, interface, e) + ) + + # Get the correct class + try: + return getattr(module, class_name) + except Exception as e: + raise ImportError( + "Cannot import class {} from module {} for CAN interface '{}': {}".format( + class_name, module_name, interface, e + ) + ) + class Bus(object): """ @@ -61,38 +104,7 @@ def __new__(cls, other, channel=None, *args, **kwargs): or set in the can.rc config. """ - config = load_config(config={ - 'interface': kwargs.get('bustype'), - 'channel': channel - }) - - if 'bustype' in kwargs: - # remove the bustype so it doesn't get passed to the backend - del kwargs['bustype'] - interface = config['interface'] - channel = config['channel'] - - # Import the correct Bus backend - try: - (module_name, class_name) = BACKENDS[interface] - except KeyError: - raise NotImplementedError("CAN interface '{}' not supported".format(interface)) - - try: - module = importlib.import_module(module_name) - except Exception as e: - raise ImportError( - "Cannot import module {} for CAN interface '{}': {}".format(module_name, interface, e) - ) - try: - cls = getattr(module, class_name) - except Exception as e: - raise ImportError( - "Cannot import class {} from module {} for CAN interface '{}': {}".format( - class_name, module_name, interface, e - ) - ) - + cls = _get_class_for_configuration(channel, args, kwargs) return cls(channel, **kwargs) From 828b10fb9eeb254e552f737359b704efec69f4d8 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 6 Mar 2018 15:22:04 +0100 Subject: [PATCH 046/168] cleaning up interface.py --- can/interface.py | 79 ++++++++++++++++++++------------------ can/interfaces/__init__.py | 1 + 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/can/interface.py b/can/interface.py index e983a2b78..281879497 100644 --- a/can/interface.py +++ b/can/interface.py @@ -11,56 +11,49 @@ import can import importlib +from pkg_resources import iter_entry_points from can.broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC -from pkg_resources import iter_entry_points from can.util import load_config + # interface_name => (module, classname) BACKENDS = { - 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), - 'socketcan_ctypes': ('can.interfaces.socketcan', 'SocketcanCtypes_Bus'), - 'socketcan_native': ('can.interfaces.socketcan', 'SocketcanNative_Bus'), - 'serial': ('can.interfaces.serial.serial_can', 'SerialBus'), - 'pcan': ('can.interfaces.pcan', 'PcanBus'), - 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), - 'ixxat': ('can.interfaces.ixxat', 'IXXATBus'), - 'nican': ('can.interfaces.nican', 'NicanBus'), - 'iscan': ('can.interfaces.iscan', 'IscanBus'), - 'virtual': ('can.interfaces.virtual', 'VirtualBus'), - 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), - 'vector': ('can.interfaces.vector', 'VectorBus'), - 'slcan': ('can.interfaces.slcan', 'slcanBus') + 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), + 'socketcan_ctypes': ('can.interfaces.socketcan', 'SocketcanCtypes_Bus'), + 'socketcan_native': ('can.interfaces.socketcan', 'SocketcanNative_Bus'), + 'serial': ('can.interfaces.serial.serial_can','SerialBus'), + 'pcan': ('can.interfaces.pcan', 'PcanBus'), + 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), + 'ixxat': ('can.interfaces.ixxat', 'IXXATBus'), + 'nican': ('can.interfaces.nican', 'NicanBus'), + 'iscan': ('can.interfaces.iscan', 'IscanBus'), + 'virtual': ('can.interfaces.virtual', 'VirtualBus'), + 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), + 'vector': ('can.interfaces.vector', 'VectorBus'), + 'slcan': ('can.interfaces.slcan', 'slcanBus') } - BACKENDS.update({ interface.name: (interface.module_name, interface.attrs[0]) for interface in iter_entry_points('python_can.interface') }) -def _get_class_for_configuration(channel, *args, **kwargs): - """ - Returns the main bus class for the given interface/configuration. - :raises: TODO +def _get_class_for_interface(interface): """ + Returns the main bus class for the given interface. - # Figure out the configuration - config = load_config(config={ - 'interface': kwargs.get('bustype'), - 'channel': channel - }) - - if 'bustype' in kwargs: - # remove the bustype so it doesn't get passed to the backend - del kwargs['bustype'] - interface = config['interface'] - channel = config['channel'] + :raises: + NotImplementedError if the interface is not known + :raises: + ImportError if there was a problem while importing the + interface or the bus class within that + """ # Find the correct backend try: - (module_name, class_name) = BACKENDS[interface] + module_name, class_name = BACKENDS[interface] except KeyError: raise NotImplementedError("CAN interface '{}' not supported".format(interface)) @@ -74,14 +67,15 @@ def _get_class_for_configuration(channel, *args, **kwargs): # Get the correct class try: - return getattr(module, class_name) + bus_class = getattr(module, class_name) except Exception as e: raise ImportError( - "Cannot import class {} from module {} for CAN interface '{}': {}".format( - class_name, module_name, interface, e - ) + "Cannot import class {} from module {} for CAN interface '{}': {}" + .format(class_name, module_name, interface, e) ) + return bus_class + class Bus(object): """ @@ -104,8 +98,19 @@ def __new__(cls, other, channel=None, *args, **kwargs): or set in the can.rc config. """ - cls = _get_class_for_configuration(channel, args, kwargs) - return cls(channel, **kwargs) + + # Figure out the configuration + config = load_config(config={ + 'interface': kwargs.get('bustype'), + 'channel': channel + }) + + # remove the bustype so it doesn't get passed to the backend + if 'bustype' in kwargs: + del kwargs['bustype'] + + cls = _get_class_for_interface(config['interface']) + return cls(channel=config['channel'], *args, **kwargs) class CyclicSendTask(CyclicSendTaskABC): diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 89ff2cdb7..b4a1e83e6 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -7,6 +7,7 @@ from pkg_resources import iter_entry_points +# TODO: isn't this a unnecessary information duplicate of `can/interface.py :: BACKENDS`? VALID_INTERFACES = set(['kvaser', 'serial', 'pcan', 'socketcan_native', 'socketcan_ctypes', 'socketcan', 'usb2can', 'ixxat', 'nican', 'iscan', 'vector', 'virtual', 'neovi', From 405d56962b5421ae62f2091315e18f2ffe559901 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 6 Mar 2018 15:27:20 +0100 Subject: [PATCH 047/168] make class Bus in interface.py extend BusABC (IDEs can now see the attributes) --- can/interface.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/can/interface.py b/can/interface.py index 281879497..2d193942d 100644 --- a/can/interface.py +++ b/can/interface.py @@ -9,12 +9,13 @@ from __future__ import absolute_import -import can import importlib from pkg_resources import iter_entry_points -from can.broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC -from can.util import load_config +import can +from .bus import BusABC +from .broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC +from .util import load_config # interface_name => (module, classname) @@ -77,7 +78,7 @@ def _get_class_for_interface(interface): return bus_class -class Bus(object): +class Bus(BusABC): """ Instantiates a CAN Bus of the given `bustype`, falls back to reading a configuration file from default locations. From e3c5910a2a51dfb5b621ba031a41ed48d1ad5f89 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 6 Mar 2018 23:21:54 +0100 Subject: [PATCH 048/168] changed BusABC to allow for software filtering and added documentation --- can/bus.py | 140 +++++++++++++++++++++++++++++++++++++++++++--------- can/util.py | 5 +- 2 files changed, 119 insertions(+), 26 deletions(-) diff --git a/can/bus.py b/can/bus.py index ede8a74d0..9a7371859 100644 --- a/can/bus.py +++ b/can/bus.py @@ -2,7 +2,7 @@ # coding: utf-8 """ -Contains the ABC bus implementation. +Contains the ABC bus implementation and documentation. """ from __future__ import print_function, absolute_import @@ -10,6 +10,7 @@ from abc import ABCMeta, abstractmethod import logging import threading +from time import time from can.broadcastmanager import ThreadBasedCyclicSendTask @@ -17,14 +18,27 @@ class BusABC(object): - """CAN Bus Abstract Base Class - Concrete implementations must implement the following methods: - * send - * recv + """CAN Bus Abstract Base Class. + + Concrete implementations *must* implement the following: + * :meth:`~can.BusABC.send` + * :meth:`~can.BusABC._recv_internal` + * set the :attr:`~can.BusABC.channel_info` attribute to a string describing + the interface and/or channel + + The *may* implement the following: + * :meth:`~can.BusABC.flush_tx_buffer` to allow discrading any + messages yet to be sent + * :meth:`~can.BusABC.shutdown` to override how the bus should + shut down, in which case the class has to either call through + `super().shutdown()` or call :meth:`~can.BusABC.flush_tx_buffer` + on its own + * :meth:`~can.BusABC.send_periodic` to override the software based + periodic sending and push it down to the kernel or hardware + * :meth:`~can.BusABC._apply_filters` to apply efficient filters + to lower level systems - As well as setting the `channel_info` attribute to a string describing the - interface. """ #: a string describing the underlying bus channel @@ -37,22 +51,16 @@ def __init__(self, channel=None, can_filters=None, **config): The can interface identifier. Expected type is backend dependent. :param list can_filters: - A list of dictionaries each containing a "can_id", a "can_mask", - and an "extended" key. - - >>> [{"can_id": 0x11, "can_mask": 0x21, "extended": False}] - - A filter matches, when `` & can_mask == can_id & can_mask`` + See :meth:`~can.BusABC.set_filters` for details. :param dict config: Any backend dependent configurations are passed in this dictionary """ - pass + self.set_filters(can_filters) def __str__(self): return self.channel_info - @abstractmethod def recv(self, timeout=None): """Block waiting for a message from the Bus. @@ -60,6 +68,41 @@ def recv(self, timeout=None): :return: None on timeout or a :class:`can.Message` object. + :raise: :class:`can.CanError` + if an error occurred while reading + """ + start = time() + + while True: + + # try to get a message + msg, already_filtered = self._recv_internal(timeout=timeout) + + # return it, if it matches + if msg and (already_filtered or self._matches_filters(msg)): + return msg + + # if not, and timeout is None, try indefinitely + elif timeout is None: + continue + + # try next one only if there still is time, and with reduced timeout + else: + + timeout = timeout - (time() - start) + + if timeout > 0: + continue + else: + return None + + @abstractmethod + def _recv_internal(self, timeout): + """ + Read a message from the bus and tell whether it was filtered. + + :raise: :class:`can.CanError` + if an error occurred while reading """ raise NotImplementedError("Trying to read from a write only bus?") @@ -92,7 +135,7 @@ def send_periodic(self, msg, period, duration=None): no duration is provided, the task will continue indefinitely. :return: A started task instance - :rtype: can.CyclicSendTaskABC + :rtype: :class:`can.CyclicSendTaskABC` Note the duration before the message stops being sent may not be exactly the same as the duration specified by the user. In @@ -101,7 +144,7 @@ def send_periodic(self, msg, period, duration=None): """ if not hasattr(self, "_lock"): - # Create send lock for this bus + # Create a send lock for this bus self._lock = threading.Lock() return ThreadBasedCyclicSendTask(self, self._lock, msg, period, duration) @@ -119,20 +162,71 @@ def __iter__(self): if msg is not None: yield msg + @property + def filters(self): + return self._can_filters + + @filters.setter + def filters(self, filters): + self.set_filters(filters) + def set_filters(self, can_filters=None): - """Apply filtering to all messages received by this Bus. + """Apply filtering to all messages received by this Bus. All messages + that match at least one filter are returned. Calling without passing any filters will reset the applied filters. :param list can_filters: - A list of dictionaries each containing a "can_id" and a "can_mask". + A list of dictionaries each containing a "can_id", a "can_mask", + and an optional "extended" key. + + >>> [{"can_id": 0x11, "can_mask": 0x21, "extended": False}] + + A filter matches, when `` & can_mask == can_id & can_mask``. + If ``extended`` is set as well, it only matches messages where + `` == extended``. Else it matches every messages based + only on the arbitration ID and mask. + + """ + if can_filters is None: + can_filters = [] + + # TODO: would it be faster to precompute `can_id & can_mask` here, and then + # instead of (can_id, can_mask, ext) store (masked_can_id, can_mask, ext)? + # Or maybe store it as a tuple/.../? for faster iteration & access? + self._can_filters = can_filters + self._apply_filters() - >>> [{"can_id": 0x11, "can_mask": 0x21}] + def _apply_filters(self): + """ + Hook for applying the filters to the underlying kernel or + hardware if supported/implemented by the interface. + """ + pass - A filter matches, when `` & can_mask == can_id & can_mask`` + def _matches_filters(self, msg): + """Checks whether the given message matches at least one of the + current filters. + See :meth:`~can.BusABC.set_filters` for details. """ - raise NotImplementedError("Trying to set_filters on unsupported bus") + + # TODO: add unit testing for this method + + for can_filter in self._can_filters: + # check if this filter even applies to the message + if 'extended' in can_filter and \ + can_filter['extended'] != msg.is_extended_id: + continue + + # then check for the mask and id + can_id = can_filter['can_id'] + can_mask = can_filter['can_mask'] + if msg.arbitration_id & can_mask == can_id & can_mask: + return True + + # nothing matched + return False def flush_tx_buffer(self): """Discard every message that may be queued in the output buffer(s). @@ -142,7 +236,7 @@ def flush_tx_buffer(self): def shutdown(self): """ Called to carry out any interface specific cleanup required - in shutting down a bus. + in shutting down a bus. Calls :meth:`~can.BusABC.flush_tx_buffer`. """ self.flush_tx_buffer() diff --git a/can/util.py b/can/util.py index c78b9d674..1759b79a6 100644 --- a/can/util.py +++ b/can/util.py @@ -191,8 +191,7 @@ def choose_socketcan_implementation(): # Check Python version: SocketCAN was added in 3.3 return 'socketcan_native' if sys.version_info >= (3, 3) else 'socketcan_ctypes' else: - msg = 'SocketCAN not available under Linux {}'.format( - rel_string) + msg = 'SocketCAN not available under Linux {}'.format(rel_string) raise Exception(msg) @@ -212,6 +211,6 @@ def set_logging_level(level_name=None): if __name__ == "__main__": print("Searching for configuration named:") print("\n".join(CONFIG_FILES)) - + print() print("Settings:") print(load_config()) From a5208ddbee5f970769c3cc9ca59b88c12fb1a43f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Mar 2018 15:39:22 +0100 Subject: [PATCH 049/168] fix proposed changes in PR --- can/bus.py | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/can/bus.py b/can/bus.py index 9a7371859..0e4d429ad 100644 --- a/can/bus.py +++ b/can/bus.py @@ -68,15 +68,16 @@ def recv(self, timeout=None): :return: None on timeout or a :class:`can.Message` object. - :raise: :class:`can.CanError` + :raises can.CanError: if an error occurred while reading """ start = time() + time_left = timeout while True: # try to get a message - msg, already_filtered = self._recv_internal(timeout=timeout) + msg, already_filtered = self._recv_internal(timeout=time_left) # return it, if it matches if msg and (already_filtered or self._matches_filters(msg)): @@ -89,9 +90,9 @@ def recv(self, timeout=None): # try next one only if there still is time, and with reduced timeout else: - timeout = timeout - (time() - start) + time_left = timeout - (time() - start) - if timeout > 0: + if time_left > 0: continue else: return None @@ -101,7 +102,7 @@ def _recv_internal(self, timeout): """ Read a message from the bus and tell whether it was filtered. - :raise: :class:`can.CanError` + :raises can.CanError: if an error occurred while reading """ raise NotImplementedError("Trying to read from a write only bus?") @@ -118,7 +119,7 @@ def send(self, msg, timeout=None): If timeout is exceeded, an exception will be raised. Might not be supported by all interfaces. - :raise: :class:`can.CanError` + :raises can.CanError: if the message could not be written. """ raise NotImplementedError("Trying to write to a readonly bus?") @@ -135,7 +136,7 @@ def send_periodic(self, msg, period, duration=None): no duration is provided, the task will continue indefinitely. :return: A started task instance - :rtype: :class:`can.CyclicSendTaskABC` + :rtype: can.CyclicSendTaskABC Note the duration before the message stops being sent may not be exactly the same as the duration specified by the user. In @@ -155,7 +156,8 @@ def __iter__(self): ... print(msg) - :yields: :class:`can.Message` msg objects. + :yields: + :class:`can.Message` msg objects. """ while True: msg = self.recv(timeout=1.0) @@ -171,13 +173,17 @@ def filters(self, filters): self.set_filters(filters) def set_filters(self, can_filters=None): - """Apply filtering to all messages received by this Bus. All messages - that match at least one filter are returned. + """Apply filtering to all messages received by this Bus. - Calling without passing any filters will reset the applied filters. + All messages that match at least one filter are returned. + If `can_filters` is `None`, all messages are matched. + If it is a zero size interable, no messages are matched. - :param list can_filters: - A list of dictionaries each containing a "can_id", a "can_mask", + Calling without passing any filters will reset the applied + filters to `None`. + + :param Iterator[dict] can_filters: + A iterable of dictionaries each containing a "can_id", a "can_mask", and an optional "extended" key. >>> [{"can_id": 0x11, "can_mask": 0x21, "extended": False}] @@ -188,9 +194,6 @@ def set_filters(self, can_filters=None): only on the arbitration ID and mask. """ - if can_filters is None: - can_filters = [] - # TODO: would it be faster to precompute `can_id & can_mask` here, and then # instead of (can_id, can_mask, ext) store (masked_can_id, can_mask, ext)? # Or maybe store it as a tuple/.../? for faster iteration & access? @@ -213,6 +216,10 @@ def _matches_filters(self, msg): # TODO: add unit testing for this method + # if no filters are set, all messages are matched + if self._can_filters is None: + return True + for can_filter in self._can_filters: # check if this filter even applies to the message if 'extended' in can_filter and \ @@ -222,7 +229,10 @@ def _matches_filters(self, msg): # then check for the mask and id can_id = can_filter['can_id'] can_mask = can_filter['can_mask'] - if msg.arbitration_id & can_mask == can_id & can_mask: + + # basically, we compute `msg.arbitration_id & can_mask == can_id & can_mask` + # by using the faster, but equivalent from below: + if (can_id ^ msg.arbitration_id) & can_mask == 0: return True # nothing matched From d829915f7421213b3b9c218101686af22dd005e6 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Mar 2018 16:02:44 +0100 Subject: [PATCH 050/168] allow older implementations of BusABC.revc() to continue working --- can/bus.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/can/bus.py b/can/bus.py index 0e4d429ad..6f2656493 100644 --- a/can/bus.py +++ b/can/bus.py @@ -12,7 +12,7 @@ import threading from time import time -from can.broadcastmanager import ThreadBasedCyclicSendTask +from .broadcastmanager import ThreadBasedCyclicSendTask logger = logging.getLogger(__name__) @@ -37,7 +37,7 @@ class BusABC(object): * :meth:`~can.BusABC.send_periodic` to override the software based periodic sending and push it down to the kernel or hardware * :meth:`~can.BusABC._apply_filters` to apply efficient filters - to lower level systems + to lower level systems like the OS kernel or hardware """ @@ -64,6 +64,10 @@ def __str__(self): def recv(self, timeout=None): """Block waiting for a message from the Bus. + If the concrete bus does not override it, this method makes sure + that all filters have been applied. That is the case for all + internal interfaces. + :param float timeout: Seconds to wait for a message. :return: @@ -97,13 +101,27 @@ def recv(self, timeout=None): else: return None - @abstractmethod def _recv_internal(self, timeout): """ Read a message from the bus and tell whether it was filtered. + This methods may be called by :meth:`~can.BusABC.recv` + to read a message multiple times if the filters set by + :meth:`~can.BusABC.set_filters` do not match and the call has + not yet timed out. + + New implementations should always override this method instead of + :meth:`~can.BusABC.recv`, to be able to take advantage of the + software based filtering provided by :meth:`~can.BusABC.recv`. + + This method is not an `@abstractmethod` for now to allow older + external implementations to continue using their existing + custom :meth:`~can.BusABC.recv` implementation. :raises can.CanError: if an error occurred while reading + :raises NotImplementedError: + if the bus provides it's own :meth:`~can.BusABC.recv` + implementation """ raise NotImplementedError("Trying to read from a write only bus?") @@ -194,9 +212,6 @@ def set_filters(self, can_filters=None): only on the arbitration ID and mask. """ - # TODO: would it be faster to precompute `can_id & can_mask` here, and then - # instead of (can_id, can_mask, ext) store (masked_can_id, can_mask, ext)? - # Or maybe store it as a tuple/.../? for faster iteration & access? self._can_filters = can_filters self._apply_filters() From 6a4aa5b3b4ce362b4c11b8effe0e08babd0270f2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 8 Mar 2018 16:05:17 +0100 Subject: [PATCH 051/168] remove unused call to BusABC.flush_tx_buffer() --- can/bus.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/can/bus.py b/can/bus.py index 6f2656493..5343225a5 100644 --- a/can/bus.py +++ b/can/bus.py @@ -18,8 +18,7 @@ class BusABC(object): - - """CAN Bus Abstract Base Class. + """The CAN Bus Abstract Base Class. Concrete implementations *must* implement the following: * :meth:`~can.BusABC.send` @@ -31,9 +30,7 @@ class BusABC(object): * :meth:`~can.BusABC.flush_tx_buffer` to allow discrading any messages yet to be sent * :meth:`~can.BusABC.shutdown` to override how the bus should - shut down, in which case the class has to either call through - `super().shutdown()` or call :meth:`~can.BusABC.flush_tx_buffer` - on its own + shut down * :meth:`~can.BusABC.send_periodic` to override the software based periodic sending and push it down to the kernel or hardware * :meth:`~can.BusABC._apply_filters` to apply efficient filters @@ -261,8 +258,8 @@ def flush_tx_buffer(self): def shutdown(self): """ Called to carry out any interface specific cleanup required - in shutting down a bus. Calls :meth:`~can.BusABC.flush_tx_buffer`. + in shutting down a bus. """ - self.flush_tx_buffer() + pass __metaclass__ = ABCMeta From 288c8b1805f45796853f56ea80a5dc265b4b58ad Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 10 Mar 2018 01:52:51 +0100 Subject: [PATCH 052/168] add some unit testing for BusABC._matches_filters() --- can/bus.py | 2 -- test/data/example_data.py | 2 ++ test/test_message_filtering.py | 58 ++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/test_message_filtering.py diff --git a/can/bus.py b/can/bus.py index 5343225a5..4738c45ce 100644 --- a/can/bus.py +++ b/can/bus.py @@ -226,8 +226,6 @@ def _matches_filters(self, msg): See :meth:`~can.BusABC.set_filters` for details. """ - # TODO: add unit testing for this method - # if no filters are set, all messages are matched if self._can_filters is None: return True diff --git a/test/data/example_data.py b/test/data/example_data.py index a66da1f36..e0ada0cde 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -106,6 +106,8 @@ ) ] +TEST_ALL_MESSAGES = TEST_MESSAGES_BASE + TEST_MESSAGES_REMOTE_FRAMES + TEST_MESSAGES_ERROR_FRAMES + TEST_COMMENTS = [ "This is the first comment", "", # empty comment diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py new file mode 100644 index 000000000..087643f59 --- /dev/null +++ b/test/test_message_filtering.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module tests :meth:`can.BusABC._matches_filters`. +""" + +from __future__ import absolute_import + +import unittest + +from can import Bus, Message + +from .data.example_data import TEST_ALL_MESSAGES + + +EXAMPLE_MSG = Message(arbitration_id=123, extended_id=True) + +MATCH_EXAMPLE = [{ + "can_id": 123, + "can_mask": 0x1FFFFFFF, + "extended": True +}] + +MATCH_ONLY_HIGHEST = [{ + "can_id": 0xFFFFFFFF, + "can_mask": 0x1FFFFFFF, + "extended": True +}] + + +class TestMessageFiltering(unittest.TestCase): + + def setUp(self): + self.bus = can.Bus(bustype='virtual', channel='testy') + + def tearDown(self): + self.bus.shutdown() + + def test_match_all(self): + # explicitly + self.bus.set_filters() + self.assertTrue(self.bus._matches_filters(EXAMPLE_MSG)) + # implicitly + self.bus.set_filters(None) + self.assertTrue(self.bus._matches_filters(EXAMPLE_MSG)) + + def test_match_nothing(self): + self.bus.set_filters([]) + for msg in TEST_ALL_MESSAGES: + self.assertFalse(self.bus._matches_filters(msg)) + + def test_match_example_message(self): + raise NotImplementedError("TODO") + + +if __name__ == '__main__': + unittest.main() From 76dd14120fc61b18a0fd1d707cac2416967a9426 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 15 Mar 2018 20:49:46 +0100 Subject: [PATCH 053/168] applied requested changes from code review --- can/interfaces/iscan.py | 6 +++--- can/interfaces/pcan/pcan.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index 6fd18b49b..8bfb89b87 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -152,11 +152,11 @@ class IscanError(CanError): def __init__(self, function, error_code, arguments): super(IscanError, self).__init__() - # Status code + # :Status code self.error_code = error_code - # Function that failed + # :Function that failed self.function = function - # Arguments passed to function + # :Arguments passed to function self.arguments = arguments def __str__(self): diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index c46d06895..4f705b302 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -86,7 +86,7 @@ def __init__(self, channel, *args, **kwargs): else: self.channel_info = channel - bitrate = kwargs.get('bitrate', can.rc.get('bitrate', 500000)) + bitrate = kwargs.get('bitrate', 500000) pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) hwtype = PCAN_TYPE_ISA From f72f2adec024fa43e1364f49ffe14ad4fb28a531 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sun, 11 Feb 2018 11:51:59 +1100 Subject: [PATCH 054/168] Handle error frame messages in CanutilsLogWriter and CanutilsLogReader. Closes #217 (cherry picked from commit 61f27e1) --- test/logformats_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 43ec1c4c9..8e5589c00 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -141,7 +141,6 @@ class TestCanutilsLog(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.CanutilsLogWriter, can.CanutilsLogReader, - check_error_frames=False, # TODO this should get fixed, see Issue #217 check_comments=False) From 472b0c7722d5eafb9f1ddbd856659a36de5089b8 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Feb 2018 18:36:41 +0100 Subject: [PATCH 055/168] small changes in can/io/log.py --- can/io/log.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/can/io/log.py b/can/io/log.py index f8e50c157..e44dd5153 100644 --- a/can/io/log.py +++ b/can/io/log.py @@ -34,16 +34,20 @@ def __init__(self, filename): def __iter__(self): for line in self.fp: temp = line.strip() - if len(temp) > 0: + + if temp: + (timestamp, bus, frame) = temp.split() timestamp = float(timestamp[1:-1]) (canId, data) = frame.split('#') + if len(canId) > 3: isExtended = True else: isExtended = False canId = int(canId, 16) - if len(data) > 0 and data[0].lower() == 'r': + + if data and data[0].lower() == 'r': isRemoteFrame = True if len(data) > 1: dlc = int(data[1:]) @@ -57,12 +61,12 @@ def __iter__(self): for i in range(0, 2 * dlc, 2): dataBin.append(int(data[i:(i + 2)], 16)) - if canId & CAN_ERR_FLAG and canId & CAN_ERR_BUSERROR: msg = Message(timestamp=timestamp, is_error_frame=True) else: msg = Message(timestamp=timestamp, arbitration_id=canId & 0x1FFFFFFF, - extended_id=isExtended, is_remote_frame=isRemoteFrame, dlc=dlc, data=dataBin) + extended_id=isExtended, is_remote_frame=isRemoteFrame, + dlc=dlc, data=dataBin) yield msg From 53abda4c8a8485c8b4c71d81cd18054e7c132ed5 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 22 Feb 2018 18:54:42 +0100 Subject: [PATCH 056/168] make ASCWriter's output consistent with the other loggers --- can/io/asc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index de3edf807..ea8d99ed6 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -128,9 +128,9 @@ def log_event(self, message, timestamp=None): return if timestamp is None: - timestamp = time.time() + timestamp = 0 - if timestamp >= self.started: + elif timestamp >= self.started: timestamp -= self.started line = self.EVENT_STRING.format(time=timestamp, message=message) From b4fed47e2f428958e62c1f03b39dc2799e5a0b1d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 15 Mar 2018 20:49:46 +0100 Subject: [PATCH 057/168] applied requested changes from code review --- can/interfaces/iscan.py | 6 +++--- can/interfaces/pcan/pcan.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index 6fd18b49b..8bfb89b87 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -152,11 +152,11 @@ class IscanError(CanError): def __init__(self, function, error_code, arguments): super(IscanError, self).__init__() - # Status code + # :Status code self.error_code = error_code - # Function that failed + # :Function that failed self.function = function - # Arguments passed to function + # :Arguments passed to function self.arguments = arguments def __str__(self): diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index c46d06895..4f705b302 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -86,7 +86,7 @@ def __init__(self, channel, *args, **kwargs): else: self.channel_info = channel - bitrate = kwargs.get('bitrate', can.rc.get('bitrate', 500000)) + bitrate = kwargs.get('bitrate', 500000) pcan_bitrate = pcan_bitrate_objs.get(bitrate, PCAN_BAUD_500K) hwtype = PCAN_TYPE_ISA From 1051f1ed5dd900a42bcd40d117d184116ba620ce Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 16 Mar 2018 13:28:15 +0100 Subject: [PATCH 058/168] Added proof-of-concept testing of SocketCAN on Travis CI & Smaller changes (#275) * added many tests in listsner_test * added platform checks and very basic SocketCanTest * added subprocess dependency for testing * testing CI builds * test CI builds * add correct version specifiers to setup.py * call setup.py correctly * better error messages in test/config.py * fix stupid error in test/config.py * fix test_timestamp unittest * removed a bazillion log messages when testing * make tests more forgiving & faster * test socketcan on Travis CI * added demo test for socketcan on Travis CI * try to fix travis builds * try to fix travis builds number 2 * fix bytes/bytearray bug in Python 2 * skip failing test and leave a note where to find the issue * added missing '@' * fix copy&paste mistake * removed setUpClass() in test case * add missing 'raise' --- .appveyor.yml | 6 ++---- .travis.yml | 4 ++-- can/interfaces/virtual.py | 4 ++-- can/io/logger.py | 10 +++++++-- can/io/player.py | 4 +++- requirements.txt | 2 -- setup.py | 26 ++++++++++++++-------- test/back2back_test.py | 45 ++++++++++++++++++++++++++++++++++----- test/config.py | 29 ++++++++++++++++++++++++- test/data/example_data.py | 2 +- test/listener_test.py | 34 ++++++++++++++++++++++++----- test/network_test.py | 7 +++--- test/open_vcan.sh | 7 ++++++ test/simplecyclic_test.py | 8 +++---- 14 files changed, 146 insertions(+), 42 deletions(-) delete mode 100644 requirements.txt create mode 100755 test/open_vcan.sh diff --git a/.appveyor.yml b/.appveyor.yml index 58b3eebe2..efa47c7d8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,10 +17,8 @@ environment: - PYTHON: "C:\\Python36-x64" install: - # We need our usual libraries - - "%PYTHON%\\python.exe -m pip install -r requirements.txt" # We need to install the python-can library itself - - "%PYTHON%\\python.exe -m pip install ." + - "%PYTHON%\\python.exe -m pip install .[test]" build: off @@ -28,4 +26,4 @@ test_script: # Note that you must use the environment variable %PYTHON% to refer to # the interpreter you're using - Appveyor does not do anything special # to put the Python version you want to use on PATH. - - "%PYTHON%\\python.exe setup.py test" + - "%PYTHON%\\python.exe setup.py test -v" diff --git a/.travis.yml b/.travis.yml index 5ccaa3ff7..0a26c32bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,8 +43,8 @@ matrix: - python: "nightly" install: - - travis_retry pip install . - - travis_retry pip install -r requirements.txt + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi + - travis_retry pip install .[test] script: - py.test -v diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 2a7186b77..0f68e3438 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -46,7 +46,7 @@ def recv(self, timeout=None): except queue.Empty: return None - logger.log(9, 'Received message:\n%s', msg) + #logger.log(9, 'Received message:\n%s', msg) return msg def send(self, msg, timeout=None): @@ -55,7 +55,7 @@ def send(self, msg, timeout=None): for bus_queue in self.channel: if bus_queue is not self.queue or self.receive_own_messages: bus_queue.put(msg) - logger.log(9, 'Transmitted message:\n%s', msg) + #logger.log(9, 'Transmitted message:\n%s', msg) def shutdown(self): self.channel.remove(self.queue) diff --git a/can/io/logger.py b/can/io/logger.py index 1a1a6683b..9be09d82f 100755 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -5,6 +5,8 @@ See the :class:`Logger` class. """ +import logging + from .asc import ASCWriter from .blf import BLFWriter from .csv import CSVWriter @@ -12,6 +14,8 @@ from .sqlite import SqliteWriter from .stdout import Printer +log = logging.getLogger("can.io.logger") + class Logger(object): """ @@ -22,6 +26,7 @@ class Logger(object): * .blf :class:`can.BLFWriter` * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` + * .log :class:`can.CanutilsLogWriter` * other: :class:`can.Printer` Note this class itself is just a dispatcher, @@ -35,13 +40,14 @@ def __new__(cls, other, filename): return Printer() elif filename.endswith(".asc"): return ASCWriter(filename) - elif filename.endswith(".log"): - return CanutilsLogWriter(filename) elif filename.endswith(".blf"): return BLFWriter(filename) elif filename.endswith(".csv"): return CSVWriter(filename) elif filename.endswith(".db"): return SqliteWriter(filename) + elif filename.endswith(".log"): + return CanutilsLogWriter(filename) else: + log.info('unknown file type "%s", falling pack to can.Printer', filename) return Printer(filename) diff --git a/can/io/player.py b/can/io/player.py index 3b839a63a..4d4f17a93 100755 --- a/can/io/player.py +++ b/can/io/player.py @@ -44,7 +44,9 @@ class LogReader(object): @classmethod def __new__(cls, other, filename): - if filename.endswith(".asc"): + if not filename: + raise TypeError("a filename must be given") + elif filename.endswith(".asc"): return ASCReader(filename) elif filename.endswith(".blf"): return BLFReader(filename) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 74371374e..000000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pyserial >= 3.0 -#Deprecated >= 1.1.0 diff --git a/setup.py b/setup.py index 68ba1ad4d..03ee7945f 100644 --- a/setup.py +++ b/setup.py @@ -5,10 +5,13 @@ python-can requires the setuptools package to be installed. """ +from sys import version_info import re import logging from setuptools import setup, find_packages +logging.basicConfig(level=logging.WARNING) + with open('can/__init__.py', 'r') as fd: version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE).group(1) @@ -16,10 +19,15 @@ with open('README.rst', 'r') as f: long_description = f.read() -logging.basicConfig(level=logging.WARNING) +tests_require = [ + 'mock', + 'nose', + 'pyserial >= 3.0', +] +if version_info.major < 3: + tests_require += ['subprocess32 >= 3.2'] setup( - # Description name="python-can", url="https://github.com/hardbyte/python-can", @@ -29,7 +37,9 @@ # Code version=version, packages=find_packages(), - + # see https://www.python.org/dev/peps/pep-0345/#version-specifiers + python_requires='>=2.7,!=3.0,!=3.1,!=3.2', + # Author author="Brian Thorne", author_email="brian@thorne.link", @@ -45,18 +55,16 @@ # Installation install_requires=[ + 'setuptools', #'Deprecated >= 1.1.0', ], extras_require={ 'serial': ['pyserial >= 3.0'], - 'neovi': ['python-ics'], + 'neovi': ['python-ics >= 2.8'], + 'test': tests_require }, # Testing test_suite="nose.collector", - tests_require=[ - 'mock', - 'nose', - 'pyserial >= 3.0' - ], + tests_require=tests_require, ) diff --git a/test/back2back_test.py b/test/back2back_test.py index 2b2f70198..5a2ab07e4 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -5,14 +5,16 @@ This module tests two virtual busses attached to each other. """ -from __future__ import absolute_import +from __future__ import absolute_import, print_function +import sys import unittest -import time +from time import sleep import can from .config import * +from .data.example_data import generate_message BITRATE = 500000 TIMEOUT = 0.1 @@ -80,12 +82,12 @@ def test_no_message(self): def test_timestamp(self): self.bus2.send(can.Message()) recv_msg1 = self.bus1.recv(TIMEOUT) - time.sleep(5) + sleep(2.0) self.bus2.send(can.Message()) recv_msg2 = self.bus1.recv(TIMEOUT) delta_time = recv_msg2.timestamp - recv_msg1.timestamp - self.assertTrue(4.8 < delta_time < 5.2, - 'Time difference should have been 5s +/- 200ms.' + self.assertTrue(1.75 <= delta_time <= 2.25, + 'Time difference should have been 2s +/- 250ms.' 'But measured {}'.format(delta_time)) def test_standard_message(self): @@ -130,6 +132,39 @@ def test_fd_message_with_brs(self): data=[0xff] * 48) self._send_and_receive(msg) +# FIXME +@unittest.skip("skip until CAN FD support is fixed, see issue #274") +#@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +class BasicTestSocketCan(unittest.TestCase): + + def setUp(self): + socketcan_version = can.util.choose_socketcan_implementation() + print("testing python-can's socketcan version:", + socketcan_version) + + self.bus1 = can.interface.Bus(channel="vcan0", + bustype=socketcan_version, + bitrate=250000, + fd=TEST_CAN_FD) + self.bus2 = can.interface.Bus(channel="vcan0", + bustype=socketcan_version, + bitrate=250000, + fd=TEST_CAN_FD) + + def tearDown(self): + self.bus1.shutdown() + self.bus2.shutdown() + + def test_basics(self): + reader = can.BufferedReader() + notifier = can.Notifier(self.bus2, [reader]) + + message = can.Message(arbitration_id=0x4321, data=[1, 2, 3], extended_id=True) + self.bus1.send(message) + + self.assertEqual(message, reader.get_message(timeout=2.0)) + notifier.stop() + if __name__ == '__main__': unittest.main() diff --git a/test/config.py b/test/config.py index a25aff627..a48d8c72c 100644 --- a/test/config.py +++ b/test/config.py @@ -2,14 +2,18 @@ # coding: utf-8 """ -This module contains some configuration for the tests. +This module contains various configuration for the tests. Some tests are skipped when run on a CI server because they are not reproducible, see #243 (https://github.com/hardbyte/python-can/issues/243). """ +import platform from os import environ as environment + +# ############################## Continuos integration + # see here for the environment variables that are set on the CI servers: # - https://docs.travis-ci.com/user/environment-variables/ # - https://www.appveyor.com/docs/environment-variables/ @@ -21,4 +25,27 @@ environment.get('CI', '').lower() == 'true' or \ environment.get('CONTINUOUS_INTEGRATION', '').lower() == 'true' +if IS_APPVEYOR and IS_TRAVIS: + raise EnvironmentError("IS_APPVEYOR and IS_TRAVIS cannot be both True at the same time") + +# ############################## Platforms + +_sys = platform.system().lower() +IS_WINDOWS = "windows" in _sys or ("win" in _sys and "darwin" not in _sys) +IS_LINUX = "linux" in _sys +IS_OSX = "darwin" in _sys + +if (IS_WINDOWS and IS_LINUX) or (IS_LINUX and IS_OSX) or (IS_WINDOWS and IS_OSX): + raise EnvironmentError( + "only one of IS_WINDOWS ({}), IS_LINUX ({}) and IS_OSX ({}) ".format(IS_WINDOWS, IS_LINUX, IS_OSX) + + "can be True at the same time " + + '(platform.system() == "{}")'.format(platform.system()) + ) +elif not IS_WINDOWS and not IS_LINUX and not IS_OSX: + raise EnvironmentError("one of IS_WINDOWS, IS_LINUX, IS_OSX has to be True") + +# ############################## What tests to run + TEST_CAN_FD = True + +TEST_INTERFACE_SOCKETCAN = IS_CI and IS_LINUX diff --git a/test/data/example_data.py b/test/data/example_data.py index a66da1f36..b84aa2c4e 100644 --- a/test/data/example_data.py +++ b/test/data/example_data.py @@ -124,5 +124,5 @@ def generate_message(arbitration_id): Generates a new message with the given ID, some random data and a non-extended ID. """ - data = bytes([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) + data = bytearray([random.randrange(0, 2 ** 8 - 1) for _ in range(8)]) return Message(arbitration_id=arbitration_id, data=data, extended_id=False) diff --git a/test/listener_test.py b/test/listener_test.py index e9495c066..f13a5f17a 100755 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -41,6 +41,7 @@ def testClassesImportable(self): self.assertTrue(hasattr(can, 'BLFReader')) self.assertTrue(hasattr(can, 'BLFWriter')) + self.assertTrue(hasattr(can, 'CSVReader')) self.assertTrue(hasattr(can, 'CSVWriter')) self.assertTrue(hasattr(can, 'CanutilsLogWriter')) @@ -73,23 +74,46 @@ def testBasicListenerCanBeAddedToNotifier(self): notifier.stop() self.assertIn(a_listener, notifier.listeners) - def testLogger(self): + def testPlayerTypeResolution(self): + def test_filetype_to_instance(extension, klass): + can_player = can.LogReader("test.{}".format(extension)) + self.assertIsInstance(can_player, klass) + if hasattr(can_player, "stop"): + can_player.stop() + + test_filetype_to_instance("asc", can.ASCReader) + test_filetype_to_instance("blf", can.BLFReader) + test_filetype_to_instance("csv", can.CSVReader) + test_filetype_to_instance("db" , can.SqliteReader) + test_filetype_to_instance("log", can.CanutilsLogReader) + + # test file extensions that are not supported + with self.assertRaisesRegexp(NotImplementedError, "xyz_42"): + test_filetype_to_instance("xyz_42", can.Printer) + with self.assertRaises(BaseException): + test_filetype_to_instance(None, can.Printer) + + def testLoggerTypeResolution(self): def test_filetype_to_instance(extension, klass): can_logger = can.Logger("test.{}".format(extension)) self.assertIsInstance(can_logger, klass) can_logger.stop() - test_filetype_to_instance('asc', can.ASCWriter) - test_filetype_to_instance('log', can.CanutilsLogWriter) + test_filetype_to_instance("asc", can.ASCWriter) test_filetype_to_instance("blf", can.BLFWriter) test_filetype_to_instance("csv", can.CSVWriter) - test_filetype_to_instance("db", can.SqliteWriter) + test_filetype_to_instance("db" , can.SqliteWriter) + test_filetype_to_instance("log", can.CanutilsLogWriter) test_filetype_to_instance("txt", can.Printer) + # test file extensions that should usa a fallback + test_filetype_to_instance(None, can.Printer) + test_filetype_to_instance("some_unknown_extention_42", can.Printer) + def testBufferedListenerReceives(self): a_listener = can.BufferedReader() a_listener(generate_message(0xDADADA)) - m = a_listener.get_message(0.2) + m = a_listener.get_message(0.1) self.assertIsNotNone(m) diff --git a/test/network_test.py b/test/network_test.py index ae6c9a81a..830adceca 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -58,7 +58,7 @@ def producer(self, ready_event, msg_read): extended_id=self.extended_flags[i], data=self.data[i] ) - logging.debug("writing message: {}".format(m)) + #logging.debug("writing message: {}".format(m)) if msg_read is not None: # Don't send until the other thread is ready msg_read.wait() @@ -92,15 +92,14 @@ def testProducerConsumer(self): print("No messages... lets go") break else: - print("received messages before the test has started...") - self.assertTrue(False) + self.fail("received messages before the test has started ...") ready.set() i = 0 while i < self.num_messages: msg_read.set() msg = self.server_bus.recv(timeout=0.5) self.assertIsNotNone(msg, "Didn't receive a message") - logging.debug("Received message {} with data: {}".format(i, msg.data)) + #logging.debug("Received message {} with data: {}".format(i, msg.data)) self.assertEqual(msg.id_type, self.extended_flags[i]) if not msg.is_remote_frame: diff --git a/test/open_vcan.sh b/test/open_vcan.sh new file mode 100755 index 000000000..0e9ba95c1 --- /dev/null +++ b/test/open_vcan.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Used by .travis.yml (which is executed with sudo privileges) + +modprobe vcan +ip link add dev vcan0 type vcan +ip link set up vcan0 diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index f4ab7cab2..8763174a8 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -18,17 +18,17 @@ class SimpleCyclicSendTaskTest(unittest.TestCase): @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_cycle_time(self): - msg = can.Message(extended_id=False, arbitration_id=0x100, data=[0,1,2,3,4,5,6,7]) + msg = can.Message(extended_id=False, arbitration_id=0x123, data=[0,1,2,3,4,5,6,7]) bus1 = can.interface.Bus(bustype='virtual') bus2 = can.interface.Bus(bustype='virtual') task = bus1.send_periodic(msg, 0.01, 1) self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) - sleep(5) + sleep(2) size = bus2.queue.qsize() # About 100 messages should have been transmitted - self.assertTrue(90 < size < 110, - '100 +/- 10 messages should have been transmitted. But queue contained {}'.format(size)) + self.assertTrue(80 <= size <= 120, + '100 +/- 20 messages should have been transmitted. But queue contained {}'.format(size)) last_msg = bus2.recv() self.assertEqual(last_msg, msg) From 9db2b2787ebe78cbb9fba3558596255a970d8579 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 16 Mar 2018 14:00:05 +0100 Subject: [PATCH 059/168] can.Notifier enhancements (#266) * prepended an underscore to private fields/methdos of can.Notifier * added add_listener and remove_listener to can.Notifier. closes #265 * added tests for can.Notifier.add_listener() and remove_listener() * added tests for can.Notifier.add_listener() and remove_listener() * fix an import problem that was cuased by pervious merge * attempt fixing Sqlite tests --- can/notifier.py | 41 ++++++++++++++++++++++++++++++++--------- test/back2back_test.py | 3 +++ test/listener_test.py | 16 ++++++++++++++++ test/logformats_test.py | 2 +- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index a3eaad6e5..c3ca1118b 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -2,7 +2,7 @@ # coding: utf-8 """ -This module contains the implementation of `can.Notifier`. +This module contains the implementation of :class:`~can.Notifier`. """ import threading @@ -18,7 +18,7 @@ def __init__(self, bus, listeners, timeout=None): list of listeners. :param bus: The :ref:`bus` to listen too. - :param listeners: An iterable of :class:`~can.Listeners` + :param listeners: An iterable of :class:`~can.Listener`s :param timeout: An optional maximum number of seconds to wait for any message. """ self.listeners = listeners @@ -28,23 +28,25 @@ def __init__(self, bus, listeners, timeout=None): # exception raised in thread self.exception = None - self.running = threading.Event() - self.running.set() + self._running = threading.Event() + self._running.set() - self._reader = threading.Thread(target=self.rx_thread, name="can.notifier") + self._reader = threading.Thread(target=self._rx_thread, + name='can.notifier for bus "{}"'.format(self.bus.channel_info)) self._reader.daemon = True self._reader.start() def stop(self): """Stop notifying Listeners when new :class:`~can.Message` objects arrive - and call :meth:`~can.Listener.stop` on each Listener.""" - self.running.clear() + and call :meth:`~can.Listener.stop` on each Listener. + """ + self._running.clear() if self.timeout is not None: self._reader.join(self.timeout + 0.1) - def rx_thread(self): + def _rx_thread(self): try: - while self.running.is_set(): + while self._running.is_set(): msg = self.bus.recv(self.timeout) if msg is not None: for callback in self.listeners: @@ -55,3 +57,24 @@ def rx_thread(self): finally: for listener in self.listeners: listener.stop() + + def add_listener(self, listener): + """Add new Listener to the notification list. + If it is already present, it will be called two times + each time a message arrives. + + :param listener: a :class:`~can.Listener` object to be added to + the list to be notified + """ + self.listeners.append(listener) + + def remove_listener(self, listener): + """Remove a listener from the notification list. This method + trows an exception if the given listener is not part of the + stored listeners. + + :param listener: a :class:`~can.Listener` object to be removed from + the list to be notified + :raises ValueError: if `listener` was never added to this notifier + """ + self.listeners.remove(listener) diff --git a/test/back2back_test.py b/test/back2back_test.py index 5a2ab07e4..b1ff537c0 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -13,9 +13,12 @@ import can +from .data.example_data import generate_message + from .config import * from .data.example_data import generate_message + BITRATE = 500000 TIMEOUT = 0.1 diff --git a/test/listener_test.py b/test/listener_test.py index f13a5f17a..2df0ab9a8 100755 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -73,6 +73,22 @@ def testBasicListenerCanBeAddedToNotifier(self): notifier = can.Notifier(self.bus, [a_listener], 0.1) notifier.stop() self.assertIn(a_listener, notifier.listeners) + + def testAddListenerToNotifier(self): + a_listener = can.Listener() + notifier = can.Notifier(self.bus, [], 0.1) + notifier.stop() + self.assertNotIn(a_listener, notifier.listeners) + notifier.add_listener(a_listener) + self.assertIn(a_listener, notifier.listeners) + + def testRemoveListenerFromNotifier(self): + a_listener = can.Listener() + notifier = can.Notifier(self.bus, [a_listener], 0.1) + notifier.stop() + self.assertIn(a_listener, notifier.listeners) + notifier.remove_listener(a_listener) + self.assertNotIn(a_listener, notifier.listeners) def testPlayerTypeResolution(self): def test_filetype_to_instance(extension, klass): diff --git a/test/logformats_test.py b/test/logformats_test.py index 43ec1c4c9..c15511525 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -166,7 +166,7 @@ class TestSqliteDatabaseFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.SqliteWriter, can.SqliteReader, - sleep_time=can.SqliteWriter.MAX_TIME_BETWEEN_WRITES, + sleep_time=can.SqliteWriter.MAX_TIME_BETWEEN_WRITES + 0.5, check_comments=False) def testSQLWriterWritesToSameFile(self): From db63418f5edd480cde554f8e3a25f4653c70797a Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 5 Mar 2018 13:50:25 -0500 Subject: [PATCH 060/168] Remove `load_default_settings` call in init `load_default_settings` is not supported in the open source version of icsneoapi --- can/interfaces/ics_neovi/neovi_bus.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index dd49b04ce..6fda30f0e 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -106,8 +106,6 @@ def __init__(self, channel=None, can_filters=None, **config): ) logger.info("Using device: {}".format(self.channel_info)) - ics.load_default_settings(self.dev) - self.sw_filters = None self.set_filters(can_filters) self.rx_buffer = deque() From 79d8d3854ab816b5df0db08fd270d58fa4792f61 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 17 Mar 2018 14:24:57 +0100 Subject: [PATCH 061/168] changed CanutilsLogWriter and ASCWriter to behave more similar --- can/io/asc.py | 48 ++++++++++++++++++++++++++++++++++++++---------- can/io/log.py | 43 +++++++++++++++++++++++++++++++------------ 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index ea8d99ed6..4866c311f 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -3,6 +3,8 @@ """ Contains handling of ASC logging files. + +Example .asc file: https://bitbucket.org/tobylorenz/vector_asc/src/47556e1a6d32c859224ca62d075e1efcc67fa690/src/Vector/ASC/tests/unittests/data/CAN_Log_Trigger_3_2.asc?at=master&fileviewer=file-view-default """ from datetime import datetime @@ -21,6 +23,8 @@ class ASCReader(object): """ Iterator of CAN messages from a ASC logging file. + + TODO: turn realtive timestamps back to absolute form """ def __init__(self, filename): @@ -28,7 +32,7 @@ def __init__(self, filename): @staticmethod def _extract_can_id(str_can_id): - if str_can_id[-1:].lower() == "x": + if str_can_id[-1:].lower() == 'x': is_extended = True can_id = int(str_can_id[0:-1], 16) else: @@ -98,21 +102,33 @@ def __iter__(self): class ASCWriter(Listener): - """Logs CAN data to an ASCII log file (.asc)""" + """Logs CAN data to an ASCII log file (.asc). + + The measurement starts with the timestamp of the first registered message. + If a message has a timestamp smaller than the previous one (or 0 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. + """ LOG_STRING = "{time: 9.4f} {channel} {id:<15} Rx {dtype} {data}\n" EVENT_STRING = "{time: 9.4f} {message}\n" def __init__(self, filename, channel=1): - now = datetime.now().strftime("%a %b %m %I:%M:%S %p %Y") + # setup self.channel = channel self.started = time.time() self.log_file = open(filename, 'w') + + # write start of file header + now = datetime.now().strftime("%a %b %m %I:%M:%S %p %Y") self.log_file.write("date %s\n" % now) self.log_file.write("base hex timestamps absolute\n") self.log_file.write("internal events logged\n") - self.log_file.write("Begin Triggerblock %s\n" % now) - self.log_event("Start of measurement") + + # the last part is written with the timestamp of the first message + self.header_written = False + self.last_timestamp = None + self.started = None def stop(self): """Stops logging and closes the file.""" @@ -127,9 +143,21 @@ def log_event(self, message, timestamp=None): logger.debug("ASCWriter: ignoring empty message") return - if timestamp is None: - timestamp = 0 + # this is the case for the very first message: + if not self.header_written: + self.last_timestamp = (timestamp or 0.0) + self.started = self.last_timestamp + self.log_file.write("Begin Triggerblock %s\n" % self.last_timestamp) + self.log_event("Start of measurement") + self.header_written = True + + # figure out the correct timestamp + if msg.timestamp is None or msg.timestamp < self.last_timestamp: + timestamp = self.last_timestamp + else: + timestamp = msg.timestamp + # turn into relative timestamps elif timestamp >= self.started: timestamp -= self.started @@ -145,7 +173,7 @@ def on_message_received(self, msg): return if msg.is_remote_frame: - dtype = "r" + dtype = 'r' data = [] else: dtype = "d {}".format(msg.dlc) @@ -153,7 +181,7 @@ def on_message_received(self, msg): arb_id = "{:X}".format(msg.arbitration_id) if msg.is_extended_id: - arb_id += "x" + arb_id += 'x' channel = msg.channel if isinstance(msg.channel, int) else self.channel @@ -161,7 +189,7 @@ def on_message_received(self, msg): channel=channel, id=arb_id, dtype=dtype, - data=" ".join(data)) + data=' '.join(data)) if not self.log_file.closed: self.log_file.write(line) diff --git a/can/io/log.py b/can/io/log.py index e44dd5153..e8a822f34 100644 --- a/can/io/log.py +++ b/can/io/log.py @@ -5,15 +5,20 @@ This module works with CAN data in ASCII log files (*.log). It is is compatible with "candump -L" from the canutils program (https://github.com/linux-can/can-utils). + +TODO: "channel" is not uesed by CanutilsLogWriter. Is that supposed to be like that? """ import time import datetime +import logging from can.message import Message from can.listener import Listener +log = logging.getLogger('can.io.canutils') + CAN_MSG_EXT = 0x80000000 CAN_ERR_FLAG = 0x20000000 CAN_ERR_BUSERROR = 0x00000080 @@ -73,39 +78,53 @@ def __iter__(self): class CanutilsLogWriter(Listener): """Logs CAN data to an ASCII log file (.log). This class is is compatible with "candump -L". + + If a message has a timestamp smaller than the previous one (or 0 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, filename, channel="vcan0"): self.channel = channel - self.started = time.time() self.log_file = open(filename, 'w') + self.last_timestamp = None def stop(self): """Stops logging and closes the file.""" if self.log_file is not None: self.log_file.close() self.log_file = None + else: + log.warn("ignoring attempt to colse a already closed file") def on_message_received(self, msg): if self.log_file is None: - return - if msg.is_error_frame: - self.log_file.write("(%f) vcan0 %08X#0000000000000000\n" % (msg.timestamp, CAN_ERR_FLAG | CAN_ERR_BUSERROR, )) + log.warn("ignoring write attempt to closed file") return - timestamp = msg.timestamp - if timestamp >= self.started: - timestamp -= self.started + # this is the case for the very first message: + if self.last_timestamp is None: + self.last_timestamp = (msg.timestamp or 0.0) - if msg.is_remote_frame: + # figure out the correct timestamp + if msg.timestamp is None or msg.timestamp < self.last_timestamp: + timestamp = self.last_timestamp + else: + timestamp = msg.timestamp + + if msg.is_error_frame: + self.log_file.write("(%f) vcan0 %08X#0000000000000000\n" % (timestamp, CAN_ERR_FLAG | CAN_ERR_BUSERROR)) + + elif msg.is_remote_frame: data = [] if msg.is_extended_id: - self.log_file.write("(%f) vcan0 %08X#R\n" % (msg.timestamp, msg.arbitration_id )) + self.log_file.write("(%f) vcan0 %08X#R\n" % (timestamp, msg.arbitration_id)) else: - self.log_file.write("(%f) vcan0 %03X#R\n" % (msg.timestamp, msg.arbitration_id )) + self.log_file.write("(%f) vcan0 %03X#R\n" % (timestamp, msg.arbitration_id)) + else: data = ["{:02X}".format(byte) for byte in msg.data] if msg.is_extended_id: - self.log_file.write("(%f) vcan0 %08X#%s\n" % (msg.timestamp, msg.arbitration_id, "".join(data))) + self.log_file.write("(%f) vcan0 %08X#%s\n" % (timestamp, msg.arbitration_id, ''.join(data))) else: - self.log_file.write("(%f) vcan0 %03X#%s\n" % (msg.timestamp, msg.arbitration_id, "".join(data))) + self.log_file.write("(%f) vcan0 %03X#%s\n" % (timestamp, msg.arbitration_id, ''.join(data))) From d1a6b16fcbcfd15082eaae9c47b1b6fd7fd20e3d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 17 Mar 2018 14:32:20 +0100 Subject: [PATCH 062/168] small fixes --- can/io/asc.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 4866c311f..6660d21dc 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -148,17 +148,15 @@ def log_event(self, message, timestamp=None): self.last_timestamp = (timestamp or 0.0) self.started = self.last_timestamp self.log_file.write("Begin Triggerblock %s\n" % self.last_timestamp) - self.log_event("Start of measurement") self.header_written = True + self.log_event("Start of measurement") # recursive # figure out the correct timestamp - if msg.timestamp is None or msg.timestamp < self.last_timestamp: + if timestamp is None or timestamp < self.last_timestamp: timestamp = self.last_timestamp - else: - timestamp = msg.timestamp # turn into relative timestamps - elif timestamp >= self.started: + if timestamp >= self.started: timestamp -= self.started line = self.EVENT_STRING.format(time=timestamp, message=message) From 58e9f513136bbe97e3d8e1851a6293a965068a98 Mon Sep 17 00:00:00 2001 From: Fabian Henze Date: Thu, 22 Mar 2018 17:48:33 +0100 Subject: [PATCH 063/168] Support using the Windows IXXAT driver in Cygwin --- can/interfaces/ixxat/canlib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index cb0ea9176..5013f60e1 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -44,6 +44,11 @@ _canlib = CLibrary("vcinpl") except Exception as e: log.warning("Cannot load IXXAT vcinpl library: %s", e) +elif sys.platform == "cygwin": + try: + _canlib = CLibrary("vcinpl.dll") + except Exception as e: + log.warning("Cannot load IXXAT vcinpl library: %s", e) else: # Will not work on other systems, but have it importable anyway for # tests/sphinx From f73f850b17d6541f7683c4bac64affb693ff1ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Fri, 23 Mar 2018 09:45:32 -0400 Subject: [PATCH 064/168] Adding bitrate settings for neoVI interface --- can/interfaces/ics_neovi/neovi_bus.py | 87 +++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 6fda30f0e..7cfdd6595 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -58,6 +58,23 @@ def is_critical(self): return self.severity == self.ICS_SPY_ERR_CRITICAL +BAUDRATE_SETTING = { + 20000: 0, + 33333: 1, + 50000: 2, + 62500: 3, + 83333: 4, + 100000: 5, + 125000: 6, + 250000: 7, + 500000: 8, + 800000: 9, + 1000000: 10, +} +VALID_BITRATES = list(BAUDRATE_SETTING.keys()) +VALID_BITRATES.sort() + + class NeoViBus(BusABC): """ The CAN Bus implemented for the python_ics interface @@ -71,12 +88,18 @@ def __init__(self, channel=None, can_filters=None, **config): The Channel id to create this bus with. :param list can_filters: A list of dictionaries each containing a "can_id" and a "can_mask". + + >>> [{"can_id": 0x11, "can_mask": 0x21}] + :param use_system_timestamp: Use system timestamp for can messages instead of the hardware time stamp - - >>> [{"can_id": 0x11, "can_mask": 0x21}] - + :param str serial: + Serial to connect (optional, will use the first found if not + supplied) + :param int bitrate: + Channel bitrate in bit/s. (optional, will enable the auto bitrate + feature if not supplied) """ super(NeoViBus, self).__init__(channel, can_filters, **config) if ics is None: @@ -97,7 +120,32 @@ def __init__(self, channel=None, can_filters=None, **config): type_filter = config.get('type_filter') serial = config.get('serial') - self.dev = self._open_device(type_filter, serial) + self.dev = self._find_device(type_filter, serial) + ics.open_device(self.dev) + + bitrate = config.get('bitrate') + + # Default auto baud setting + settings = { + 'SetBaudrate': ics.AUTO, + 'Baudrate': BAUDRATE_SETTING[500000], # Default baudrate setting + 'auto_baud': 1 + } + + if bitrate is not None: + if int(bitrate) not in VALID_BITRATES: + raise ValueError( + 'Invalid bitrate. Valid bitrates are {}'.format( + VALID_BITRATES + ) + ) + baud_rate_setting = BAUDRATE_SETTING[int(bitrate)] + settings = { + 'SetBaudrate': ics.AUTO, + 'Baudrate': baud_rate_setting, + 'auto_baud': 0, + } + self._set_can_settings(channel, settings) self.channel_info = '%s %s CH:%s' % ( self.dev.Name, @@ -126,13 +174,9 @@ def get_serial_number(device): :return: ics device serial string :rtype: str """ - def to_base36(n, alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"): - return (to_base36(n // 36) + alphabet[n % 36]).lstrip("0") \ - if n > 0 else "0" - a0000 = 604661760 if device.SerialNumber >= a0000: - return to_base36(device.SerialNumber) + return ics.base36enc(device.SerialNumber) return str(device.SerialNumber) def shutdown(self): @@ -140,7 +184,7 @@ def shutdown(self): self.opened = False ics.close_device(self.dev) - def _open_device(self, type_filter=None, serial=None): + def _find_device(self, type_filter=None, serial=None): if type_filter is not None: devices = ics.find_devices(type_filter) else: @@ -159,9 +203,30 @@ def _open_device(self, type_filter=None, serial=None): msg.append('with serial {}'.format(serial)) msg.append('found.') raise Exception(' '.join(msg)) - ics.open_device(dev) return dev + def _get_can_settings(self, channel): + """Return the CanSettings for channel + + :param channel: can channel number + :return: ics.CanSettings + """ + device_settings = ics.get_device_settings(self.dev) + return getattr(device_settings, 'can{}'.format(channel)) + + def _set_can_settings(self, channel, setting): + """Applies can settings to channel + + :param channel: can channel number + :param setting: settings dictionary (only the settings to update) + :return: None + """ + device_settings = ics.get_device_settings(self.dev) + channel_settings = getattr(device_settings, 'can{}'.format(channel)) + for setting, value in setting.items(): + setattr(channel_settings, setting, value) + ics.set_device_settings(self.dev, device_settings) + def _process_msg_queue(self, timeout=None): try: messages, errors = ics.get_messages(self.dev, False, timeout) From deb50be200c2d2766e60385550f951ddd7b61dc9 Mon Sep 17 00:00:00 2001 From: Edoardo Bezzeccheri Date: Fri, 16 Mar 2018 14:52:17 +0100 Subject: [PATCH 065/168] Updated PCAN interface The file was updated from the latest download available from PCAN. The header states that last edit was made on 13/11/2017. Mostly some flags has been added. --- can/interfaces/pcan/PCANBasic.py | 135 ++++++++++++++++++------------- 1 file changed, 78 insertions(+), 57 deletions(-) diff --git a/can/interfaces/pcan/PCANBasic.py b/can/interfaces/pcan/PCANBasic.py index 2fc442627..5d79ccdfb 100644 --- a/can/interfaces/pcan/PCANBasic.py +++ b/can/interfaces/pcan/PCANBasic.py @@ -5,12 +5,16 @@ PCAN-Basic API Author: Keneth Wagner +Last change: 13.11.2017 Wagner -Copyright (C) 1999-2016 PEAK-System Technik GmbH, Darmstadt, Germany +Language: Python 2.7, 3.5 + +Copyright (C) 1999-2017 PEAK-System Technik GmbH, Darmstadt, Germany http://www.peak-system.com """ from ctypes import * +from string import * import platform import logging @@ -105,7 +109,7 @@ PCAN_LANBUS16 = TPCANHandle(0x810) # PCAN-LAN interface, channel 16 # Represent the PCAN error and status codes -PCAN_ERROR_OK = TPCANStatus(0x00000) # No error +PCAN_ERROR_OK = TPCANStatus(0x00000) # No error PCAN_ERROR_XMTFULL = TPCANStatus(0x00001) # Transmit buffer in CAN controller is full PCAN_ERROR_OVERRUN = TPCANStatus(0x00002) # CAN controller was read too late PCAN_ERROR_BUSLIGHT = TPCANStatus(0x00004) # Bus error: an error counter reached the 'light' limit @@ -146,35 +150,46 @@ PCAN_LAN = TPCANDevice(0x08) # PCAN Gateway devices # PCAN parameters -PCAN_DEVICE_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter -PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 5-Volt power parameter -PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter -PCAN_MESSAGE_FILTER = TPCANParameter(0x04) # PCAN message filter parameter -PCAN_API_VERSION = TPCANParameter(0x05) # PCAN-Basic API version parameter -PCAN_CHANNEL_VERSION = TPCANParameter(0x06) # PCAN device channel version parameter -PCAN_BUSOFF_AUTORESET = TPCANParameter(0x07) # PCAN Reset-On-Busoff parameter -PCAN_LISTEN_ONLY = TPCANParameter(0x08) # PCAN Listen-Only parameter -PCAN_LOG_LOCATION = TPCANParameter(0x09) # Directory path for log files -PCAN_LOG_STATUS = TPCANParameter(0x0A) # Debug-Log activation status -PCAN_LOG_CONFIGURE = TPCANParameter(0x0B) # Configuration of the debugged information (LOG_FUNCTION_***) -PCAN_LOG_TEXT = TPCANParameter(0x0C) # Custom insertion of text into the log file -PCAN_CHANNEL_CONDITION = TPCANParameter(0x0D) # Availability status of a PCAN-Channel -PCAN_HARDWARE_NAME = TPCANParameter(0x0E) # PCAN hardware name parameter -PCAN_RECEIVE_STATUS = TPCANParameter(0x0F) # Message reception status of a PCAN-Channel -PCAN_CONTROLLER_NUMBER = TPCANParameter(0x10) # CAN-Controller number of a PCAN-Channel -PCAN_TRACE_LOCATION = TPCANParameter(0x11) # Directory path for PCAN trace files -PCAN_TRACE_STATUS = TPCANParameter(0x12) # CAN tracing activation status -PCAN_TRACE_SIZE = TPCANParameter(0x13) # Configuration of the maximum file size of a CAN trace -PCAN_TRACE_CONFIGURE = TPCANParameter(0x14) # Configuration of the trace file storing mode (TRACE_FILE_***) -PCAN_CHANNEL_IDENTIFYING = TPCANParameter(0x15) # Physical identification of a USB based PCAN-Channel by blinking its associated LED -PCAN_CHANNEL_FEATURES = TPCANParameter(0x16) # Capabilities of a PCAN device (FEATURE_***) -PCAN_BITRATE_ADAPTING = TPCANParameter(0x17) # Using of an existing bit rate (PCAN-View connected to a channel) -PCAN_BITRATE_INFO = TPCANParameter(0x18) # Configured bit rate as Btr0Btr1 value -PCAN_BITRATE_INFO_FD = TPCANParameter(0x19) # Configured bit rate as TPCANBitrateFD string -PCAN_BUSSPEED_NOMINAL = TPCANParameter(0x1A) # Configured nominal CAN Bus speed as Bits per seconds -PCAN_BUSSPEED_DATA = TPCANParameter(0x1B) # Configured CAN data speed as Bits per seconds -PCAN_IP_ADDRESS = TPCANParameter(0x1C) # Remote address of a LAN channel as string in IPv4 format -PCAN_LAN_SERVICE_STATUS = TPCANParameter(0x1D) # Status of the Virtual PCAN-Gateway Service +PCAN_DEVICE_NUMBER = TPCANParameter(0x01) # PCAN-USB device number parameter +PCAN_5VOLTS_POWER = TPCANParameter(0x02) # PCAN-PC Card 5-Volt power parameter +PCAN_RECEIVE_EVENT = TPCANParameter(0x03) # PCAN receive event handler parameter +PCAN_MESSAGE_FILTER = TPCANParameter(0x04) # PCAN message filter parameter +PCAN_API_VERSION = TPCANParameter(0x05) # PCAN-Basic API version parameter +PCAN_CHANNEL_VERSION = TPCANParameter(0x06) # PCAN device channel version parameter +PCAN_BUSOFF_AUTORESET = TPCANParameter(0x07) # PCAN Reset-On-Busoff parameter +PCAN_LISTEN_ONLY = TPCANParameter(0x08) # PCAN Listen-Only parameter +PCAN_LOG_LOCATION = TPCANParameter(0x09) # Directory path for log files +PCAN_LOG_STATUS = TPCANParameter(0x0A) # Debug-Log activation status +PCAN_LOG_CONFIGURE = TPCANParameter(0x0B) # Configuration of the debugged information (LOG_FUNCTION_***) +PCAN_LOG_TEXT = TPCANParameter(0x0C) # Custom insertion of text into the log file +PCAN_CHANNEL_CONDITION = TPCANParameter(0x0D) # Availability status of a PCAN-Channel +PCAN_HARDWARE_NAME = TPCANParameter(0x0E) # PCAN hardware name parameter +PCAN_RECEIVE_STATUS = TPCANParameter(0x0F) # Message reception status of a PCAN-Channel +PCAN_CONTROLLER_NUMBER = TPCANParameter(0x10) # CAN-Controller number of a PCAN-Channel +PCAN_TRACE_LOCATION = TPCANParameter(0x11) # Directory path for PCAN trace files +PCAN_TRACE_STATUS = TPCANParameter(0x12) # CAN tracing activation status +PCAN_TRACE_SIZE = TPCANParameter(0x13) # Configuration of the maximum file size of a CAN trace +PCAN_TRACE_CONFIGURE = TPCANParameter(0x14) # Configuration of the trace file storing mode (TRACE_FILE_***) +PCAN_CHANNEL_IDENTIFYING = TPCANParameter(0x15) # Physical identification of a USB based PCAN-Channel by blinking its associated LED +PCAN_CHANNEL_FEATURES = TPCANParameter(0x16) # Capabilities of a PCAN device (FEATURE_***) +PCAN_BITRATE_ADAPTING = TPCANParameter(0x17) # Using of an existing bit rate (PCAN-View connected to a channel) +PCAN_BITRATE_INFO = TPCANParameter(0x18) # Configured bit rate as Btr0Btr1 value +PCAN_BITRATE_INFO_FD = TPCANParameter(0x19) # Configured bit rate as TPCANBitrateFD string +PCAN_BUSSPEED_NOMINAL = TPCANParameter(0x1A) # Configured nominal CAN Bus speed as Bits per seconds +PCAN_BUSSPEED_DATA = TPCANParameter(0x1B) # Configured CAN data speed as Bits per seconds +PCAN_IP_ADDRESS = TPCANParameter(0x1C) # Remote address of a LAN channel as string in IPv4 format +PCAN_LAN_SERVICE_STATUS = TPCANParameter(0x1D) # Status of the Virtual PCAN-Gateway Service +PCAN_ALLOW_STATUS_FRAMES = TPCANParameter(0x1E) # Status messages reception status within a PCAN-Channel +PCAN_ALLOW_RTR_FRAMES = TPCANParameter(0x1F) # RTR messages reception status within a PCAN-Channel +PCAN_ALLOW_ERROR_FRAMES = TPCANParameter(0x20) # Error messages reception status within a PCAN-Channel +PCAN_INTERFRAME_DELAY = TPCANParameter(0x21) # Delay, in microseconds, between sending frames +PCAN_ACCEPTANCE_FILTER_11BIT = TPCANParameter(0x22) # Filter over code and mask patterns for 11-Bit messages +PCAN_ACCEPTANCE_FILTER_29BIT = TPCANParameter(0x23) # Filter over code and mask patterns for 29-Bit messages +PCAN_IO_DIGITAL_CONFIGURATION = TPCANParameter(0x24) # Output mode of 32 digital I/O pin of a PCAN-USB Chip. 1: Output-Active 0 : Output Inactive +PCAN_IO_DIGITAL_VALUE = TPCANParameter(0x25) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip +PCAN_IO_DIGITAL_SET = TPCANParameter(0x26) # Value assigned to a 32 digital I/O pins of a PCAN-USB Chip - Multiple digital I/O pins to 1 = High +PCAN_IO_DIGITAL_CLEAR = TPCANParameter(0x27) # Clear multiple digital I/O pins to 0 +PCAN_IO_ANALOG_VALUE = TPCANParameter(0x28) # Get value of a single analog input pin # PCAN parameter values PCAN_PARAMETER_OFF = int(0x00) # The PCAN parameter is not set (inactive) @@ -188,9 +203,9 @@ PCAN_CHANNEL_PCANVIEW = PCAN_CHANNEL_AVAILABLE | PCAN_CHANNEL_OCCUPIED # The PCAN-Channel handle is already being used by a PCAN-View application, but is available to connect LOG_FUNCTION_DEFAULT = int(0x00) # Logs system exceptions / errors -LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions -LOG_FUNCTION_PARAMETERS = int(0x02) # Logs the parameters passed to the PCAN-Basic API functions -LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions +LOG_FUNCTION_ENTRY = int(0x01) # Logs the entries to the PCAN-Basic API functions +LOG_FUNCTION_PARAMETERS = int(0x02) # Logs the parameters passed to the PCAN-Basic API functions +LOG_FUNCTION_LEAVE = int(0x04) # Logs the exits from the PCAN-Basic API functions LOG_FUNCTION_WRITE = int(0x08) # Logs the CAN messages passed to the CAN_Write function LOG_FUNCTION_READ = int(0x10) # Logs the CAN messages received within the CAN_Read function LOG_FUNCTION_ALL = int(0xFFFF)# Logs all possible information within the PCAN-Basic API functions @@ -202,6 +217,8 @@ TRACE_FILE_OVERWRITE = int(0x80) # Causes the overwriting of available traces (same name) FEATURE_FD_CAPABLE = int(0x01) # Device supports flexible data-rate (CAN-FD) +FEATURE_DELAY_CAPABLE = int(0x02) # Device supports a delay between sending frames (FPGA based USB devices) +FEATURE_IO_CAPABLE = int(0x04) # Device supports I/O functionality for electronic circuits (USB-Chip devices) SERVICE_STATUS_STOPPED = int(0x01) # The service is not running SERVICE_STATUS_RUNNING = int(0x04) # The service is running @@ -213,11 +230,12 @@ PCAN_MESSAGE_FD = TPCANMessageType(0x04) # The PCAN message represents a FD frame in terms of CiA Specs PCAN_MESSAGE_BRS = TPCANMessageType(0x08) # The PCAN message represents a FD bit rate switch (CAN data at a higher bit rate) PCAN_MESSAGE_ESI = TPCANMessageType(0x10) # The PCAN message represents a FD error state indicator(CAN FD transmitter was error active) +PCAN_MESSAGE_ERRFRAME = TPCANMessageType(0x40) # The PCAN message represents an error frame PCAN_MESSAGE_STATUS = TPCANMessageType(0x80) # The PCAN message represents a PCAN status message # Frame Type / Initialization Mode -PCAN_MODE_STANDARD = PCAN_MESSAGE_STANDARD -PCAN_MODE_EXTENDED = PCAN_MESSAGE_EXTENDED +PCAN_MODE_STANDARD = PCAN_MESSAGE_STANDARD +PCAN_MODE_EXTENDED = PCAN_MESSAGE_EXTENDED # Baud rate codes = BTR0/BTR1 register values for the CAN controller. # You can define your own Baud rate with the BTROBTR1 register. @@ -245,7 +263,7 @@ # * Each pair of parameter/value must be separated using ','. # # Example: -# f_clock=80000000,nom_brp=0,nom_tseg1=13,nom_tseg2=0,nom_sjw=0,data_brp=0,data_tseg1=13,data_tseg2=0,data_sjw=0 +# f_clock=80000000,nom_brp=10,nom_tseg1=5,nom_tseg2=2,nom_sjw=1,data_brp=4,data_tseg1=7,data_tseg2=2,data_sjw=1 # PCAN_BR_CLOCK = TPCANBitrateFD(b"f_clock") PCAN_BR_CLOCK_MHZ = TPCANBitrateFD(b"f_clock_mhz") @@ -263,12 +281,13 @@ # Supported No-Plug-And-Play Hardware types PCAN_TYPE_ISA = TPCANType(0x01) # PCAN-ISA 82C200 PCAN_TYPE_ISA_SJA = TPCANType(0x09) # PCAN-ISA SJA1000 -PCAN_TYPE_ISA_PHYTEC = TPCANType(0x04) # PHYTEC ISA +PCAN_TYPE_ISA_PHYTEC = TPCANType(0x04) # PHYTEC ISA PCAN_TYPE_DNG = TPCANType(0x02) # PCAN-Dongle 82C200 PCAN_TYPE_DNG_EPP = TPCANType(0x03) # PCAN-Dongle EPP 82C200 PCAN_TYPE_DNG_SJA = TPCANType(0x05) # PCAN-Dongle SJA1000 PCAN_TYPE_DNG_SJA_EPP = TPCANType(0x06) # PCAN-Dongle EPP SJA1000 + class TPCANMsg (Structure): """ Represents a PCAN message @@ -278,6 +297,7 @@ class TPCANMsg (Structure): ("LEN", c_ubyte), # Data Length Code of the message (0..8) ("DATA", c_ubyte * 8) ] # Data of the message (DATA[0]..DATA[7]) + class TPCANTimestamp (Structure): """ Represents a timestamp of a received PCAN message @@ -287,9 +307,10 @@ class TPCANTimestamp (Structure): ("millis_overflow", c_ushort), # Roll-arounds of millis ("micros", c_ushort) ] # Microseconds: 0..999 + class TPCANMsgFD (Structure): """ - Represents a PCAN message from a FD capable hardware + Represents a PCAN message """ _fields_ = [ ("ID", c_uint), # 11/29-bit message identifier ("MSGTYPE", TPCANMessageType), # Type of the message @@ -315,12 +336,12 @@ def __init__(self): def Initialize( self, - Channel, - Btr0Btr1, - HwType = TPCANType(0), + Channel, + Btr0Btr1, + HwType = TPCANType(0), IOPort = c_uint(0), Interrupt = c_ushort(0)): - + """ Initializes a PCAN Channel @@ -340,12 +361,12 @@ def Initialize( except: logger.error("Exception on PCANBasic.Initialize") raise - + def InitializeFD( self, - Channel, + Channel, BitrateFD): - + """ Initializes a FD capable PCAN Channel @@ -361,8 +382,8 @@ def InitializeFD( nom_brp, nom_sjw, nom_tseg1, nom_tseg2. * Following Parameters are optional (not used yet): data_ssp_offset, nom_samp - Example: - f_clock_mhz=80,nom_brp=0,nom_tseg1=13,nom_tseg2=0,nom_sjw=0,data_brp=0,data_tseg1=13,data_tseg2=0,data_sjw=0 + Example: + f_clock=80000000,nom_brp=10,nom_tseg1=5,nom_tseg2=2,nom_sjw=1,data_brp=4,data_tseg1=7,data_tseg2=2,data_sjw=1 Returns: A TPCANStatus error code @@ -419,7 +440,7 @@ def Reset( except: logger.error("Exception on PCANBasic.Reset") raise - + def GetStatus( self, Channel): @@ -468,7 +489,7 @@ def Read( return TPCANStatus(res),msg,timestamp except: logger.error("Exception on PCANBasic.Read") - raise + raise def ReadFD( self, @@ -498,7 +519,7 @@ def ReadFD( return TPCANStatus(res),msg,timestamp except: logger.error("Exception on PCANBasic.ReadFD") - raise + raise def Write( self, @@ -598,7 +619,7 @@ def GetValue( Returns: A touple with 2 values - """ + """ try: if Parameter == PCAN_API_VERSION or Parameter == PCAN_HARDWARE_NAME or Parameter == PCAN_CHANNEL_VERSION or Parameter == PCAN_LOG_LOCATION or Parameter == PCAN_TRACE_LOCATION or Parameter == PCAN_BITRATE_INFO_FD or Parameter == PCAN_IP_ADDRESS: mybuffer = create_string_buffer(256) @@ -609,7 +630,7 @@ def GetValue( return TPCANStatus(res),mybuffer.value except: logger.error("Exception on PCANBasic.GetValue") - raise + raise def SetValue( self, @@ -634,7 +655,7 @@ def SetValue( Returns: A TPCANStatus error code - """ + """ try: if Parameter == PCAN_LOG_LOCATION or Parameter == PCAN_LOG_TEXT or Parameter == PCAN_TRACE_LOCATION: mybuffer = create_string_buffer(256) @@ -662,7 +683,7 @@ def GetErrorText( Neutral (0x00), German (0x07), English (0x09), Spanish (0x0A), Italian (0x10) and French (0x0C) - The return value of this method is a 2-touple, where + The return value of this method is a 2-touple, where the first value is the result (TPCANStatus) of the method and the second one, the error text @@ -672,11 +693,11 @@ def GetErrorText( Returns: A touple with 2 values - """ + """ try: mybuffer = create_string_buffer(256) res = self.__m_dllBasic.CAN_GetErrorText(Error,Language,byref(mybuffer)) return TPCANStatus(res),mybuffer.value except: logger.error("Exception on PCANBasic.GetErrorText") - raise + raise From a4f420da997e3609cf79dbfb846cd3cbfd77a091 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 2 Apr 2018 12:22:31 +0200 Subject: [PATCH 066/168] adjusted the ASC test cases to check only rounded timestamps --- test/logformats_test.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 8e5589c00..21285217f 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -38,20 +38,25 @@ def _test_writer_and_reader(test_case, writer_constructor, reader_constructor, sleep_time=None, check_remote_frames=True, check_error_frames=True, - check_comments=False): + check_comments=False, round_timestamps=False): """Tests a pair of writer and reader by writing all data first and then reading all data and checking if they could be reconstructed correctly. - :param test_case: the test case the use the assert methods on - :param sleep_time: specifies the time to sleep after writing all messages. + :param unittest.TestCase test_case: the test case the use the assert methods on + :param Callable writer_constructor: the constructor of the writer class + :param Callable reader_constructor: the constructor of the reader class + + :param float sleep_time: specifies the time to sleep after writing all messages. gets ignored when set to None - :param check_remote_frames: if true, also tests remote frames - :param check_error_frames: if true, also tests error frames - :param check_comments: if true, also inserts comments at some + :param bool check_remote_frames: if True, also tests remote frames + :param bool check_error_frames: if True, also tests error frames + :param bool check_comments: if True, also inserts comments at some locations and checks if they are contained anywhere literally in the resulting file. The locations as selected randomly but deterministically, which makes the test reproducible. + :param bool round_timestamps: if True, rounds timestamps using :meth:`~builtin.round` + before comparing the read messages/events """ assert isinstance(test_case, unittest.TestCase), \ @@ -119,7 +124,12 @@ def _test_writer_and_reader(test_case, writer_constructor, reader_constructor, s # check the order and content of the individual messages for i, (read, original) in enumerate(zip(read_messages, original_messages)): try: + # check everything except the timestamp test_case.assertEqual(read, original) + # check the timestamp + if round_timestamps: + original.timestamp = round(original.timestamp) + read.timestamp = round(read.timestamp) test_case.assertAlmostEqual(read.timestamp, original.timestamp, places=6) except Exception as exception: # attach the index @@ -149,7 +159,7 @@ class TestAscFileFormat(unittest.TestCase): def test_writer_and_reader(self): _test_writer_and_reader(self, can.ASCWriter, can.ASCReader, - check_comments=True) + check_comments=True, round_timestamps=True) class TestCsvFileFormat(unittest.TestCase): From 0b9be035dc17603ffc18168fef5ebea42f0edae3 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 2 Apr 2018 12:23:40 +0200 Subject: [PATCH 067/168] unified the code in and corrected the ASCWriter class --- can/io/asc.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 6660d21dc..d61b70b1d 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -110,13 +110,13 @@ class ASCWriter(Listener): It the first message does not have a timestamp, it is set to zero. """ - LOG_STRING = "{time: 9.4f} {channel} {id:<15} Rx {dtype} {data}\n" - EVENT_STRING = "{time: 9.4f} {message}\n" + FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" + FORMAT_DATE = "%a %b %m %I:%M:%S %p %Y" + FORMAT_EVENT = "{time} {message}\n" def __init__(self, filename, channel=1): # setup self.channel = channel - self.started = time.time() self.log_file = open(filename, 'w') # write start of file header @@ -137,7 +137,11 @@ def stop(self): self.log_file.close() def log_event(self, message, timestamp=None): - """Add an arbitrary message to the log file.""" + """Add a message to the log file. + + :param str message: an arbitrary message + :param float message: the absolute timestamp of the event + """ if not message: # if empty or None logger.debug("ASCWriter: ignoring empty message") @@ -149,7 +153,7 @@ def log_event(self, message, timestamp=None): self.started = self.last_timestamp self.log_file.write("Begin Triggerblock %s\n" % self.last_timestamp) self.header_written = True - self.log_event("Start of measurement") # recursive + self.log_event("Start of measurement") # recursive call # figure out the correct timestamp if timestamp is None or timestamp < self.last_timestamp: @@ -159,9 +163,13 @@ def log_event(self, message, timestamp=None): if timestamp >= self.started: timestamp -= self.started - line = self.EVENT_STRING.format(time=timestamp, message=message) + formatted_date = time.strftime(self.FORMAT_DATE, time.localtime(timestamp)) - if not self.log_file.closed: + line = self.FORMAT_EVENT.format(time=timestamp, message=message) + + if self.log_file.closed: + logger.warn("ASCWriter: ignoring write call to closed file") + else: self.log_file.write(line) def on_message_received(self, msg): @@ -183,11 +191,9 @@ def on_message_received(self, msg): channel = msg.channel if isinstance(msg.channel, int) else self.channel - line = self.LOG_STRING.format(time=msg.timestamp, - channel=channel, - id=arb_id, - dtype=dtype, - data=' '.join(data)) + serialized = self.FORMAT_MESSAGE.format(channel=channel, + id=arb_id, + dtype=dtype, + data=' '.join(data)) - if not self.log_file.closed: - self.log_file.write(line) + self.log_event(serialized, msg.timestamp) From 8f526c5a91956909219f0e2ed8fee6d1189ed034 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 2 Apr 2018 12:34:22 +0200 Subject: [PATCH 068/168] small corrections --- can/io/asc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index d61b70b1d..f892418f7 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -112,7 +112,7 @@ class ASCWriter(Listener): FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" FORMAT_DATE = "%a %b %m %I:%M:%S %p %Y" - FORMAT_EVENT = "{time} {message}\n" + FORMAT_EVENT = "{timestamp: 9.4f} {message}\n" def __init__(self, filename, channel=1): # setup @@ -151,7 +151,9 @@ def log_event(self, message, timestamp=None): if not self.header_written: self.last_timestamp = (timestamp or 0.0) self.started = self.last_timestamp - self.log_file.write("Begin Triggerblock %s\n" % self.last_timestamp) + formatted_date = time.strftime(self.FORMAT_DATE, time.localtime(self.last_timestamp)) + self.log_file.write("base hex timestamps absolute\n") + self.log_file.write("Begin Triggerblock %s\n" % formatted_date) self.header_written = True self.log_event("Start of measurement") # recursive call @@ -159,13 +161,11 @@ def log_event(self, message, timestamp=None): if timestamp is None or timestamp < self.last_timestamp: timestamp = self.last_timestamp - # turn into relative timestamps + # turn into relative timestamps if necessary if timestamp >= self.started: timestamp -= self.started - formatted_date = time.strftime(self.FORMAT_DATE, time.localtime(timestamp)) - - line = self.FORMAT_EVENT.format(time=timestamp, message=message) + line = self.FORMAT_EVENT.format(timestamp=timestamp, message=message) if self.log_file.closed: logger.warn("ASCWriter: ignoring write call to closed file") From afcc678420e51aca978e9e64937d7c95df7c3c9d Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 10 Apr 2018 12:10:42 +0200 Subject: [PATCH 069/168] BLF and ASC log file fixes (#291) Add 1 to channel numbers (to avoid use of 0) Better handling of error frames in BLF Better handling of CAN FD frames in BLF Add channel support to ASCReader Fixes #279 --- can/io/asc.py | 19 +++-- can/io/blf.py | 176 ++++++++++++++++++++++++++-------------- can/util.py | 33 ++++++++ test/logformats_test.py | 7 +- 4 files changed, 166 insertions(+), 69 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index f892418f7..5636469f9 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -56,12 +56,18 @@ def __iter__(self): continue timestamp = float(timestamp) + try: + # See ASCWriter + channel = int(channel) - 1 + except ValueError: + pass if dummy.strip()[0:10] == 'ErrorFrame': - msg = Message(timestamp=timestamp, is_error_frame=True) + msg = Message(timestamp=timestamp, is_error_frame=True, + channel=channel) yield msg - elif not channel.isdigit() or dummy.strip()[0:10] == 'Statistic:': + elif not isinstance(channel, int) or dummy.strip()[0:10] == 'Statistic:': pass elif dummy[-1:].lower() == 'r': @@ -70,7 +76,8 @@ def __iter__(self): msg = Message(timestamp=timestamp, arbitration_id=can_id_num & CAN_ID_MASK, extended_id=is_extended_id, - is_remote_frame=True) + is_remote_frame=True, + channel=channel) yield msg else: @@ -97,7 +104,8 @@ def __iter__(self): extended_id=is_extended_id, is_remote_frame=False, dlc=dlc, - data=frame) + data=frame, + channel=channel) yield msg @@ -189,7 +197,8 @@ def on_message_received(self, msg): if msg.is_extended_id: arb_id += 'x' - channel = msg.channel if isinstance(msg.channel, int) else self.channel + # Many interfaces start channel numbering at 0 which is invalid + channel = msg.channel + 1 if isinstance(msg.channel, int) else self.channel serialized = self.FORMAT_MESSAGE.format(channel=channel, id=arb_id, diff --git a/can/io/blf.py b/can/io/blf.py index 3b96a621a..588f56b32 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -22,6 +22,7 @@ from can.message import Message from can.listener import Listener +from can.util import len2dlc, dlc2len # 0 = unknown, 2 = CANoe @@ -35,9 +36,16 @@ # time start (SYSTEMTIME), time stop (SYSTEMTIME) FILE_HEADER_STRUCT = struct.Struct("<4sLBBBBBBBBQQLL8H8H72x") -# signature ("LOBJ"), header size, header version (1), object size, object type, -# flags, object version, size uncompressed or timestamp -OBJ_HEADER_STRUCT = struct.Struct("<4sHHLLL2xHQ") +# signature ("LOBJ"), header size, header version (1), object size, object type +OBJ_HEADER_BASE_STRUCT = struct.Struct("<4sHHLL") + +# flags, client index, object version, timestamp +OBJ_HEADER_STRUCT = struct.Struct(" len(data): # Object continues in next log container break + pos += OBJ_HEADER_BASE_STRUCT.size + # Read rest of header + header += OBJ_HEADER_STRUCT.unpack_from(data, pos) + pos += OBJ_HEADER_STRUCT.size + obj_type = header[4] - timestamp = header[7] / 1000000000.0 + self.start_timestamp + timestamp = header[8] * 1e-9 + self.start_timestamp + if obj_type == CAN_MESSAGE: - assert obj_size == OBJ_HEADER_STRUCT.size + CAN_MSG_STRUCT.size (channel, flags, dlc, can_id, - can_data) = CAN_MSG_STRUCT.unpack_from( - data, pos + OBJ_HEADER_STRUCT.size) + can_data) = CAN_MSG_STRUCT.unpack_from(data, pos) msg = Message(timestamp=timestamp, arbitration_id=can_id & 0x1FFFFFFF, extended_id=bool(can_id & CAN_MSG_EXT), is_remote_frame=bool(flags & REMOTE_FLAG), dlc=dlc, data=can_data[:dlc], - channel=channel) + channel=channel - 1) yield msg elif obj_type == CAN_FD_MESSAGE: - assert obj_size == OBJ_HEADER_STRUCT.size + CAN_FD_MSG_STRUCT.size (channel, flags, dlc, can_id, _, _, fd_flags, - valid_bytes, can_data) = CAN_FD_MSG_STRUCT.unpack_from( - data, pos + OBJ_HEADER_STRUCT.size) + _, can_data) = CAN_FD_MSG_STRUCT.unpack_from(data, pos) + length = dlc2len(dlc) msg = Message(timestamp=timestamp, arbitration_id=can_id & 0x1FFFFFFF, extended_id=bool(can_id & CAN_MSG_EXT), @@ -162,22 +188,29 @@ def __iter__(self): is_fd=bool(fd_flags & EDL), bitrate_switch=bool(fd_flags & BRS), error_state_indicator=bool(fd_flags & ESI), - dlc=dlc, - data=can_data[:valid_bytes], - channel=channel) + dlc=length, + data=can_data[:length], + channel=channel - 1) yield msg - elif obj_type == CAN_ERROR: - assert obj_size == OBJ_HEADER_STRUCT.size + CAN_ERROR_STRUCT.size - channel, length = CAN_ERROR_STRUCT.unpack_from( - data, pos + OBJ_HEADER_STRUCT.size) - msg = Message(timestamp=timestamp, is_error_frame=True, - channel=channel) + elif obj_type == CAN_ERROR_EXT: + (channel, _, _, _, _, dlc, _, can_id, _, + can_data) = CAN_ERROR_EXT_STRUCT.unpack_from(data, pos) + msg = Message(timestamp=timestamp, + is_error_frame=True, + extended_id=bool(can_id & CAN_MSG_EXT), + arbitration_id=can_id & 0x1FFFFFFF, + dlc=dlc, + data=can_data[:dlc], + channel=channel - 1) yield msg - pos += obj_size + + pos += obj_size - HEADER_SIZE # Add padding bytes pos += obj_size % 4 + # Save remaing data that could not be processed tail = data[pos:] + self.fp.close() @@ -187,7 +220,7 @@ class BLFWriter(Listener): """ #: Max log container size of uncompressed data - MAX_CACHE_SIZE = 0x20000 + MAX_CACHE_SIZE = 128 * 1024 #: ZLIB compression level COMPRESSION_LEVEL = 9 @@ -205,29 +238,38 @@ def __init__(self, filename, channel=1): self.stop_timestamp = None def on_message_received(self, msg): - channel = msg.channel if isinstance(msg.channel, int) else self.channel + # Many interfaces start channel numbering at 0 which is invalid in BLF + channel = msg.channel + 1 if isinstance(msg.channel, int) else self.channel + arb_id = msg.arbitration_id + if msg.id_type: + arb_id |= CAN_MSG_EXT + flags = REMOTE_FLAG if msg.is_remote_frame else 0 + data = bytes(msg.data) + if msg.is_error_frame: - data = CAN_ERROR_STRUCT.pack(channel, 0) - self._add_object(CAN_ERROR, data, msg.timestamp) + data = CAN_ERROR_EXT_STRUCT.pack(channel, + 0, # length + 0, # flags + 0, # ecc + 0, # position + len2dlc(msg.dlc), + 0, # frame length + arb_id, + 0, # ext flags + data) + self._add_object(CAN_ERROR_EXT, data, msg.timestamp) + elif msg.is_fd: + fd_flags = EDL + if msg.bitrate_switch: + fd_flags |= BRS + if msg.error_state_indicator: + fd_flags |= ESI + data = CAN_FD_MSG_STRUCT.pack(channel, flags, len2dlc(msg.dlc), + arb_id, 0, 0, fd_flags, msg.dlc, data) + self._add_object(CAN_FD_MESSAGE, data, msg.timestamp) else: - flags = REMOTE_FLAG if msg.is_remote_frame else 0 - arb_id = msg.arbitration_id - if msg.id_type: - arb_id |= CAN_MSG_EXT - if msg.is_fd: - fd_flags = EDL - if msg.bitrate_switch: - fd_flags |= BRS - if msg.error_state_indicator: - fd_flags |= ESI - data = CAN_FD_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, - 0, 0, fd_flags, msg.dlc, - bytes(msg.data)) - self._add_object(CAN_FD_MESSAGE, data, msg.timestamp) - else: - data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, - bytes(msg.data)) - self._add_object(CAN_MESSAGE, data, msg.timestamp) + data = CAN_MSG_STRUCT.pack(channel, flags, msg.dlc, arb_id, data) + self._add_object(CAN_MESSAGE, data, msg.timestamp) def log_event(self, text, timestamp=None): """Add an arbitrary message to the log file as a global marker. @@ -255,16 +297,19 @@ def _add_object(self, obj_type, data, timestamp=None): if self.start_timestamp is None: self.start_timestamp = timestamp self.stop_timestamp = timestamp - timestamp = int((timestamp - self.start_timestamp) * 1000000000) - obj_size = OBJ_HEADER_STRUCT.size + len(data) - header = OBJ_HEADER_STRUCT.pack( - b"LOBJ", OBJ_HEADER_STRUCT.size, 1, obj_size, obj_type, - 2, 0, max(timestamp, 0)) - self.cache.append(header) + timestamp = int((timestamp - self.start_timestamp) * 1e9) + obj_size = HEADER_SIZE + len(data) + base_header = OBJ_HEADER_BASE_STRUCT.pack( + b"LOBJ", HEADER_SIZE, 1, obj_size, obj_type) + obj_header = OBJ_HEADER_STRUCT.pack(2, 0, 0, max(timestamp, 0)) + + self.cache.append(base_header) + self.cache.append(obj_header) self.cache.append(data) padding_size = len(data) % 4 if padding_size: self.cache.append(b"\x00" * padding_size) + self.cache_size += obj_size + padding_size self.count_of_objects += 1 if self.cache_size >= self.MAX_CACHE_SIZE: @@ -285,14 +330,19 @@ def _flush(self): self.cache_size = len(tail) compressed_data = zlib.compress(uncompressed_data, self.COMPRESSION_LEVEL) - obj_size = OBJ_HEADER_STRUCT.size + len(compressed_data) - header = OBJ_HEADER_STRUCT.pack( - b"LOBJ", 16, 1, obj_size, LOG_CONTAINER, 2, 0, len(uncompressed_data)) - self.fp.write(header) + obj_size = (OBJ_HEADER_STRUCT.size + LOG_CONTAINER_STRUCT.size + + len(compressed_data)) + base_header = OBJ_HEADER_BASE_STRUCT.pack( + b"LOBJ", OBJ_HEADER_BASE_STRUCT.size, 1, obj_size, LOG_CONTAINER) + container_header = LOG_CONTAINER_STRUCT.pack( + ZLIB_DEFLATE, len(uncompressed_data)) + self.fp.write(base_header) + self.fp.write(container_header) self.fp.write(compressed_data) # Write padding bytes self.fp.write(b"\x00" * (obj_size % 4)) - self.uncompressed_size += len(uncompressed_data) + OBJ_HEADER_STRUCT.size + self.uncompressed_size += OBJ_HEADER_STRUCT.size + LOG_CONTAINER_STRUCT.size + self.uncompressed_size += len(uncompressed_data) def stop(self): """Stops logging and closes the file.""" diff --git a/can/util.py b/can/util.py index c78b9d674..cec42341d 100644 --- a/can/util.py +++ b/can/util.py @@ -23,6 +23,12 @@ log = logging.getLogger('can.util') +# List of valid data lengths for a CAN FD message +CAN_FD_DLC = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 12, 16, 20, 24, 32, 48, 64 +] + REQUIRED_KEYS = [ 'interface', 'channel', @@ -209,6 +215,33 @@ def set_logging_level(level_name=None): log.debug("Logging set to {}".format(level_name)) +def len2dlc(length): + """Calculate the DLC from data length. + + :param int length: Length in number of bytes (0-64) + + :returns: DLC (0-15) + :rtype: int + """ + if length <= 8: + return length + for dlc, nof_bytes in enumerate(CAN_FD_DLC): + if nof_bytes >= length: + return dlc + return 15 + + +def dlc2len(dlc): + """Calculate the data length from DLC. + + :param int dlc: DLC (0-15) + + :returns: Data length in number of bytes (0-64) + :rtype: int + """ + return CAN_FD_DLC[dlc] if dlc <= 15 else 64 + + if __name__ == "__main__": print("Searching for configuration named:") print("\n".join(CONFIG_FILES)) diff --git a/test/logformats_test.py b/test/logformats_test.py index d4cf413de..095a8946d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -221,12 +221,17 @@ def test_writer_and_reader(self): def test_reader(self): logfile = os.path.join(os.path.dirname(__file__), "data", "logfile.blf") messages = list(can.BLFReader(logfile)) - self.assertEqual(len(messages), 1) + self.assertEqual(len(messages), 2) self.assertEqual(messages[0], can.Message( extended_id=False, arbitration_id=0x64, data=[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])) + self.assertEqual(messages[1], + can.Message( + is_error_frame=True, + extended_id=True, + arbitration_id=0x1FFFFFFF)) if __name__ == '__main__': From 6dbf15b64dd04d9f3a3ccd2091b8a9174af1fb68 Mon Sep 17 00:00:00 2001 From: Fabian Henze Date: Wed, 28 Mar 2018 15:51:24 +0200 Subject: [PATCH 070/168] ixxat & cygwin: Fix issue where exceptions in driver functions were ignored Since ctypes has no HRESULT attribute under cygwin or linux, the ixxat driver would work, but would not handle any exceptions. --- can/ctypesutil.py | 7 ++++++- can/interfaces/ixxat/canlib.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/can/ctypesutil.py b/can/ctypesutil.py index 5525f56a8..6dc372268 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -12,7 +12,7 @@ log = logging.getLogger('can.ctypesutil') -__all__ = ['CLibrary', 'HANDLE', 'PHANDLE'] +__all__ = ['CLibrary', 'HANDLE', 'PHANDLE', 'HRESULT'] try: _LibBase = ctypes.WinDLL @@ -84,8 +84,13 @@ def function_type(self): if sys.platform == "win32": CLibrary = CLibrary_Win32 + HRESULT = ctypes.HRESULT else: CLibrary = CLibrary_Unix + if sys.platform == "cygwin": + # Define HRESULT for cygwin + class HRESULT(ctypes.c_long): + pass # Common win32 definitions diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 5013f60e1..cc7b44606 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -17,7 +17,7 @@ from can import Message from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) -from can.ctypesutil import CLibrary, HANDLE, PHANDLE +from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT from can.interfaces.ixxat import constants, structures @@ -130,7 +130,7 @@ def __check_status(result, function, arguments): _canlib.map_symbol("vciInitialize", ctypes.c_long, (), __check_status) #void VCIAPI vciFormatError (HRESULT hrError, PCHAR pszText, UINT32 dwsize); - _canlib.map_symbol("vciFormatError", None, (ctypes.HRESULT, ctypes.c_char_p, ctypes.c_uint32)) + _canlib.map_symbol("vciFormatError", None, (ctypes_HRESULT, ctypes.c_char_p, ctypes.c_uint32)) # Hack to have vciFormatError as a free function vciFormatError = functools.partial(__vciFormatError, _canlib) From 723a48746a596d88890d9947fe3336970313bf8f Mon Sep 17 00:00:00 2001 From: Fabian Henze Date: Wed, 28 Mar 2018 15:51:31 +0200 Subject: [PATCH 071/168] Fix opening ixxat by hardware id on x64 by being explicit about struct types Previously the unions would be too large on x64 systems causing the offset of the following fields to shift + add a bit of debugging output that would have helped me find the issue --- can/interfaces/ixxat/canlib.py | 2 ++ can/interfaces/ixxat/structures.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index cc7b44606..ed4471766 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -314,6 +314,8 @@ def __init__(self, channel, can_filters=None, **config): else: if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): break + else: + log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) _canlib.vciEnumDeviceClose(self._device_handle) _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) diff --git a/can/interfaces/ixxat/structures.py b/can/interfaces/ixxat/structures.py index 93eadd37c..72cab99b7 100644 --- a/can/interfaces/ixxat/structures.py +++ b/can/interfaces/ixxat/structures.py @@ -11,8 +11,8 @@ class LUID(ctypes.Structure): _fields_ = [ - ("LowPart", ctypes.c_ulong), - ("HighPart", ctypes.c_long), + ("LowPart", ctypes.c_uint32), + ("HighPart", ctypes.c_int32), ] PLUID = ctypes.POINTER(LUID) @@ -27,9 +27,9 @@ class VCIID(ctypes.Union): class GUID(ctypes.Structure): _fields_ = [ - ("Data1", ctypes.c_long), - ("Data2", ctypes.c_short), - ("Data3", ctypes.c_short), + ("Data1", ctypes.c_uint32), + ("Data2", ctypes.c_uint16), + ("Data3", ctypes.c_uint16), ("Data4", ctypes.c_char * 8), ] From 40f660c7904ac407e35ad8b7dde979b5a70cf542 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 17 Apr 2018 15:59:10 +0200 Subject: [PATCH 072/168] Fix SocketCAN version selection, many crashes and re-enable tests (#293) * drop support for (C)Python 3.3 * use socketcan_ctypes on Python 3.4 as well * test with CPython 3.3, but allow failures * use feature checking instead of version checking in choose_socketcan_implementation() * try to reenable socketcan testing * added comment to BasicTestSocketCan * fix choose_socketcan_implementation() implementation & added many comments --- .appveyor.yml | 11 ++++++-- .travis.yml | 10 +++++-- README.rst | 2 +- can/util.py | 63 +++++++++++++++++++++++++++++------------- doc/development.rst | 2 ++ setup.py | 4 +-- test/back2back_test.py | 7 +++-- 7 files changed, 69 insertions(+), 30 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index efa47c7d8..644e0c6bc 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,16 +6,23 @@ environment: # http://www.appveyor.com/docs/installed-software#python - PYTHON: "C:\\Python27" - - PYTHON: "C:\\Python33" - PYTHON: "C:\\Python34" - PYTHON: "C:\\Python35" - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python27-x64" - - PYTHON: "C:\\Python33-x64" - PYTHON: "C:\\Python34-x64" - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36-x64" + # officially unsupported + - PYTHON: "C:\\Python33" + - PYTHON: "C:\\Python33-x64" + +matrix: + allow_failures: + - PYTHON: "C:\\Python33" + - PYTHON: "C:\\Python33-x64" + install: # We need to install the python-can library itself - "%PYTHON%\\python.exe -m pip install .[test]" diff --git a/.travis.yml b/.travis.yml index 0a26c32bf..065f12081 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: python python: # CPython: - "2.7" - - "3.3" + - "3.3" # but allowed to fail - "3.4" - "3.5" - "3.6" @@ -37,11 +37,15 @@ matrix: - os: osx python: "nightly" - # allow all nighly builds to fail, since these python versions might be unstable - # we do not allow dev builds to fail, since these builds are stable enough allow_failures: + # allow all nighly builds to fail, since these python versions might be unstable - python: "nightly" + # we do not allow dev builds to fail, since these builds are considered stable enough + + # Python 3.3 tests are allowed to fail since Python 3.3 is not offically supported any more + - python: "3.3" + install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi - travis_retry pip install .[test] diff --git a/README.rst b/README.rst index f8b1bca26..8e9ad8b7d 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ Python developers; providing `common abstractions to different hardware devices`, and a suite of utilities for sending and receiving messages on a can bus. -The library supports Python 2.7, Python 3.3+ as well as PyPy 2 & 3 and runs on Mac, Linux and Windows. +The library supports Python 2.7, Python 3.4+ as well as PyPy 2 & 3 and runs on Mac, Linux and Windows. You can find more information in the documentation, online at `python-can.readthedocs.org `__. diff --git a/can/util.py b/can/util.py index cec42341d..6ca57b5ea 100644 --- a/can/util.py +++ b/can/util.py @@ -175,31 +175,56 @@ def load_config(path=None, config=None): def choose_socketcan_implementation(): - """Set the best version of SocketCAN for this system. + """Set the best version of the SocketCAN module for this system. - :param config: The can.rc configuration dictionary - :raises Exception: If the system doesn't support SocketCAN + :rtype: str + :return: + either 'socketcan_ctypes' or 'socketcan_native', + depending on the current platform and environment + :raises Exception: If the system doesn't support SocketCAN at all """ + # Check OS: SocketCAN is available only under Linux if not sys.platform.startswith('linux'): - msg = 'SocketCAN not available under {}'.format( - sys.platform) + msg = 'SocketCAN not available under {}'.format(sys.platform) raise Exception(msg) + + # Check release: SocketCAN was added to Linux 2.6.25 + rel_string = platform.release() + m = re.match(r'\d+\.\d+\.\d', rel_string) + if not m: # None or empty + msg = 'Bad linux release {}'.format(rel_string) + raise Exception(msg) + rel_num = [int(i) for i in rel_string[:m.end()].split('.')] + + if (rel_num < [2, 6, 25]): + msg = 'SocketCAN not available under Linux {}'.format(rel_string) + raise Exception(msg) + + # Check Python version: + # + # CPython: + # Support for SocketCAN was added in Python 3.3, but support for + # CAN FD frames (with socket.CAN_RAW_FD_FRAMES) was just added + # to Python in version 3.5. + # So we want to use socketcan_native only on Python >= 3.5 (see #274). + # + # PyPy: + # Furthermore, socket.CAN_* is not supported by PyPy 2 or 3 (as of + # April 2018) at all. Thus we want to use socketcan_ctypes there as well. + # + # General approach: + # To support possible future versions of current platforms as well as + # potential other ones, we take the approach of feature checking instead + # of platform/version checking. + + try: + # try to import typical attributes + from socket import CAN_RAW, CAN_BCM, CAN_RAW_FD_FRAMES + except ImportError: + return 'socketcan_ctypes' else: - # Check release: SocketCAN was added to Linux 2.6.25 - rel_string = platform.release() - m = re.match(r'\d+\.\d+\.\d', rel_string) - if m is None: - msg = 'Bad linux release {}'.format(rel_string) - raise Exception(msg) - rel_num = [int(i) for i in rel_string[:m.end()].split('.')] - if (rel_num >= [2, 6, 25]): - # Check Python version: SocketCAN was added in 3.3 - return 'socketcan_native' if sys.version_info >= (3, 3) else 'socketcan_ctypes' - else: - msg = 'SocketCAN not available under Linux {}'.format( - rel_string) - raise Exception(msg) + return 'socketcan_native' def set_logging_level(level_name=None): diff --git a/doc/development.rst b/doc/development.rst index 03dbd0d0c..c3ebb0541 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -21,6 +21,8 @@ The following assumes that the commands are executed from the root of the reposi ``python setup.py install``. - The unit tests can be run with ``python setup.py test``. The tests can be run with ``python2``, ``python3``, ``pypy`` or ``pypy3`` to test with other python versions, if they are installed. + Maybe, you need to execute ``pip3 install python-can[test]`` (or only ``pip`` for Python 2), + if some dependencies are missing. - The docs can be built with ``sphinx-build doc/ doc/_build``. Appending ``-n`` to the command makes Sphinx complain about more subtle problems. diff --git a/setup.py b/setup.py index 03ee7945f..a378af621 100644 --- a/setup.py +++ b/setup.py @@ -37,8 +37,6 @@ # Code version=version, packages=find_packages(), - # see https://www.python.org/dev/peps/pep-0345/#version-specifiers - python_requires='>=2.7,!=3.0,!=3.1,!=3.2', # Author author="Brian Thorne", @@ -54,6 +52,8 @@ }, # Installation + # see https://www.python.org/dev/peps/pep-0345/#version-specifiers + python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", install_requires=[ 'setuptools', #'Deprecated >= 1.1.0', diff --git a/test/back2back_test.py b/test/back2back_test.py index b1ff537c0..304bec690 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -135,10 +135,11 @@ def test_fd_message_with_brs(self): data=[0xff] * 48) self._send_and_receive(msg) -# FIXME -@unittest.skip("skip until CAN FD support is fixed, see issue #274") -#@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") class BasicTestSocketCan(unittest.TestCase): + """ + TODO Test more thoroughly. See #273. + """ def setUp(self): socketcan_version = can.util.choose_socketcan_implementation() From cac82833fba96b5f9edbeff8ecc599aa131ed0fe Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 10 Apr 2018 22:09:11 +0200 Subject: [PATCH 073/168] added file that are generated by tests to the ignore list --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7666d148a..96acb31a4 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ nosetests.xml coverage.xml *,cover .hypothesis/ +test.* # Translations *.mo From 9cccc61aeaef3e6745a754435229eabf3cc1d159 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 10 Apr 2018 22:10:12 +0200 Subject: [PATCH 074/168] removed the executable flag from test/listener_test.py --- test/listener_test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 test/listener_test.py diff --git a/test/listener_test.py b/test/listener_test.py old mode 100755 new mode 100644 From d26e0359c4c14cb6bf825ddc64b3803664f46241 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 10 Apr 2018 22:13:49 +0200 Subject: [PATCH 075/168] add nice error handler & tests & added IS_UNIX flag in conifg --- can/interfaces/socketcan/socketcan_common.py | 25 ++++++++++++++++ test/config.py | 1 + test/sockectan_helpers.py | 31 ++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 test/sockectan_helpers.py diff --git a/can/interfaces/socketcan/socketcan_common.py b/can/interfaces/socketcan/socketcan_common.py index 05ce48b6c..55018d87a 100644 --- a/can/interfaces/socketcan/socketcan_common.py +++ b/can/interfaces/socketcan/socketcan_common.py @@ -5,6 +5,8 @@ Defines common socketcan functions. """ +import os +import errno import struct from can.interfaces.socketcan.socketcan_constants import CAN_EFF_FLAG @@ -32,3 +34,26 @@ def pack_filters(can_filters=None): filter_data.append(can_mask) return struct.pack(can_filter_fmt, *filter_data) + + +def error_code_to_str(code): + """ + Converts a given error code (errno) to a useful and human readable string. + + :param int error_code: a possibly invalid/unknown error code + :rtype: str + :returns: a string explaining and containing the given error code, or a string + explaining that the errorcode is unknown if that is the case + """ + + try: + name = errno.errorcode[code] + except KeyError: + name = "UNKNOWN" + + try: + description = os.strerror(code) + except ValueError: + description = "no description available" + + return "{} (errno {}): {}".format(name, code, description) diff --git a/test/config.py b/test/config.py index a48d8c72c..3c37bcbe6 100644 --- a/test/config.py +++ b/test/config.py @@ -34,6 +34,7 @@ IS_WINDOWS = "windows" in _sys or ("win" in _sys and "darwin" not in _sys) IS_LINUX = "linux" in _sys IS_OSX = "darwin" in _sys +IS_UNIX = IS_LINUX or IS_OSX if (IS_WINDOWS and IS_LINUX) or (IS_LINUX and IS_OSX) or (IS_WINDOWS and IS_OSX): raise EnvironmentError( diff --git a/test/sockectan_helpers.py b/test/sockectan_helpers.py new file mode 100644 index 000000000..c92a1943d --- /dev/null +++ b/test/sockectan_helpers.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + +from __future__ import absolute_import + +import unittest + +from .config import * +from can.interfaces.socketcan.socketcan_common import error_code_to_str + +class TestSocketCanHelpers(unittest.TestCase): + + @unittest.skipUnless(IS_UNIX, "skip if not on UNIX") + def test_error_code_to_str(self): + """ + Check that the function does not crash & always + returns a least one character. + """ + + # all possible & also some invalid error codes + test_data = range(0, 256) + (-1, 256, 5235, 346264) + + for error_code in test_data: + string = error_code_to_str(error_code) + self.assertTrue(string) # not None or empty + +if __name__ == '__main__': + unittest.main() From 9c7331626b85fb0b765f1775624ca3c0728b0493 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 10 Apr 2018 22:15:20 +0200 Subject: [PATCH 076/168] patched the send/write methods of both socketcan interfaces, the now handle sending more robustly and ctypes gives way better error messages (it includes errno & a human readable description) --- can/interfaces/socketcan/socketcan_ctypes.py | 29 +++++++++++++------- can/interfaces/socketcan/socketcan_native.py | 11 ++++---- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index d9e7f225b..484055ccc 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -18,7 +18,7 @@ from can.bus import BusABC from can.message import Message from can.interfaces.socketcan.socketcan_constants import * # CAN_RAW -from can.interfaces.socketcan.socketcan_common import * # parseCanFilters +from can.interfaces.socketcan.socketcan_common import * # Set up logging log = logging.getLogger('can.socketcan.ctypes') @@ -26,7 +26,7 @@ if not sys.platform.startswith("win32"): - libc = ctypes.cdll.LoadLibrary(find_library("c")) + libc = ctypes.CDLL(find_library("c"), use_errno=True) log.info("Loading libc with ctypes") else: log.warning("libc is unavailable") @@ -43,8 +43,6 @@ class SocketcanCtypes_Bus(BusABC): An implementation of the :class:`can.bus.BusABC` for SocketCAN using :mod:`ctypes`. """ - channel_info = "ctypes socketcan channel" - def __init__(self, channel='vcan0', receive_own_messages=False, @@ -57,6 +55,7 @@ def __init__(self, self.socket = createSocket() self.channel = channel + self.channel_info = "ctypes socketcan channel '%s'" % channel log.debug("Result of createSocket was %d", self.socket) @@ -138,12 +137,22 @@ def send(self, msg, timeout=None): if not ready_send_sockets: raise can.CanError("Timeout while sending") - bytes_sent = libc.write(self.socket, ctypes.byref(frame), ctypes.sizeof(frame)) - - if bytes_sent == -1: - raise can.CanError("can.socketcan.ctypes failed to transmit") - elif bytes_sent == 0: - raise can.CanError("Transmit buffer overflow") + # all sizes & lengths are in bytes + total_sent = 0 + remaining = ctypes.sizeof(frame) + while remaining > 0: + # this might not send the entire frame + # see: http://man7.org/linux/man-pages/man2/write.2.html + bytes_sent = libc.write(self.socket, ctypes.byref(frame, total_sent), remaining) + + if bytes_sent == 0: + raise can.CanError("Transmit buffer overflow") + elif bytes_sent == -1: + error_message = error_code_to_str(ctypes.get_errno()) + raise can.CanError("can.socketcan_ctypes failed to transmit: {}".format(error_message)) + + total_sent += bytes_sent + remaining -= bytes_sent log.debug("Frame transmitted with %s bytes", bytes_sent) diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 82e49511a..8e2aaa65a 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -35,7 +35,7 @@ import can from can.interfaces.socketcan.socketcan_constants import * # CAN_RAW, CAN_*_FLAG -from can.interfaces.socketcan.socketcan_common import * # parseCanFilters +from can.interfaces.socketcan.socketcan_common import * from can import Message, BusABC from can.broadcastmanager import ModifiableCyclicTaskABC, RestartableCyclicTaskABC, LimitedDurationCyclicSendTaskABC @@ -465,6 +465,7 @@ def send(self, msg, timeout=None): log.debug("We've been asked to write a message to the bus") logger_tx = log.getChild("tx") logger_tx.debug("sending: %s", msg) + if timeout: # Wait for write availability _, ready_send_sockets, _ = select.select([], [self.socket], [], timeout) @@ -472,12 +473,10 @@ def send(self, msg, timeout=None): raise can.CanError("Timeout while sending") try: - bytes_sent = self.socket.send(build_can_frame(msg)) + self.socket.sendall(build_can_frame(msg)) except OSError as exc: - raise can.CanError("Transmit failed (%s)" % exc) - - if bytes_sent == 0: - raise can.CanError("Transmit buffer overflow") + error_message = error_code_to_str(exc.errno) + raise can.CanError("can.socketcan_native failed to transmit: {}".format(error_message)) def send_periodic(self, msg, period, duration=None): task = CyclicSendTask(self.channel, msg, period) From b97a502108967f2ff93b18104863bc5cc1c4c393 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 27 Apr 2018 10:58:06 +0200 Subject: [PATCH 077/168] fix log levels of socketcan library loading errors --- can/interfaces/socketcan/socketcan_native.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 8e2aaa65a..3eda5299f 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -3,34 +3,33 @@ """ This implementation is for versions of Python that have native -can socket and can bcm socket support: >3.4 +can socket and can bcm socket support: >=3.5 """ import logging -import select import threading + +import os +import select import socket import struct - import errno -import os - log = logging.getLogger('can.socketcan.native') log_tx = log.getChild("tx") log_rx = log.getChild("rx") -log.info("Loading socketcan native backend") +log.debug("Loading socketcan native backend") try: import fcntl except ImportError: - log.warning("fcntl not available on this platform") + log.error("fcntl not available on this platform") try: socket.CAN_RAW except: - log.debug("CAN_* properties not found in socket module. These are required to use native socketcan") + log.error("CAN_* properties not found in socket module. These are required to use native socketcan") import can From ddfac970b8686d9d73d136d10ad6543669bbc0a2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 14 May 2018 16:41:11 +0200 Subject: [PATCH 078/168] Feature: API for automatic configuration detection (#278) * factored out some common import functionality * cleaned up interface.py * make class Bus in interface.py extend BusABC (IDEs can now see the attributes) * added static BusABC._detect_available_configs() * added public detect_available_channels() method * added _detect_available_configs() to virtual bus * added locks around the global channels variable in virtual bus * added unit tests for detect_available_configs() * added channel detection for socketcan * added tests for socketcan channel detection * catch ImportErrors in detect_available_configs() * skip socketcan test on windwos * various cleanups --- can/__init__.py | 2 +- can/bus.py | 20 +++ can/interface.py | 173 ++++++++++++++----- can/interfaces/__init__.py | 1 + can/interfaces/socketcan/__init__.py | 1 + can/interfaces/socketcan/socketcan_common.py | 35 ++++ can/interfaces/socketcan/socketcan_ctypes.py | 8 +- can/interfaces/socketcan/socketcan_native.py | 19 +- can/interfaces/virtual.py | 65 ++++++- setup.py | 14 +- test/sockectan_helpers.py | 19 +- test/test_detect_available_configs.py | 58 +++++++ 12 files changed, 347 insertions(+), 68 deletions(-) create mode 100644 test/test_detect_available_configs.py diff --git a/can/__init__.py b/can/__init__.py index f612eeda4..f784ae1b1 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -35,7 +35,7 @@ class CanError(IOError): from can.notifier import Notifier from can.interfaces import VALID_INTERFACES from . import interface -from .interface import Bus +from .interface import Bus, detect_available_configs from can.broadcastmanager import send_periodic, \ CyclicSendTaskABC, \ diff --git a/can/bus.py b/can/bus.py index ede8a74d0..5bb370382 100644 --- a/can/bus.py +++ b/can/bus.py @@ -25,6 +25,11 @@ class BusABC(object): As well as setting the `channel_info` attribute to a string describing the interface. + + They may implement :meth:`~can.BusABC._detect_available_configs` to allow + the interface to report which configurations are currently available for + new connections. + """ #: a string describing the underlying bus channel @@ -146,4 +151,19 @@ def shutdown(self): """ self.flush_tx_buffer() + @staticmethod + def _detect_available_configs(): + """Detect all configurations/channels that this interface could + currently connect with. + + This might be quite time consuming. + + May not to be implemented by every interface on every platform. + + :rtype: Iterator[dict] + :return: an iterable of dicts, each being a configuration suitable + for usage in the interface's bus constructor. + """ + raise NotImplementedError() + __metaclass__ = ABCMeta diff --git a/can/interface.py b/can/interface.py index 96a22c780..f6ba2bc56 100644 --- a/can/interface.py +++ b/can/interface.py @@ -9,38 +9,91 @@ from __future__ import absolute_import -import can +import sys import importlib - -from can.broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC from pkg_resources import iter_entry_points -from can.util import load_config +import logging + +import can +from .bus import BusABC +from .broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC +from .util import load_config + +if sys.version_info.major > 2: + basestring = str + + +log = logging.getLogger('can.interface') +log_autodetect = log.getChild('detect_available_configs') # interface_name => (module, classname) BACKENDS = { - 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), - 'socketcan_ctypes': ('can.interfaces.socketcan', 'SocketcanCtypes_Bus'), - 'socketcan_native': ('can.interfaces.socketcan', 'SocketcanNative_Bus'), - 'serial': ('can.interfaces.serial.serial_can', 'SerialBus'), - 'pcan': ('can.interfaces.pcan', 'PcanBus'), - 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), - 'ixxat': ('can.interfaces.ixxat', 'IXXATBus'), - 'nican': ('can.interfaces.nican', 'NicanBus'), - 'iscan': ('can.interfaces.iscan', 'IscanBus'), - 'virtual': ('can.interfaces.virtual', 'VirtualBus'), - 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), - 'vector': ('can.interfaces.vector', 'VectorBus'), - 'slcan': ('can.interfaces.slcan', 'slcanBus') + 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), + 'socketcan_ctypes': ('can.interfaces.socketcan', 'SocketcanCtypes_Bus'), + 'socketcan_native': ('can.interfaces.socketcan', 'SocketcanNative_Bus'), + 'serial': ('can.interfaces.serial.serial_can','SerialBus'), + 'pcan': ('can.interfaces.pcan', 'PcanBus'), + 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), + 'ixxat': ('can.interfaces.ixxat', 'IXXATBus'), + 'nican': ('can.interfaces.nican', 'NicanBus'), + 'iscan': ('can.interfaces.iscan', 'IscanBus'), + 'virtual': ('can.interfaces.virtual', 'VirtualBus'), + 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), + 'vector': ('can.interfaces.vector', 'VectorBus'), + 'slcan': ('can.interfaces.slcan', 'slcanBus') } - BACKENDS.update({ interface.name: (interface.module_name, interface.attrs[0]) for interface in iter_entry_points('python_can.interface') }) -class Bus(object): +def _get_class_for_interface(interface): + """ + Returns the main bus class for the given interface. + + :raises: + NotImplementedError if the interface is not known + :raises: + ImportError if there was a problem while importing the + interface or the bus class within that + """ + + # filter out the socketcan special case + if interface == 'socketcan': + try: + interface = can.util.choose_socketcan_implementation() + except Exception as e: + raise ImportError("Cannot choose socketcan implementation: {}".format(e)) + + # Find the correct backend + try: + module_name, class_name = BACKENDS[interface] + except KeyError: + raise NotImplementedError("CAN interface '{}' not supported".format(interface)) + + # Import the correct interface module + try: + module = importlib.import_module(module_name) + except Exception as e: + raise ImportError( + "Cannot import module {} for CAN interface '{}': {}".format(module_name, interface, e) + ) + + # Get the correct class + try: + bus_class = getattr(module, class_name) + except Exception as e: + raise ImportError( + "Cannot import class {} from module {} for CAN interface '{}': {}" + .format(class_name, module_name, interface, e) + ) + + return bus_class + + +class Bus(BusABC): """ Instantiates a CAN Bus of the given `bustype`, falls back to reading a configuration file from default locations. @@ -61,39 +114,77 @@ def __new__(cls, other, channel=None, *args, **kwargs): or set in the can.rc config. """ + + # Figure out the configuration config = load_config(config={ - 'interface': kwargs.get('bustype'), + 'interface': kwargs.get('bustype', kwargs.get('interface')), 'channel': channel }) + # remove the bustype & interface so it doesn't get passed to the backend if 'bustype' in kwargs: - # remove the bustype so it doesn't get passed to the backend del kwargs['bustype'] - interface = config['interface'] - channel = config['channel'] + if 'interface' in kwargs: + del kwargs['interface'] - # Import the correct Bus backend - try: - (module_name, class_name) = BACKENDS[interface] - except KeyError: - raise NotImplementedError("CAN interface '{}' not supported".format(interface)) + cls = _get_class_for_interface(config['interface']) + return cls(channel=config['channel'], *args, **kwargs) + + +def detect_available_configs(interfaces=None): + """Detect all configurations/channels that the interfaces could + currently connect with. + + This might be quite time consuming. + + Automated configuration detection may not be implemented by + every interface on every platform. This method will not raise + an error in that case, but with rather return an empty list + for that interface. + + :param interfaces: either + - the name of an interface to be searched in as a string, + - an iterable of interface names to search in, or + - `None` to search in all known interfaces. + :rtype: list of `dict`s + :return: an iterable of dicts, each suitable for usage in + :class:`can.interface.Bus`'s constructor. + """ + + # Figure out where to search + if interfaces is None: + # use an iterator over the keys so we do not have to copy it + interfaces = BACKENDS.keys() + elif isinstance(interfaces, basestring): + interfaces = [interfaces, ] + # else it is supposed to be an iterable of strings + + result = [] + for interface in interfaces: try: - module = importlib.import_module(module_name) - except Exception as e: - raise ImportError( - "Cannot import module {} for CAN interface '{}': {}".format(module_name, interface, e) - ) + bus_class = _get_class_for_interface(interface) + except ImportError: + log_autodetect.debug('interface "%s" can not be loaded for detection of available configurations', interface) + continue + + # get available channels try: - cls = getattr(module, class_name) - except Exception as e: - raise ImportError( - "Cannot import class {} from module {} for CAN interface '{}': {}".format( - class_name, module_name, interface, e - ) - ) + available = list(bus_class._detect_available_configs()) + except NotImplementedError: + log_autodetect.debug('interface "%s" does not support detection of available configurations', interface) + else: + log_autodetect.debug('interface "%s" detected %i available configurations', interface, len(available)) + + # add the interface name to the configs if it is not already present + for config in available: + if 'interface' not in config: + config['interface'] = interface + + # append to result + result += available - return cls(channel, **kwargs) + return result class CyclicSendTask(CyclicSendTaskABC): diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 89ff2cdb7..b4a1e83e6 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -7,6 +7,7 @@ from pkg_resources import iter_entry_points +# TODO: isn't this a unnecessary information duplicate of `can/interface.py :: BACKENDS`? VALID_INTERFACES = set(['kvaser', 'serial', 'pcan', 'socketcan_native', 'socketcan_ctypes', 'socketcan', 'usb2can', 'ixxat', 'nican', 'iscan', 'vector', 'virtual', 'neovi', diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index 2e0a9ad0f..a861b93f3 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -2,6 +2,7 @@ # coding: utf-8 """ +See: https://www.kernel.org/doc/Documentation/networking/can.txt """ from can.interfaces.socketcan import socketcan_constants as constants diff --git a/can/interfaces/socketcan/socketcan_common.py b/can/interfaces/socketcan/socketcan_common.py index 55018d87a..4e904f774 100644 --- a/can/interfaces/socketcan/socketcan_common.py +++ b/can/interfaces/socketcan/socketcan_common.py @@ -5,12 +5,20 @@ Defines common socketcan functions. """ +import logging import os import errno import struct +import sys +if sys.version_info[0] < 3 and os.name == 'posix': + import subprocess32 as subprocess +else: + import subprocess +import re from can.interfaces.socketcan.socketcan_constants import CAN_EFF_FLAG +log = logging.getLogger('can.socketcan_common') def pack_filters(can_filters=None): if can_filters is None: @@ -36,6 +44,33 @@ def pack_filters(can_filters=None): return struct.pack(can_filter_fmt, *filter_data) +_PATTERN_CAN_INTERFACE = re.compile(r"v?can\d+") + +def find_available_interfaces(): + """Returns the names of all open can/vcan interfaces using + the ``ip link list`` command. If the lookup fails, an error + is logged to the console and an empty list is returned. + + :rtype: an iterable of :class:`str` + """ + + try: + # it might be good to add "type vcan", but that might (?) exclude physical can devices + command = ["ip", "-o", "link", "list", "up"] + output = subprocess.check_output(command, universal_newlines=True) + + except Exception as e: # subprocess.CalledProcessError was too specific + log.error("failed to fetch opened can devices: %s", e) + return [] + + else: + #log.debug("find_available_interfaces(): output=\n%s", output) + # output contains some lines like "1: vcan42: ..." + # extract the "vcan42" of each line + interface_names = [line.split(": ", 3)[1] for line in output.splitlines()] + log.debug("find_available_interfaces(): detected: %s", interface_names) + return filter(_PATTERN_CAN_INTERFACE.match, interface_names) + def error_code_to_str(code): """ Converts a given error code (errno) to a useful and human readable string. diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index 484055ccc..8b5b3b0e6 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -18,7 +18,8 @@ from can.bus import BusABC from can.message import Message from can.interfaces.socketcan.socketcan_constants import * # CAN_RAW -from can.interfaces.socketcan.socketcan_common import * +from can.interfaces.socketcan.socketcan_common import \ + pack_filters, find_available_interfaces, error_code_to_str # Set up logging log = logging.getLogger('can.socketcan.ctypes') @@ -164,6 +165,11 @@ def send_periodic(self, msg, period, duration=None): return task + @staticmethod + def _detect_available_configs(): + return [{'interface': 'socketcan_ctypes', 'channel': channel} + for channel in find_available_interfaces()] + class SOCKADDR(ctypes.Structure): # See /usr/include/i386-linux-gnu/bits/socket.h for original struct diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 3eda5299f..83c6874d2 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -3,7 +3,9 @@ """ This implementation is for versions of Python that have native -can socket and can bcm socket support: >=3.5 +can socket and can bcm socket support. + +See :meth:`can.util.choose_socketcan_implementation()`. """ import logging @@ -32,12 +34,12 @@ log.error("CAN_* properties not found in socket module. These are required to use native socketcan") import can - -from can.interfaces.socketcan.socketcan_constants import * # CAN_RAW, CAN_*_FLAG -from can.interfaces.socketcan.socketcan_common import * from can import Message, BusABC - -from can.broadcastmanager import ModifiableCyclicTaskABC, RestartableCyclicTaskABC, LimitedDurationCyclicSendTaskABC +from can.broadcastmanager import ModifiableCyclicTaskABC, \ + RestartableCyclicTaskABC, LimitedDurationCyclicSendTaskABC +from can.interfaces.socketcan.socketcan_constants import * # CAN_RAW, CAN_*_FLAG +from can.interfaces.socketcan.socketcan_common import \ + pack_filters, find_available_interfaces, error_code_to_str # struct module defines a binary packing format: # https://docs.python.org/3/library/struct.html#struct-format-strings @@ -492,6 +494,11 @@ def set_filters(self, can_filters=None): socket.CAN_RAW_FILTER, filter_struct) + @staticmethod + def _detect_available_configs(): + return [{'interface': 'socketcan_native', 'channel': channel} + for channel in find_available_interfaces()] + if __name__ == "__main__": # Create two sockets on vcan0 to test send and receive diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 0f68e3438..c364cda59 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -15,6 +15,8 @@ import queue except ImportError: import Queue as queue +from threading import RLock +import random from can.bus import BusABC @@ -23,22 +25,37 @@ # Channels are lists of queues, one for each connection channels = {} +channels_lock = RLock() class VirtualBus(BusABC): - """Virtual CAN bus using an internal message queue for testing.""" + """ + A virtual CAN bus using an internal message queue. It can be + used for example for testing. + + In this interface, a channel is an arbitarty object used as + an identifier for connected buses. + + Implements :meth:`can.BusABC._detect_available_configs`; see + :meth:`can.VirtualBus._detect_available_configs` for how it + behaves here. + """ def __init__(self, channel=None, receive_own_messages=False, **config): - self.channel_info = 'Virtual bus channel %s' % channel + # the channel identifier may be an arbitrary object + self.channel_id = channel + self.channel_info = 'Virtual bus channel %s' % self.channel_id self.receive_own_messages = receive_own_messages - # Create a new channel if one does not exist - if channel not in channels: - channels[channel] = [] + with channels_lock: + + # Create a new channel if one does not exist + if self.channel_id not in channels: + channels[self.channel_id] = [] + self.channel = channels[self.channel_id] - self.queue = queue.Queue() - self.channel = channels[channel] - self.channel.append(self.queue) + self.queue = queue.Queue() + self.channel.append(self.queue) def recv(self, timeout=None): try: @@ -58,4 +75,34 @@ def send(self, msg, timeout=None): #logger.log(9, 'Transmitted message:\n%s', msg) def shutdown(self): - self.channel.remove(self.queue) + with channels_lock: + self.channel.remove(self.queue) + + # remove if emtpy + if not self.channel: + del channels[self.channel_id] + + @staticmethod + def _detect_available_configs(): + """ + Returns all currently used channels as well as + one other currently unused channel. + + This method will have problems if thousands of + autodetected busses are used at once. + """ + with channels_lock: + available_channels = list(channels.keys()) + + # find a currently unused channel + get_extra = lambda: "channel-{}".format(random.randint(0, 9999)) + extra = get_extra() + while extra in available_channels: + extra = get_extra() + + available_channels += [extra] + + return [ + {'interface': 'virtual', 'channel': channel} + for channel in available_channels + ] diff --git a/setup.py b/setup.py index a378af621..59482bc74 100644 --- a/setup.py +++ b/setup.py @@ -19,13 +19,12 @@ with open('README.rst', 'r') as f: long_description = f.read() +# Dependencies tests_require = [ - 'mock', - 'nose', - 'pyserial >= 3.0', + 'mock >= 2.0.0', + 'nose >= 1.3.7', + 'pyserial >= 3.0' ] -if version_info.major < 3: - tests_require += ['subprocess32 >= 3.2'] setup( # Description @@ -47,7 +46,7 @@ # Package data package_data={ - "": ["CONTRIBUTORS.txt", "LICENSE.txt"], + "": ["CONTRIBUTORS.txt", "LICENSE.txt", "CHANGELOG.txt"], "doc": ["*.*"] }, @@ -56,8 +55,7 @@ python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", install_requires=[ 'setuptools', - #'Deprecated >= 1.1.0', - ], + ] + (['subprocess32 ~= 3.2.7'] if version_info.major < 3 else []), extras_require={ 'serial': ['pyserial >= 3.0'], 'neovi': ['python-ics >= 2.8'], diff --git a/test/sockectan_helpers.py b/test/sockectan_helpers.py index c92a1943d..846de8647 100644 --- a/test/sockectan_helpers.py +++ b/test/sockectan_helpers.py @@ -2,18 +2,22 @@ # coding: utf-8 """ +Tests helpers in `can.interfaces.socketcan.socketcan_common`. """ from __future__ import absolute_import import unittest +from can.interfaces.socketcan.socketcan_common import \ + find_available_interfaces, error_code_to_str + from .config import * -from can.interfaces.socketcan.socketcan_common import error_code_to_str + class TestSocketCanHelpers(unittest.TestCase): - @unittest.skipUnless(IS_UNIX, "skip if not on UNIX") + @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_error_code_to_str(self): """ Check that the function does not crash & always @@ -27,5 +31,16 @@ def test_error_code_to_str(self): string = error_code_to_str(error_code) self.assertTrue(string) # not None or empty + @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") + def test_find_available_interfaces(self): + result = list(find_available_interfaces()) + self.assertGreaterEqual(len(result), 0) + for entry in result: + self.assertRegexpMatches(entry, r"v?can\d+") + if IS_CI: + self.assertGreaterEqual(len(result), 1) + self.assertIn("vcan0", result) + + if __name__ == '__main__': unittest.main() diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py new file mode 100644 index 000000000..f9fa1079c --- /dev/null +++ b/test/test_detect_available_configs.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module tests :meth:`can.BusABC._detect_available_configs` and +:meth:`can.BusABC.detect_available_configs`. +""" + +from __future__ import absolute_import + +import sys +import unittest +if sys.version_info.major > 2: + basestring = str + +from can import detect_available_configs + +from .config import IS_LINUX + + +class TestDetectAvailableConfigs(unittest.TestCase): + + def test_count_returned(self): + # At least virtual has to always return at least one interface + self.assertGreaterEqual (len(detect_available_configs() ), 1) + self.assertEquals (len(detect_available_configs(interfaces=[]) ), 0) + self.assertGreaterEqual (len(detect_available_configs(interfaces='virtual') ), 1) + self.assertGreaterEqual (len(detect_available_configs(interfaces=['virtual']) ), 1) + self.assertGreaterEqual (len(detect_available_configs(interfaces=None) ), 1) + + def test_general_values(self): + configs = detect_available_configs() + for config in configs: + self.assertIn('interface', config) + self.assertIn('channel', config) + self.assertIsInstance(config['interface'], basestring) + + def test_content_virtual(self): + configs = detect_available_configs(interfaces='virtual') + for config in configs: + self.assertEqual(config['interface'], 'virtual') + + def test_content_socketcan(self): + configs = detect_available_configs(interfaces='socketcan') + for config in configs: + self.assertIn(config['interface'], ('socketcan_native', 'socketcan_ctypes')) + + @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") + def test_socketcan_on_ci_server(self): + configs = detect_available_configs(interfaces='socketcan') + self.assertGreaterEqual(len(configs), 1) + self.assertIn('vcan0', [config['channel'] for config in configs]) + + # see TestSocketCanHelpers.test_find_available_interfaces() + + +if __name__ == '__main__': + unittest.main() From f097d9338b402019d9f2f97960ed87c11d588101 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 14 May 2018 17:29:12 +0200 Subject: [PATCH 079/168] more docs --- can/bus.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/can/bus.py b/can/bus.py index a63e2212f..cfebb28ff 100644 --- a/can/bus.py +++ b/can/bus.py @@ -18,11 +18,13 @@ class BusABC(object): - """The CAN Bus Abstract Base Class. + """The CAN Bus Abstract Base Class that serves as the basis + for all concrete interfaces. Concrete implementations *must* implement the following: * :meth:`~can.BusABC.send` to send individual messages * :meth:`~can.BusABC._recv_internal` to receive individual messages + (see note below) * set the :attr:`~can.BusABC.channel_info` attribute to a string describing the underlying bus and/or channel @@ -39,6 +41,14 @@ class BusABC(object): to report which configurations are currently available for new connections + .. note:: + + Previously concrete bus classes had to override :meth:`~can.BusABC.recv` + directly instead of :meth:`~can.BusABC._recv_internal`, but that has + changed to allow the abstract base class to handle in-software message + filtering. Older (custom) interfaces might still be implemented like + that and thus might not provide message filtering. + """ #: a string describing the underlying bus and/or channel From 90a823ba14ff1130a7e9fe8e73316ddebe3685bb Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 14 May 2018 19:09:42 +0200 Subject: [PATCH 080/168] more docs --- can/bus.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/can/bus.py b/can/bus.py index cfebb28ff..2bed1ae25 100644 --- a/can/bus.py +++ b/can/bus.py @@ -78,7 +78,7 @@ def recv(self, timeout=None): that all filters have been applied. That is the case for all internal interfaces. - :param float timeout: Seconds to wait for a message. + :param float timeout: seconds to wait for a message :return: None on timeout or a :class:`can.Message` object. @@ -123,15 +123,25 @@ def _recv_internal(self, timeout): :meth:`~can.BusABC.recv`, to be able to take advantage of the software based filtering provided by :meth:`~can.BusABC.recv`. - This method is not an `@abstractmethod` for now to allow older - external implementations to continue using their existing - custom :meth:`~can.BusABC.recv` implementation. + .. note:: + + This method is not an `@abstractmethod` (for now) to allow older + external implementations to continue using their existing + :meth:`~can.BusABC.recv` implementation. + + :param float timeout: seconds to wait for a message + :rtype: tuple[can.Message, bool] or tuple[None, bool] + :return: + 1. a message that was read or None on timeout + 2. a bool that is True if message filtering has already + been done and else False :raises can.CanError: if an error occurred while reading :raises NotImplementedError: if the bus provides it's own :meth:`~can.BusABC.recv` implementation + """ raise NotImplementedError("Trying to read from a write only bus?") @@ -166,6 +176,8 @@ def send_periodic(self, msg, period, duration=None): :return: A started task instance :rtype: can.CyclicSendTaskABC + .. note:: + Note the duration before the message stops being sent may not be exactly the same as the duration specified by the user. In general the message will be sent at the given rate until at From 3e12e3c786e1dcaff7e85cecd1a6428917f1f9ec Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 14 May 2018 21:15:09 +0200 Subject: [PATCH 081/168] added very basic test for message filtering --- test/test_message_filtering.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index 087643f59..c99e52020 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -14,10 +14,11 @@ from .data.example_data import TEST_ALL_MESSAGES -EXAMPLE_MSG = Message(arbitration_id=123, extended_id=True) +EXAMPLE_MSG = Message(arbitration_id=0x123, extended_id=True) +HIGHEST_MSG = Message(arbitration_id=0x1FFFFFFF, extended_id=True) MATCH_EXAMPLE = [{ - "can_id": 123, + "can_id": 0x123, "can_mask": 0x1FFFFFFF, "extended": True }] @@ -51,7 +52,12 @@ def test_match_nothing(self): self.assertFalse(self.bus._matches_filters(msg)) def test_match_example_message(self): - raise NotImplementedError("TODO") + self.bus.set_filters(MATCH_EXAMPLE) + self.assertTrue(self.bus._matches_filters(EXAMPLE_MSG)) + self.assertFalse(self.bus._matches_filters(HIGHEST_MSG)) + self.bus.set_filters(MATCH_ONLY_HIGHEST) + self.assertFalse(self.bus._matches_filters(EXAMPLE_MSG)) + self.assertTrue(self.bus._matches_filters(HIGHEST_MSG)) if __name__ == '__main__': From a831e48279636b065438255ebc3d7b89716e42e9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 14 May 2018 23:08:24 +0200 Subject: [PATCH 082/168] fix import --- test/test_message_filtering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index c99e52020..278c92187 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -33,7 +33,7 @@ class TestMessageFiltering(unittest.TestCase): def setUp(self): - self.bus = can.Bus(bustype='virtual', channel='testy') + self.bus = Bus(bustype='virtual', channel='testy') def tearDown(self): self.bus.shutdown() From ed1ba8b1d85bba25c3a1cc34f9836c034d1e28c2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 14 May 2018 23:24:56 +0200 Subject: [PATCH 083/168] switch virtaul interface to new receive method --- can/interfaces/virtual.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index c364cda59..a22ede94d 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -16,7 +16,7 @@ except ImportError: import Queue as queue from threading import RLock -import random +from random import randint from can.bus import BusABC @@ -33,7 +33,7 @@ class VirtualBus(BusABC): A virtual CAN bus using an internal message queue. It can be used for example for testing. - In this interface, a channel is an arbitarty object used as + In this interface, a channel is an arbitrary object used as an identifier for connected buses. Implements :meth:`can.BusABC._detect_available_configs`; see @@ -42,6 +42,9 @@ class VirtualBus(BusABC): """ def __init__(self, channel=None, receive_own_messages=False, **config): + config.update({'receive_own_messages': receive_own_messages}) + super(VirtualBus, self).__init__(channel=channel, **config) + # the channel identifier may be an arbitrary object self.channel_id = channel self.channel_info = 'Virtual bus channel %s' % self.channel_id @@ -57,14 +60,14 @@ def __init__(self, channel=None, receive_own_messages=False, **config): self.queue = queue.Queue() self.channel.append(self.queue) - def recv(self, timeout=None): + def _recv_internal(self, timeout=None): try: msg = self.queue.get(block=True, timeout=timeout) except queue.Empty: - return None - - #logger.log(9, 'Received message:\n%s', msg) - return msg + return None, False + else: + #logger.log(9, 'Received message:\n%s', msg) + return msg, False def send(self, msg, timeout=None): msg.timestamp = time.time() @@ -78,7 +81,7 @@ def shutdown(self): with channels_lock: self.channel.remove(self.queue) - # remove if emtpy + # remove if empty if not self.channel: del channels[self.channel_id] @@ -88,14 +91,17 @@ def _detect_available_configs(): Returns all currently used channels as well as one other currently unused channel. - This method will have problems if thousands of - autodetected busses are used at once. + .. note:: + + This method will run into problems if thousands of + autodetected busses are used at once. + """ with channels_lock: available_channels = list(channels.keys()) # find a currently unused channel - get_extra = lambda: "channel-{}".format(random.randint(0, 9999)) + get_extra = lambda: "channel-{}".format(randint(0, 9999)) extra = get_extra() while extra in available_channels: extra = get_extra() From 805159fae7ae174154324ff984602a5620962ce2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 00:23:36 +0200 Subject: [PATCH 084/168] added _recv_internal for kvaser interface --- can/interfaces/kvaser/canlib.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 51cd84d55..c37144835 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -9,6 +9,8 @@ Copyright (C) 2010 Dynamic Controls """ +from __future__ import absolute_import + import sys import time import logging @@ -16,7 +18,7 @@ from can import CanError, BusABC from can import Message -from can.interfaces.kvaser import constants as canstat +from . import constants as canstat log = logging.getLogger('can.kvaser') @@ -314,8 +316,8 @@ def __init__(self, channel, can_filters=None, **config): Time segment 2, that is, the number of quanta from the sampling point to the end of the bit. :param int sjw: - The Synchronisation Jump Width. Decides the maximum number of time quanta - that the controller can resynchronise every bit. + The Synchronization Jump Width. Decides the maximum number of time quanta + that the controller can resynchronize every bit. :param int no_samp: Either 1 or 3. Some CAN controllers can also sample each bit three times. In this case, the bit will be sampled three quanta in a row, @@ -419,8 +421,6 @@ def __init__(self, channel, can_filters=None, **config): self._write_handle = canOpenChannel(channel, flags) canBusOn(self._read_handle) - self.set_filters(can_filters) - can_driver_mode = canstat.canDRIVER_SILENT if driver_mode == DRIVER_MODE_SILENT else canstat.canDRIVER_NORMAL canSetBusOutputControl(self._write_handle, can_driver_mode) log.debug('Going bus on TX handle') @@ -434,7 +434,8 @@ def __init__(self, channel, can_filters=None, **config): log.info(str(exc)) self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR) - super(KvaserBus, self).__init__() + self._is_filtered = False + super(KvaserBus, self).__init__(channel=channel, can_filters=can_filters, **config) def set_filters(self, can_filters=None): """Apply filtering to all messages received by this Bus. @@ -460,11 +461,14 @@ def set_filters(self, can_filters=None): for handle in (self._read_handle, self._write_handle): canSetAcceptanceFilter(handle, can_id, can_mask, extended) except (NotImplementedError, CANLIBError) as e: + self._is_filtered = False log.error('Filtering is not supported - %s', e) else: + self._is_filtered = True log.info('canlib is filtering on ID 0x%X, mask 0x%X', can_id, can_mask) else: + self._is_filtered = False log.info('Hardware filtering has been disabled') try: for handle in (self._read_handle, self._write_handle): @@ -478,9 +482,9 @@ def flush_tx_buffer(self): """ canIoCtl(self._write_handle, canstat.canIOCTL_FLUSH_TX_BUFFER, 0, 0) - def recv(self, timeout=None): + def _recv_internal(self, timeout=None): """ - Read a message from kvaser device. + Read a message from kvaser device and return whether filtering has taken place. """ arb_id = ctypes.c_long(0) data = ctypes.create_string_buffer(64) @@ -531,10 +535,10 @@ def recv(self, timeout=None): rx_msg.flags = flags rx_msg.raw_timestamp = msg_timestamp #log.debug('Got message: %s' % rx_msg) - return rx_msg + return rx_msg, self._is_filtered else: #log.debug('read complete -> status not okay') - return None + return None, self._is_filtered def send(self, msg, timeout=None): #log.debug("Writing a message: {}".format(msg)) From 20518c2248f3b7cfadc3c139ae7a123897b69823 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 00:53:00 +0200 Subject: [PATCH 085/168] add _receive_internal to socketcan native & ctypes --- can/interfaces/socketcan/socketcan_ctypes.py | 58 ++++++++++---------- can/interfaces/socketcan/socketcan_native.py | 48 ++++++++-------- 2 files changed, 55 insertions(+), 51 deletions(-) diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index 8b5b3b0e6..fae5e4622 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -2,9 +2,10 @@ # coding: utf-8 """ +See :meth:`can.util.choose_socketcan_implementation()`. """ -from __future__ import print_function +from __future__ import print_function, absolute_import import ctypes import threading @@ -14,12 +15,10 @@ from ctypes.util import find_library import can +from can import Message, BusABC from can.broadcastmanager import CyclicSendTaskABC, RestartableCyclicTaskABC, ModifiableCyclicTaskABC -from can.bus import BusABC -from can.message import Message -from can.interfaces.socketcan.socketcan_constants import * # CAN_RAW -from can.interfaces.socketcan.socketcan_common import \ - pack_filters, find_available_interfaces, error_code_to_str +from .socketcan_constants import * # CAN_RAW +from .socketcan_common import pack_filters, find_available_interfaces, error_code_to_str # Set up logging log = logging.getLogger('can.socketcan.ctypes') @@ -51,7 +50,7 @@ def __init__(self, """ :param str channel: The can interface name with which to create this bus. An example channel - would be 'vcan0'. + would be 'vcan0' or 'can0'. """ self.socket = createSocket() @@ -60,11 +59,6 @@ def __init__(self, log.debug("Result of createSocket was %d", self.socket) - # Add any socket options such as can frame filters - if 'can_filters' in kwargs and len(kwargs['can_filters']) > 0: - log.debug("Creating a filtered can bus") - self.set_filters(kwargs['can_filters']) - error = bindSocket(self.socket, channel) if error < 0: m = u'bindSocket failed for channel {} with error {}'.format( @@ -73,8 +67,11 @@ def __init__(self, if receive_own_messages: error1 = recv_own_msgs(self.socket) + # TODO handle error - super(SocketcanCtypes_Bus, self).__init__(*args, **kwargs) + self._is_filtered = False + kwargs.update({'receive_own_messages': receive_own_messages}) + super(SocketcanCtypes_Bus, self).__init__(channel=channel, *args, **kwargs) def set_filters(self, can_filters=None): """Apply filtering to all messages received by this Bus. @@ -93,22 +90,26 @@ def set_filters(self, can_filters=None): res = libc.setsockopt(self.socket, SOL_CAN_RAW, CAN_RAW_FILTER, - filter_struct, len(filter_struct) - ) - # TODO Is this serious enough to raise a CanError exception? + filter_struct, + len(filter_struct)) if res != 0: - log.error('Setting filters failed: ' + str(res)) + # fall back to "software filtering" (= not in kernel) + self._is_filtered = False + # TODO Is this serious enough to raise a CanError exception? + log.error('Setting filters failed: %s, falling back to software filtering (not in kernel)', str(res)) + else: + self._is_filtered = True - def recv(self, timeout=None): + def _recv_internal(self, timeout=None): log.debug("Trying to read a msg") - if timeout is None or len(select.select([self.socket], - [], [], timeout)[0]) > 0: - packet = capturePacket(self.socket) - else: + ready_write_sockets, _, _ = select.select([self.socket], [], [], timeout) + if not ready_write_sockets: # socket wasn't readable or timeout occurred - return None + return None, self._is_filtered + + packet = capturePacket(self.socket) log.debug("Receiving a message") @@ -127,16 +128,15 @@ def recv(self, timeout=None): data=packet['Data'] ) - return rx_msg + return rx_msg, self._is_filtered def send(self, msg, timeout=None): frame = _build_can_frame(msg) - if timeout: - # Wait for write availability. write will fail below on timeout - _, ready_send_sockets, _ = select.select([], [self.socket], [], timeout) - if not ready_send_sockets: - raise can.CanError("Timeout while sending") + # Wait for write availability. write will fail below on timeout + _, ready_send_sockets, _ = select.select([], [self.socket], [], timeout) + if not ready_send_sockets: + raise can.CanError("Timeout while sending") # all sizes & lengths are in bytes total_sent = 0 diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 83c6874d2..6220da814 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -407,23 +407,18 @@ def __init__(self, channel, receive_own_messages=False, fd=False, **kwargs): """ :param str channel: The can interface name with which to create this bus. An example channel - would be 'vcan0'. + would be 'vcan0' pr 'can0'. :param bool receive_own_messages: If messages transmitted should also be received back. :param bool fd: If CAN-FD frames should be supported. :param list can_filters: - A list of dictionaries, each containing a "can_id" and a "can_mask". + See BusABC class. """ self.socket = create_socket(CAN_RAW) self.channel = channel self.channel_info = "native socketcan channel '%s'" % channel - # add any socket options such as can frame filters - if 'can_filters' in kwargs and kwargs['can_filters']: # = not None or empty - log.debug("Creating a filtered can bus") - self.set_filters(kwargs['can_filters']) - # set the receive_own_messages paramater try: self.socket.setsockopt(socket.SOL_CAN_RAW, @@ -433,34 +428,37 @@ def __init__(self, channel, receive_own_messages=False, fd=False, **kwargs): log.error("Could not receive own messages (%s)", e) if fd: + # TODO handle errors self.socket.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FD_FRAMES, struct.pack('i', 1)) bind_socket(self.socket, channel) - super(SocketcanNative_Bus, self).__init__() + + kwargs.update({'receive_own_messages': receive_own_messages, 'fd': fd}) + super(SocketcanNative_Bus, self).__init__(channel=channel, **kwargs) def shutdown(self): self.socket.close() - def recv(self, timeout=None): - try: - if timeout is not None: + def _recv_internal(self, timeout=None): + if timeout: + try: # get all sockets that are ready (can be a list with a single value # being self.socket or an empty list if self.socket is not ready) ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout) - else: - ready_receive_sockets = True - except OSError: - # something bad happened (e.g. the interface went down) - log.exception("Error while waiting for timeout") - return None - - if ready_receive_sockets: # not empty - return capture_message(self.socket) + except OSError: + # something bad happened (e.g. the interface went down) + log.exception("Error while waiting for timeout") + ready_receive_sockets = False + else: + ready_receive_sockets = True + + if ready_receive_sockets: # not empty or True + return capture_message(self.socket), True else: # socket wasn't readable or timeout occurred - return None + return None, True def send(self, msg, timeout=None): log.debug("We've been asked to write a message to the bus") @@ -490,6 +488,8 @@ def send_periodic(self, msg, period, duration=None): def set_filters(self, can_filters=None): filter_struct = pack_filters(can_filters) + + # TODO handle errors, see SocketcanCtypes self.socket.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FILTER, filter_struct) @@ -501,6 +501,8 @@ def _detect_available_configs(): if __name__ == "__main__": + # TODO move below to examples? + # Create two sockets on vcan0 to test send and receive # # If you want to try it out you can do the following: @@ -508,6 +510,7 @@ def _detect_available_configs(): # modprobe vcan # ip link add dev vcan0 type vcan # ifconfig vcan0 up + # log.setLevel(logging.DEBUG) def receiver(event): @@ -521,7 +524,8 @@ def sender(event): event.wait() sender_socket = create_socket() bind_socket(sender_socket, 'vcan0') - sender_socket.send(build_can_frame(0x01, b'\x01\x02\x03')) + msg = Message(arbitration_id=0x01, data=b'\x01\x02\x03') + sender_socket.send(build_can_frame(msg)) print("Sender sent a message.") import threading From ba3d3cbe74e226e6d7a3d44fe9a97fc79e79cb50 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 01:03:12 +0200 Subject: [PATCH 086/168] added _recv_internal for PCAN --- can/interfaces/pcan/pcan.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 4f705b302..b9d131611 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -5,15 +5,16 @@ Enable basic CAN over a PCAN USB device. """ +from __future__ import absolute_import, print_function + import logging import sys import time import can -from can import CanError +from can import CanError, Message from can.bus import BusABC -from can.message import Message -from can.interfaces.pcan.PCANBasic import * +from .PCANBasic import * boottimeEpoch = 0 try: @@ -74,14 +75,14 @@ def __init__(self, channel, *args, **kwargs): the PCAN interface includes the `flash()` and `status()` methods. :param str channel: - The can interface name. An example would be PCAN_USBBUS1 + The can interface name. An example would be 'PCAN_USBBUS1' :param int bitrate: Bitrate of channel in bit/s. - Default is 500 Kbs + Default is 500 kbit/s """ - if channel is None or channel == '': + if not channel: raise ArgumentError("Must specify a PCAN channel") else: self.channel_info = channel @@ -108,13 +109,13 @@ def __init__(self, channel, *args, **kwargs): if result != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result)) - super(PcanBus, self).__init__(*args, **kwargs) + super(PcanBus, self).__init__(channel=channel, *args, **kwargs) def _get_formatted_error(self, error): """ - Gets the text using the GetErrorText API function - If the function succeeds, the translated error is returned. If it fails, - a text describing the current error is returned. Multiple errors may + Gets the text using the GetErrorText API function. + If the function call succeeds, the translated error is returned. If it fails, + a text describing the current error is returned. Multiple errors may be present in which case their individual messages are included in the return string, one line per error. """ @@ -160,13 +161,14 @@ def status_is_ok(self): return status == PCAN_ERROR_OK def reset(self): - # Command the PCAN driver to reset the bus after an error. - + """ + Command the PCAN driver to reset the bus after an error. + """ status = self.m_objPCANBasic.Reset(self.m_PcanHandle) - return status == PCAN_ERROR_OK - def recv(self, timeout=None): + def _recv_internal(self, timeout=None): + if HAS_EVENTS: # We will utilize events for the timeout handling timeout_ms = int(timeout * 1000) if timeout is not None else INFINITE @@ -221,7 +223,7 @@ def recv(self, timeout=None): dlc=dlc, data=theMsg.DATA[:dlc]) - return rx_msg + return rx_msg, False def send(self, msg, timeout=None): if msg.id_type: @@ -264,4 +266,7 @@ def shutdown(self): class PcanError(CanError): + """ + TODO: add docs + """ pass From 8c590a814e7f298a2ddf14545e949695bdd51591 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 01:08:05 +0200 Subject: [PATCH 087/168] fix for test & better method signatures --- can/interfaces/pcan/pcan.py | 2 +- can/interfaces/socketcan/socketcan_ctypes.py | 2 +- can/interfaces/socketcan/socketcan_native.py | 2 +- can/interfaces/virtual.py | 2 +- test/test_kvaser.py | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index b9d131611..f388b6953 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -167,7 +167,7 @@ def reset(self): status = self.m_objPCANBasic.Reset(self.m_PcanHandle) return status == PCAN_ERROR_OK - def _recv_internal(self, timeout=None): + def _recv_internal(self, timeout): if HAS_EVENTS: # We will utilize events for the timeout handling diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index fae5e4622..eee9b7a51 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -100,7 +100,7 @@ def set_filters(self, can_filters=None): else: self._is_filtered = True - def _recv_internal(self, timeout=None): + def _recv_internal(self, timeout): log.debug("Trying to read a msg") diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 6220da814..7438e03ad 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -441,7 +441,7 @@ def __init__(self, channel, receive_own_messages=False, fd=False, **kwargs): def shutdown(self): self.socket.close() - def _recv_internal(self, timeout=None): + def _recv_internal(self, timeout): if timeout: try: # get all sockets that are ready (can be a list with a single value diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index a22ede94d..9811e8912 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -60,7 +60,7 @@ def __init__(self, channel=None, receive_own_messages=False, **config): self.queue = queue.Queue() self.channel.append(self.queue) - def _recv_internal(self, timeout=None): + def _recv_internal(self, timeout): try: msg = self.queue.get(block=True, timeout=timeout) except queue.Empty: diff --git a/test/test_kvaser.py b/test/test_kvaser.py index eb0a73ac3..5bcd59103 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -39,7 +39,7 @@ def setUp(self): self.msg = {} self.msg_in_cue = None - self.bus = can.interface.Bus(channel=0, bustype='kvaser') + self.bus = can.Bus(channel=0, bustype='kvaser') def tearDown(self): if self.bus: @@ -123,7 +123,7 @@ def test_send_standard(self): self.assertSequenceEqual(self.msg['data'], [50, 51]) def test_recv_no_message(self): - self.assertEqual(self.bus.recv(), None) + self.assertEqual(self.bus.recv(timeout=0.5), None) def test_recv_extended(self): self.msg_in_cue = can.Message( From 3abbdd7d9b49e8d9a7e1d1e7aa1b3cd32e044250 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 10:24:39 +0200 Subject: [PATCH 088/168] add timeouts to CI tests --- .appveyor.yml | 5 ++++- .travis.yml | 2 +- setup.py | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 644e0c6bc..bea63bb21 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,3 +1,6 @@ +# format version +version: "1.0" + environment: matrix: @@ -33,4 +36,4 @@ test_script: # Note that you must use the environment variable %PYTHON% to refer to # the interpreter you're using - Appveyor does not do anything special # to put the Python version you want to use on PATH. - - "%PYTHON%\\python.exe setup.py test -v" + - "%PYTHON%\\python.exe setup.py test -v --timeout=300" diff --git a/.travis.yml b/.travis.yml index 065f12081..1e6f4be50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,4 +51,4 @@ install: - travis_retry pip install .[test] script: - - py.test -v + - py.test -v --timeout=300 diff --git a/setup.py b/setup.py index 59482bc74..0af5e8577 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ tests_require = [ 'mock >= 2.0.0', 'nose >= 1.3.7', + 'pytest-timeout >= 1.2.1', 'pyserial >= 3.0' ] From f1e3c75c75061559d6e30260631d503bbb866c95 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 10:27:38 +0200 Subject: [PATCH 089/168] remove wrong config fom .appveyor.yml --- .appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index bea63bb21..d46b65721 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,6 +1,3 @@ -# format version -version: "1.0" - environment: matrix: From 0a75a94bcd4a998cc297cfe8a31d43cb3a10335a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 10:45:32 +0200 Subject: [PATCH 090/168] fix correct call to _apply_filters --- can/bus.py | 18 +++++++---- can/interfaces/kvaser/canlib.py | 33 ++++++-------------- can/interfaces/socketcan/socketcan_ctypes.py | 18 ++--------- can/interfaces/socketcan/socketcan_native.py | 4 +-- 4 files changed, 26 insertions(+), 47 deletions(-) diff --git a/can/bus.py b/can/bus.py index 2bed1ae25..62d924d5c 100644 --- a/can/bus.py +++ b/can/bus.py @@ -129,6 +129,12 @@ def _recv_internal(self, timeout): external implementations to continue using their existing :meth:`~can.BusABC.recv` implementation. + .. note:: + + The second return value (whether filtering was already done) may change + over time for some interfaces, like for example in the Kvaser interface. + Thus it cannot be simplified to a constant value. + :param float timeout: seconds to wait for a message :rtype: tuple[can.Message, bool] or tuple[None, bool] :return: @@ -212,17 +218,17 @@ def filters(self): def filters(self, filters): self.set_filters(filters) - def set_filters(self, can_filters=None): + def set_filters(self, filters=None): """Apply filtering to all messages received by this Bus. All messages that match at least one filter are returned. - If `can_filters` is `None`, all messages are matched. + If `filters` is `None`, all messages are matched. If it is a zero size interable, no messages are matched. Calling without passing any filters will reset the applied filters to `None`. - :param Iterator[dict] can_filters: + :param Iterator[dict] filters: A iterable of dictionaries each containing a "can_id", a "can_mask", and an optional "extended" key. @@ -234,10 +240,10 @@ def set_filters(self, can_filters=None): only on the arbitration ID and mask. """ - self._can_filters = can_filters - self._apply_filters() + self._filters = filters + self._apply_filters(self.filters) - def _apply_filters(self): + def _apply_filters(self, filters): """ Hook for applying the filters to the underlying kernel or hardware if supported/implemented by the interface. diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index c37144835..4b6737f2d 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -296,10 +296,7 @@ def __init__(self, channel, can_filters=None, **config): The Channel id to create this bus with. :param list can_filters: - A list of dictionaries each containing a "can_id" and a "can_mask". - - >>> [{"can_id": 0x11, "can_mask": 0x21}] - + See :meth:`can.BusABC.__init__`. Backend Configuration @@ -340,7 +337,9 @@ def __init__(self, channel, can_filters=None, **config): :param int data_bitrate: Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. + """ + log.info("CAN Filters: {}".format(can_filters)) log.info("Got configuration of: {}".format(config)) bitrate = config.get('bitrate', 500000) @@ -437,26 +436,11 @@ def __init__(self, channel, can_filters=None, **config): self._is_filtered = False super(KvaserBus, self).__init__(channel=channel, can_filters=can_filters, **config) - def set_filters(self, can_filters=None): - """Apply filtering to all messages received by this Bus. - - Calling without passing any filters will reset the applied filters. - - Since Kvaser only supports setting one filter per handle, the filtering - will be disabled if more than one filter is requested. - - :param list can_filters: - A list of dictionaries each containing a "can_id", "can_mask" and - "extended". - - >>> [{"can_id": 0x11, "can_mask": 0x21, "extended": False}] - - A filter matches, when `` & can_mask == can_id & can_mask`` - """ - if can_filters and len(can_filters) == 1: - can_id = can_filters[0]['can_id'] - can_mask = can_filters[0]['can_mask'] - extended = 1 if can_filters[0].get('extended') else 0 + def _apply_filters(self, filters): + if filters and len(filters) == 1: + can_id = filters[0]['can_id'] + can_mask = filters[0]['can_mask'] + extended = 1 if filters[0].get('extended') else 0 try: for handle in (self._read_handle, self._write_handle): canSetAcceptanceFilter(handle, can_id, can_mask, extended) @@ -475,6 +459,7 @@ def set_filters(self, can_filters=None): for extended in (0, 1): canSetAcceptanceFilter(handle, 0, 0, extended) except (NotImplementedError, CANLIBError): + # TODO add logging? pass def flush_tx_buffer(self): diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index eee9b7a51..a5eb3169b 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -67,26 +67,14 @@ def __init__(self, if receive_own_messages: error1 = recv_own_msgs(self.socket) - # TODO handle error + # TODO handle potential error self._is_filtered = False kwargs.update({'receive_own_messages': receive_own_messages}) super(SocketcanCtypes_Bus, self).__init__(channel=channel, *args, **kwargs) - def set_filters(self, can_filters=None): - """Apply filtering to all messages received by this Bus. - - Calling without passing any filters will reset the applied filters. - - :param list can_filters: - A list of dictionaries each containing a "can_id" and a "can_mask". - - >>> [{"can_id": 0x11, "can_mask": 0x21}] - - A filter matches, when `` & can_mask == can_id & can_mask`` - - """ - filter_struct = pack_filters(can_filters) + def _apply_filters(self, filters): + filter_struct = pack_filters(filters) res = libc.setsockopt(self.socket, SOL_CAN_RAW, CAN_RAW_FILTER, diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 7438e03ad..935bef268 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -486,8 +486,8 @@ def send_periodic(self, msg, period, duration=None): return task - def set_filters(self, can_filters=None): - filter_struct = pack_filters(can_filters) + def _apply_filters(self, filters): + filter_struct = pack_filters(filters) # TODO handle errors, see SocketcanCtypes self.socket.setsockopt(socket.SOL_CAN_RAW, From a7df72a00619a6f13507fece45dd1abd2aa05823 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 10:57:06 +0200 Subject: [PATCH 091/168] add fix for parameter name --- can/bus.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/bus.py b/can/bus.py index 62d924d5c..3729e7989 100644 --- a/can/bus.py +++ b/can/bus.py @@ -241,7 +241,7 @@ def set_filters(self, filters=None): """ self._filters = filters - self._apply_filters(self.filters) + self._apply_filters(self._filters) def _apply_filters(self, filters): """ @@ -258,10 +258,10 @@ def _matches_filters(self, msg): """ # if no filters are set, all messages are matched - if self._can_filters is None: + if self._filters is None: return True - for can_filter in self._can_filters: + for can_filter in self._filters: # check if this filter even applies to the message if 'extended' in can_filter and \ can_filter['extended'] != msg.is_extended_id: From 30b4157cad8359003abfc3d1539be62eb71aa796 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 11:03:44 +0200 Subject: [PATCH 092/168] docs --- can/bus.py | 23 ++++++++++---------- can/interfaces/socketcan/socketcan_ctypes.py | 2 ++ can/interfaces/socketcan/socketcan_native.py | 3 +++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/can/bus.py b/can/bus.py index 3729e7989..bd1f469fa 100644 --- a/can/bus.py +++ b/can/bus.py @@ -28,7 +28,7 @@ class BusABC(object): * set the :attr:`~can.BusABC.channel_info` attribute to a string describing the underlying bus and/or channel - The *may* implement the following: + They *may* implement the following: * :meth:`~can.BusABC.flush_tx_buffer` to allow discrading any messages yet to be sent * :meth:`~can.BusABC.shutdown` to override how the bus should @@ -43,11 +43,11 @@ class BusABC(object): .. note:: - Previously concrete bus classes had to override :meth:`~can.BusABC.recv` + Previously, concrete bus classes had to override :meth:`~can.BusABC.recv` directly instead of :meth:`~can.BusABC._recv_internal`, but that has changed to allow the abstract base class to handle in-software message - filtering. Older (custom) interfaces might still be implemented like - that and thus might not provide message filtering. + filtering as a fallback. Older (custom) interfaces might still be + implemented like that and thus might not provide message filtering. """ @@ -121,7 +121,8 @@ def _recv_internal(self, timeout): New implementations should always override this method instead of :meth:`~can.BusABC.recv`, to be able to take advantage of the - software based filtering provided by :meth:`~can.BusABC.recv`. + software based filtering provided by :meth:`~can.BusABC.recv` + as a fallback. .. note:: @@ -212,7 +213,7 @@ def __iter__(self): @property def filters(self): - return self._can_filters + return self._filters @filters.setter def filters(self, filters): @@ -261,15 +262,15 @@ def _matches_filters(self, msg): if self._filters is None: return True - for can_filter in self._filters: + for filter in self._filters: # check if this filter even applies to the message - if 'extended' in can_filter and \ - can_filter['extended'] != msg.is_extended_id: + if 'extended' in filter and \ + filter['extended'] != msg.is_extended_id: continue # then check for the mask and id - can_id = can_filter['can_id'] - can_mask = can_filter['can_mask'] + can_id = filter['can_id'] + can_mask = filter['can_mask'] # basically, we compute `msg.arbitration_id & can_mask == can_id & can_mask` # by using the faster, but equivalent from below: diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index a5eb3169b..9a11d40e5 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -41,6 +41,8 @@ class SocketcanCtypes_Bus(BusABC): """ An implementation of the :class:`can.bus.BusABC` for SocketCAN using :mod:`ctypes`. + + Implements :meth:`can.BusABC._detect_available_configs`. """ def __init__(self, diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 935bef268..2a125ea81 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -402,6 +402,9 @@ def capture_message(sock): class SocketcanNative_Bus(BusABC): + """ + Implements :meth:`can.BusABC._detect_available_configs`. + """ def __init__(self, channel, receive_own_messages=False, fd=False, **kwargs): """ From e8c6711fd4b5cac16011e58bb9b7bc11ac0183fa Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 11:11:15 +0200 Subject: [PATCH 093/168] docs --- can/interfaces/socketcan/socketcan_native.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 2a125ea81..e0854fabb 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -410,13 +410,13 @@ def __init__(self, channel, receive_own_messages=False, fd=False, **kwargs): """ :param str channel: The can interface name with which to create this bus. An example channel - would be 'vcan0' pr 'can0'. + would be 'vcan0' or 'can0'. :param bool receive_own_messages: - If messages transmitted should also be received back. + If transmitted messages should also be received by this bus. :param bool fd: If CAN-FD frames should be supported. :param list can_filters: - See BusABC class. + See :meth:`can.BusABC.set_filters`. """ self.socket = create_socket(CAN_RAW) self.channel = channel @@ -508,7 +508,7 @@ def _detect_available_configs(): # Create two sockets on vcan0 to test send and receive # - # If you want to try it out you can do the following: + # If you want to try it out you can do the following (possibly using sudo): # # modprobe vcan # ip link add dev vcan0 type vcan From 73b3fac9f6609dd9c7a8cc88502a66600c057860 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 12:49:31 +0200 Subject: [PATCH 094/168] change Vector backend to use new methods --- can/interfaces/vector/canlib.py | 46 +++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 44b72be62..f88d23506 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -119,8 +119,6 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, else: LOG.info('Install pywin32 to avoid polling') - self.set_filters(can_filters) - try: vxlapi.xlActivateChannel(self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0) @@ -133,26 +131,40 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, vxlapi.xlGetSyncTime(self.port_handle, offset) self._time_offset = time.time() - offset.value * 1e-9 - super(VectorBus, self).__init__() - - def set_filters(self, can_filters=None): - if can_filters: - # Only one filter per ID type allowed - if len(can_filters) == 1 or ( - len(can_filters) == 2 and - can_filters[0].get("extended") != can_filters[1].get("extended")): - for can_filter in can_filters: + self._is_filtered = False + super(VectorBus, self).__init__(channel=channel, can_filters=can_filters, + poll_interval=0.01, receive_own_messages=False, bitrate=None, + rx_queue_size=256, app_name="CANalyzer", **config) + + def _apply_filters(self, filters): + if filters: + # Only up to one filter per ID type allowed + if len(filters) == 1 or (len(filters) == 2 and + filters[0].get("extended") != filters[1].get("extended")): + for can_filter in filters: try: - vxlapi.xlCanSetChannelAcceptance( - self.port_handle, self.mask, + vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, can_filter["can_id"], can_filter["can_mask"], vxlapi.XL_CAN_EXT if can_filter.get("extended") else vxlapi.XL_CAN_STD) except VectorError as exc: LOG.warning("Could not set filters: %s", exc) + # go to fallback + else: + self._is_filtered = True + return else: - LOG.warning("Only one filter per extended or standard ID allowed") + LOG.warning("Only up to one filter per extended or standard ID allowed") + # go to fallback + + # fallback: reset filters + self._is_filtered = False + try: + vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_EXT) + vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_STD) + except VectorError as exc: + LOG.warning("Could not reset filters: %s", exc) - def recv(self, timeout=None): + def _recv_internal(self, timeout): end_time = time.time() + timeout if timeout is not None else None event = vxlapi.XLevent(0) event_count = ctypes.c_uint() @@ -178,10 +190,10 @@ def recv(self, timeout=None): dlc=dlc, data=event.tagData.msg.data[:dlc], channel=event.chanIndex) - return msg + return msg, self._is_filtered if end_time is not None and time.time() > end_time: - return None + return None, self._is_filtered if HAS_EVENTS: # Wait for receive event to occur From 2f489509aba05098d749f6baf79052cfd85c2b0b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 13:08:01 +0200 Subject: [PATCH 095/168] docs --- can/bus.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/can/bus.py b/can/bus.py index bd1f469fa..a030f8db3 100644 --- a/can/bus.py +++ b/can/bus.py @@ -2,7 +2,7 @@ # coding: utf-8 """ -Contains the ABC bus implementation and documentation. +Contains the ABC bus implementation and it's documentation. """ from __future__ import print_function, absolute_import @@ -14,21 +14,19 @@ from .broadcastmanager import ThreadBasedCyclicSendTask -logger = logging.getLogger(__name__) - class BusABC(object): """The CAN Bus Abstract Base Class that serves as the basis for all concrete interfaces. - Concrete implementations *must* implement the following: + Concrete implementations *have to* implement the following: * :meth:`~can.BusABC.send` to send individual messages * :meth:`~can.BusABC._recv_internal` to receive individual messages (see note below) * set the :attr:`~can.BusABC.channel_info` attribute to a string describing the underlying bus and/or channel - They *may* implement the following: + They *might* implement the following: * :meth:`~can.BusABC.flush_tx_buffer` to allow discrading any messages yet to be sent * :meth:`~can.BusABC.shutdown` to override how the bus should @@ -56,7 +54,11 @@ class BusABC(object): @abstractmethod def __init__(self, channel=None, can_filters=None, **config): - """ + """Construct and open a CAN bus instance of the specified type. + + Subclasses should call though this one with all parameters as + it applies filters (for now). + :param channel: The can interface identifier. Expected type is backend dependent. @@ -78,8 +80,14 @@ def recv(self, timeout=None): that all filters have been applied. That is the case for all internal interfaces. - :param float timeout: seconds to wait for a message + To enable receiving for an interface, please override + :meth:`~can.BusABC._recv_internal` instead of this one. + Overriding this method is deprecated. + + :param float timeout: + seconds to wait for a message or None to wait indefinitely + :rtype: can.Message or None :return: None on timeout or a :class:`can.Message` object. :raises can.CanError: @@ -122,7 +130,7 @@ def _recv_internal(self, timeout): New implementations should always override this method instead of :meth:`~can.BusABC.recv`, to be able to take advantage of the software based filtering provided by :meth:`~can.BusABC.recv` - as a fallback. + as a fallback. This method should never be called directly. .. note:: @@ -147,14 +155,15 @@ def _recv_internal(self, timeout): if an error occurred while reading :raises NotImplementedError: if the bus provides it's own :meth:`~can.BusABC.recv` - implementation + implementation (legacy implementation) """ raise NotImplementedError("Trying to read from a write only bus?") @abstractmethod def send(self, msg, timeout=None): - """Transmit a message to CAN bus. + """Transmit a message to the CAN bus. + Override this method to enable the transmit path. :param can.Message msg: A message object. @@ -248,14 +257,23 @@ def _apply_filters(self, filters): """ Hook for applying the filters to the underlying kernel or hardware if supported/implemented by the interface. + + :param Iterator[dict] filters: + See :meth:`~can.BusABC.set_filters` for details. """ pass def _matches_filters(self, msg): """Checks whether the given message matches at least one of the - current filters. + current filters. See :meth:`~can.BusABC.set_filters` for details + on how the filters work. + + This method should not be overridden. - See :meth:`~can.BusABC.set_filters` for details. + :param can.Message msg: + the message to check if matching + :rtype: bool + :return: whether the given message matches at least one filter """ # if no filters are set, all messages are matched @@ -273,7 +291,7 @@ def _matches_filters(self, msg): can_mask = filter['can_mask'] # basically, we compute `msg.arbitration_id & can_mask == can_id & can_mask` - # by using the faster, but equivalent from below: + # by using the shorter, but equivalent from below: if (can_id ^ msg.arbitration_id) & can_mask == 0: return True From 567fb2e81091b439657838055f4981a903f7b5c2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Tue, 15 May 2018 13:43:50 +0200 Subject: [PATCH 096/168] added new bus methods for neovi interface --- can/interfaces/ics_neovi/neovi_bus.py | 96 +++++++-------------------- 1 file changed, 23 insertions(+), 73 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 7cfdd6595..a3d6e24be 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -14,8 +14,7 @@ import logging from collections import deque -from can import Message, CanError -from can.bus import BusABC +from can import Message, CanError, BusABC logger = logging.getLogger(__name__) @@ -30,6 +29,10 @@ class ICSApiError(CanError): + """ + TODO add docs + """ + # A critical error which affects operation or accuracy. ICS_SPY_ERR_CRITICAL = 0x10 # An error which is not understood. @@ -83,13 +86,10 @@ class NeoViBus(BusABC): def __init__(self, channel=None, can_filters=None, **config): """ - :param int channel: The Channel id to create this bus with. :param list can_filters: - A list of dictionaries each containing a "can_id" and a "can_mask". - - >>> [{"can_id": 0x11, "can_mask": 0x21}] + See :meth:`can.BusABC.set_filters` for details. :param use_system_timestamp: Use system timestamp for can messages instead of the hardware time @@ -101,18 +101,17 @@ def __init__(self, channel=None, can_filters=None, **config): Channel bitrate in bit/s. (optional, will enable the auto bitrate feature if not supplied) """ - super(NeoViBus, self).__init__(channel, can_filters, **config) if ics is None: raise ImportError('Please install python-ics') + super(NeoViBus, self).__init__(channel=channel, can_filters=can_filters, **config) + logger.info("CAN Filters: {}".format(can_filters)) logger.info("Got configuration of: {}".format(config)) - self._use_system_timestamp = bool( - config.get('use_system_timestamp', False) - ) + self._use_system_timestamp = bool(config.get('use_system_timestamp', False)) - # TODO: Add support for multiples channels + # TODO: Add support for multiple channels try: channel = int(channel) except ValueError: @@ -133,13 +132,14 @@ def __init__(self, channel=None, can_filters=None, **config): } if bitrate is not None: - if int(bitrate) not in VALID_BITRATES: + bitrate = int(bitrate) + if bitrate not in VALID_BITRATES: raise ValueError( 'Invalid bitrate. Valid bitrates are {}'.format( VALID_BITRATES ) ) - baud_rate_setting = BAUDRATE_SETTING[int(bitrate)] + baud_rate_setting = BAUDRATE_SETTING[bitrate] settings = { 'SetBaudrate': ics.AUTO, 'Baudrate': baud_rate_setting, @@ -154,12 +154,10 @@ def __init__(self, channel=None, can_filters=None, **config): ) logger.info("Using device: {}".format(self.channel_info)) - self.sw_filters = None - self.set_filters(can_filters) self.rx_buffer = deque() self.opened = True - self.network = int(channel) if channel is not None else None + self.network = channel if channel is not None else None # TODO: Change the scaling based on the device type self.ts_scaling = ( @@ -227,7 +225,7 @@ def _set_can_settings(self, channel, setting): setattr(channel_settings, setting, value) ics.set_device_settings(self.dev, device_settings) - def _process_msg_queue(self, timeout=None): + def _process_msg_queue(self, timeout): try: messages, errors = ics.get_messages(self.dev, False, timeout) except ics.RuntimeError: @@ -235,8 +233,6 @@ def _process_msg_queue(self, timeout=None): for ics_msg in messages: if ics_msg.NetworkID != self.network: continue - if not self._is_filter_match(ics_msg.ArbIDOrHeader): - continue self.rx_buffer.append(ics_msg) if errors: logger.warning("%d error(s) found" % errors) @@ -247,26 +243,6 @@ def _process_msg_queue(self, timeout=None): raise error logger.warning(error) - def _is_filter_match(self, arb_id): - """ - If SW filtering is used, checks if the `arb_id` matches any of - the filters setup. - - :param int arb_id: - CAN ID to check against. - - :return: - True if `arb_id` matches any filters - (or if SW filtering is not used). - """ - if not self.sw_filters: - # Filtering done on HW or driver level or no filtering - return True - for can_filter in self.sw_filters: - if not (arb_id ^ can_filter['can_id']) & can_filter['can_mask']: - return True - return False - def _get_timestamp_for_msg(self, ics_msg): if self._use_system_timestamp: # This is the system time stamp. @@ -304,22 +280,21 @@ def _ics_msg_to_message(self, ics_msg): channel=ics_msg.NetworkID ) - def recv(self, timeout=None): - msg = None + def _recv_internal(self, timeout): if not self.rx_buffer: - self._process_msg_queue(timeout=timeout) + self._process_msg_queue(timeout) try: ics_msg = self.rx_buffer.popleft() msg = self._ics_msg_to_message(ics_msg) except IndexError: - pass - return msg + return None, False + else: + return msg, False def send(self, msg, timeout=None): if not self.opened: - return - data = tuple(msg.data) + raise CanError("bus not yet opened") flags = 0 if msg.is_extended_id: @@ -329,8 +304,8 @@ def send(self, msg, timeout=None): message = ics.SpyMessage() message.ArbIDOrHeader = msg.arbitration_id - message.NumberBytesData = len(data) - message.Data = data + message.NumberBytesData = len(msg.data) + message.Data = tuple(msg.data) message.StatusBitField = flags message.StatusBitField2 = 0 message.NetworkID = self.network @@ -339,28 +314,3 @@ def send(self, msg, timeout=None): ics.transmit_messages(self.dev, message) except ics.RuntimeError: raise ICSApiError(*ics.get_last_api_error(self.dev)) - - def set_filters(self, can_filters=None): - """Apply filtering to all messages received by this Bus. - - Calling without passing any filters will reset the applied filters. - - :param list can_filters: - A list of dictionaries each containing a "can_id" and a "can_mask". - - >>> [{"can_id": 0x11, "can_mask": 0x21}] - - A filter matches, when - `` & can_mask == can_id & can_mask`` - - """ - self.sw_filters = can_filters or [] - - if not len(self.sw_filters): - logger.info("Filtering has been disabled") - else: - for can_filter in can_filters: - can_id = can_filter["can_id"] - can_mask = can_filter["can_mask"] - logger.info( - "Filtering on ID 0x%X, mask 0x%X", can_id, can_mask) From b3df2d7d50388fb8afa72f50cbe4ace71de6ae75 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 11:08:39 +0200 Subject: [PATCH 097/168] removed problemativ command line argument on AppVeyor --- .appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index d46b65721..c724c96a2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -33,4 +33,5 @@ test_script: # Note that you must use the environment variable %PYTHON% to refer to # the interpreter you're using - Appveyor does not do anything special # to put the Python version you want to use on PATH. - - "%PYTHON%\\python.exe setup.py test -v --timeout=300" + - "%PYTHON%\\python.exe setup.py test -v" # --timeout=300 -> TODO: + # need to switch to pytest like on Unix first, but that's for another PR From f1d0a5e50e0cffce092d5c0c0ca40ea75689db8f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 11:27:31 +0200 Subject: [PATCH 098/168] better error handling in socketcan and correct filtering for native one --- can/interfaces/socketcan/socketcan_ctypes.py | 3 ++- can/interfaces/socketcan/socketcan_native.py | 21 ++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index 9a11d40e5..f859a78d6 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -86,7 +86,8 @@ def _apply_filters(self, filters): # fall back to "software filtering" (= not in kernel) self._is_filtered = False # TODO Is this serious enough to raise a CanError exception? - log.error('Setting filters failed: %s, falling back to software filtering (not in kernel)', str(res)) + # TODO print error code (the errno, not "res", which is always -1) + log.error('Setting filters failed: falling back to software filtering (not in kernel)') else: self._is_filtered = True diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index e0854fabb..6198493dc 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -458,10 +458,10 @@ def _recv_internal(self, timeout): ready_receive_sockets = True if ready_receive_sockets: # not empty or True - return capture_message(self.socket), True + return capture_message(self.socket), self._is_filtered else: # socket wasn't readable or timeout occurred - return None, True + return None, self._is_filtered def send(self, msg, timeout=None): log.debug("We've been asked to write a message to the bus") @@ -490,12 +490,17 @@ def send_periodic(self, msg, period, duration=None): return task def _apply_filters(self, filters): - filter_struct = pack_filters(filters) - - # TODO handle errors, see SocketcanCtypes - self.socket.setsockopt(socket.SOL_CAN_RAW, - socket.CAN_RAW_FILTER, - filter_struct) + try: + self.socket.setsockopt(socket.SOL_CAN_RAW, + socket.CAN_RAW_FILTER, + pack_filters(filters)) + except socket.error as err: + # fall back to "software filtering" (= not in kernel) + self._is_filtered = False + # TODO Is this serious enough to raise a CanError exception? + log.error('Setting filters failed; falling back to software filtering (not in kernel): %s', err) + else: + self._is_filtered = True @staticmethod def _detect_available_configs(): From 43258be69cb71606888309aaae19e35065d61fd0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 11:41:43 +0200 Subject: [PATCH 099/168] fixes for PCAN --- can/interfaces/pcan/pcan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index f388b6953..e44efff80 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -186,15 +186,15 @@ def _recv_internal(self, timeout): result = None val = WaitForSingleObject(self._recv_event, timeout_ms) if val != WAIT_OBJECT_0: - return None + return None, False elif timeout is not None and timeout_clock() >= end_time: - return None + return None, False else: result = None time.sleep(0.001) elif result[0] & (PCAN_ERROR_BUSLIGHT | PCAN_ERROR_BUSHEAVY): log.warning(self._get_formatted_error(result[0])) - return None + return None, False elif result[0] != PCAN_ERROR_OK: raise PcanError(self._get_formatted_error(result[0])) From 2a71dfed1020e83fa740d777d60e47921e4a69c5 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 11:41:53 +0200 Subject: [PATCH 100/168] new methods for usb2can --- can/interfaces/usb2can/__init__.py | 6 +++-- can/interfaces/usb2can/usb2canInterface.py | 28 +++++++++------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/can/interfaces/usb2can/__init__.py b/can/interfaces/usb2can/__init__.py index 6cf4660f0..8262dc47b 100644 --- a/can/interfaces/usb2can/__init__.py +++ b/can/interfaces/usb2can/__init__.py @@ -4,5 +4,7 @@ """ """ -from can.interfaces.usb2can.usb2canInterface import Usb2canBus -from can.interfaces.usb2can.usb2canabstractionlayer import Usb2CanAbstractionLayer +from __future__ import absolute_import + +from .usb2canInterface import Usb2canBus +from .usb2canabstractionlayer import Usb2CanAbstractionLayer diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 03f09ee8c..3a22ae5b4 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -5,10 +5,12 @@ This interface is for windows only, otherwise use socketCAN. """ +from __future__ import absolute_import, division + import logging from can import BusABC, Message -from can.interfaces.usb2can.usb2canabstractionlayer import * +from .usb2canabstractionlayer import * bootTimeEpoch = 0 try: @@ -42,7 +44,7 @@ def message_convert_tx(msg): for i in range(length): messagetx.data[i] = msg.data[i] - messagetx.flags = 80000000 + messagetx.flags = 0x80000000 if msg.is_error_frame: messagetx.flags |= IS_ERROR_FRAME @@ -77,8 +79,6 @@ def message_convert_rx(messagerx): class Usb2canBus(BusABC): """Interface to a USB2CAN Bus. - Note the USB2CAN interface doesn't implement set_filters, or flush_tx_buffer methods. - :param str channel: The device's serial number. If not provided, Windows Management Instrumentation will be used to identify the first such device. The *kwarg* `serial` may also be @@ -99,7 +99,6 @@ def __init__(self, channel, *args, **kwargs): # set flags on the connection if 'flags' in kwargs: enable_flags = kwargs["flags"] - else: enable_flags = 0x00000008 @@ -112,16 +111,10 @@ def __init__(self, channel, *args, **kwargs): from can.interfaces.usb2can.serial_selector import serial deviceID = serial() - # set baudrate in kb/s from bitrate - # (eg:500000 bitrate must be 500) - if 'bitrate' in kwargs: - br = kwargs["bitrate"] - - # max rate is 1000 kbps - baudrate = min(1000, int(br/1000)) - # set default value - else: - baudrate = 500 + # get baudrate in b/s from bitrate or use default + bitrate = kwargs.get("bitrate", d=500000) + # convert to kb/s (eg:500000 bitrate must be 500), max rate is 1000 kb/s + baudrate = min(1000, int(bitrate/1000)) connector = format_connection_string(deviceID, baudrate) @@ -134,7 +127,7 @@ def send(self, msg, timeout=None): else: self.can.send(self.handle, byref(tx)) - def recv(self, timeout=None): + def _recv_internal(self, timeout): messagerx = CanalMsg() @@ -154,8 +147,9 @@ def recv(self, timeout=None): log.error('Canal Error %s', status) rx = None - return rx + return rx, False def shutdown(self): """Shut down the device safely""" + # TODO handle error status = self.can.close(self.handle) From 79f2c4c4ab9756c88b242a81805d182b3b250711 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 11:51:09 +0200 Subject: [PATCH 101/168] added new mthods to slcan --- can/interfaces/ics_neovi/neovi_bus.py | 1 - can/interfaces/slcan.py | 53 +++++++++++++++------------ 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index a3d6e24be..bc7a133bf 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -283,7 +283,6 @@ def _ics_msg_to_message(self, ics_msg): def _recv_internal(self, timeout): if not self.rx_buffer: self._process_msg_queue(timeout) - try: ics_msg = self.rx_buffer.popleft() msg = self._ics_msg_to_message(ics_msg) diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index bd5990eac..61a02f8b8 100755 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -4,7 +4,10 @@ """ Interface for slcan compatible interfaces (win32/linux). -Note: Linux users can use slcand/socketcan as well. +.. note:: + + Linux users can use slcand or socketcan as well. + """ from __future__ import absolute_import @@ -41,18 +44,6 @@ class slcanBus(BusABC): _SLEEP_AFTER_SERIAL_OPEN = 2 # in seconds - def write(self, string): - if not string.endswith('\r'): - string += '\r' - self.serialPort.write(string.decode()) - self.serialPort.flush() - - def open(self): - self.write('O') - - def close(self): - self.write('C') - def __init__(self, channel, ttyBaudrate=115200, timeout=1, bitrate=None, **kwargs): """ :param string channel: @@ -61,7 +52,7 @@ def __init__(self, channel, ttyBaudrate=115200, timeout=1, bitrate=None, **kwarg :param int ttyBaudrate: baudrate of underlying serial or usb device :param int bitrate: - Bitrate in bits/s + Bitrate in bit/s :param float poll_interval: Poll interval in seconds when reading messages :param float timeout: @@ -89,9 +80,22 @@ def __init__(self, channel, ttyBaudrate=115200, timeout=1, bitrate=None, **kwarg self.open() - super(slcanBus, self).__init__(channel, **kwargs) + super(slcanBus, self).__init__(channel, ttyBaudrate=115200, timeout=1, + bitrate=None, **kwargs) + + def write(self, string): + if not string.endswith('\r'): + string += '\r' + self.serialPort.write(string.decode()) + self.serialPort.flush() + + def open(self): + self.write('O') + + def close(self): + self.write('C') - def recv(self, timeout=None): + def _recv_internal(self, timeout): if timeout is not None: self.serialPortOrig.timeout = timeout @@ -101,7 +105,7 @@ def recv(self, timeout=None): frame = [] readStr = self.serialPort.readline() if not readStr: - return None + return None, False else: if readStr[0] == 'T': # extended frame @@ -127,14 +131,15 @@ def recv(self, timeout=None): remote = True if canId is not None: - return Message(arbitration_id=canId, - extended_id=extended, - timestamp=time.time(), # Better than nothing... - is_remote_frame=remote, - dlc=dlc, - data=frame) + msg = Message(arbitration_id=canId, + extended_id=extended, + timestamp=time.time(), # Better than nothing... + is_remote_frame=remote, + dlc=dlc, + data=frame) + return msg, False else: - return None + return None, False def send(self, msg, timeout=None): if msg.is_remote_frame: From dec258e4405011cc6febd76e570a71c0b055f189 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 12:21:04 +0200 Subject: [PATCH 102/168] small fixes --- can/interfaces/kvaser/canlib.py | 2 +- can/interfaces/pcan/pcan.py | 10 +++++----- can/interfaces/virtual.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 4b6737f2d..070038997 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -296,7 +296,7 @@ def __init__(self, channel, can_filters=None, **config): The Channel id to create this bus with. :param list can_filters: - See :meth:`can.BusABC.__init__`. + See :meth:`can.BusABC.set_filters`. Backend Configuration diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index e44efff80..dbc426893 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -12,8 +12,7 @@ import time import can -from can import CanError, Message -from can.bus import BusABC +from can import CanError, Message, BusABC from .PCANBasic import * boottimeEpoch = 0 @@ -72,14 +71,14 @@ def __init__(self, channel, *args, **kwargs): """A PCAN USB interface to CAN. On top of the usual :class:`~can.Bus` methods provided, - the PCAN interface includes the `flash()` and `status()` methods. + the PCAN interface includes the :meth:`~can.interface.pcan.PcanBus.flash()` + and :meth:`~can.interface.pcan.PcanBus.status()` methods. :param str channel: The can interface name. An example would be 'PCAN_USBBUS1' :param int bitrate: - Bitrate of channel in bit/s. - Default is 500 kbit/s + Bitrate of channel in bit/s. Default is 500 kbit/s. """ if not channel: @@ -121,6 +120,7 @@ def _get_formatted_error(self, error): """ def bits(n): + """TODO: document""" while n: b = n & (~n+1) yield b diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 9811e8912..46e393f54 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -42,8 +42,8 @@ class VirtualBus(BusABC): """ def __init__(self, channel=None, receive_own_messages=False, **config): - config.update({'receive_own_messages': receive_own_messages}) - super(VirtualBus, self).__init__(channel=channel, **config) + super(VirtualBus, self).__init__(channel=channel, + receive_own_messages=receive_own_messages, **config) # the channel identifier may be an arbitrary object self.channel_id = channel From d61c744bac7594798693c27779557d2f92f1c2b2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 12:21:16 +0200 Subject: [PATCH 103/168] new methods for nican backend --- can/interfaces/nican.py | 46 +++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index 7d3a59fb3..a35aacb02 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -7,6 +7,12 @@ Implementation references: * http://www.ni.com/pdf/manuals/370289c.pdf * https://github.com/buendiya/NicanPython + +TODO: We could implement this interface such that setting other filters + could work when the initial filters were set to zero using the + software fallback. Or could the software filters even be changed + after the connection was opened? We need to document that bahaviour! + """ import ctypes @@ -113,6 +119,14 @@ def get_error_message(status_code): class NicanBus(BusABC): """ The CAN Bus implemented for the NI-CAN interface. + + .. warning:: + + This interface does implement efficient filtering of messages, but + the filters have to be set in :meth:`~can.interfaces.nican.NicanBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.nican.NicanBus.set_filters` + does not work. + """ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, @@ -125,9 +139,7 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, Bitrate in bits/s :param list can_filters: - A list of dictionaries each containing a "can_id" and a "can_mask". - - >>> [{"can_id": 0x11, "can_mask": 0x21}] + See :meth:`can.BusABC.set_filters`. :param bool log_errors: If True, communication errors will appear as CAN messages with @@ -136,6 +148,7 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, :raises can.interfaces.nican.NicanError: If starting communication fails + """ if nican is None: raise ImportError("The NI-CAN driver could not be loaded. " @@ -188,17 +201,17 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, self.handle = ctypes.c_ulong() nican.ncOpenObject(channel, ctypes.byref(self.handle)) - def recv(self, timeout=None): + super(NicanBus, self).__init__(channel=channel, + can_filters=can_filters, bitrate=bitrate, + log_errors=log_errors, **kwargs) + + def _recv_internal(self, timeout): """ - Read a message from NI-CAN. + Read a message from a NI-CAN bus. :param float timeout: Max time to wait in seconds or None if infinite - :returns: - The CAN message or None if timeout - :rtype: can.Message - :raises can.interfaces.nican.NicanError: If reception fails """ @@ -213,7 +226,7 @@ def recv(self, timeout=None): self.handle, NC_ST_READ_AVAIL, timeout, ctypes.byref(state)) except NicanError as e: if e.error_code == TIMEOUT_ERROR_CODE: - return None + return None, True else: raise @@ -235,7 +248,7 @@ def recv(self, timeout=None): arbitration_id=arb_id, dlc=dlc, data=raw_msg.data[:dlc]) - return msg + return msg, True def send(self, msg, timeout=None): """ @@ -258,6 +271,7 @@ def send(self, msg, timeout=None): nican.ncWrite( self.handle, ctypes.sizeof(raw_msg), ctypes.byref(raw_msg)) + # TODO: # ncWaitForState can not be called here if the recv() method is called # from a different thread, which is a very common use case. # Maybe it is possible to use ncCreateNotification instead but seems a @@ -276,6 +290,16 @@ def shutdown(self): """Close object.""" nican.ncCloseObject(self.handle) + __set_filters_has_been_called = False + def set_filters(self, can_filers=None): + """Unsupported. See note on :class:`~can.interfaces.nican.NicanBus`. + """ + if self.__set_filters_has_been_called: + logger.warn("using filters is not supported like this, see note on NicanBus") + else: + # allow the constructor to call this without causing a warning + self.__set_filters_has_been_called = True + class NicanError(CanError): """Error from NI-CAN driver.""" From c7607df8fe6b16e9727bdf821ab2a24d6555418f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 12:26:20 +0200 Subject: [PATCH 104/168] better imports in IXXAT --- can/interfaces/ixxat/canlib.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index ed4471766..4e6f7876e 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -7,21 +7,19 @@ Copyright (C) 2016 Giuseppe Corbelli """ +from __future__ import absolute_import, division + import ctypes import functools import logging import sys -import time -from can import CanError, BusABC -from can import Message +from can import CanError, BusABC, Message from can.broadcastmanager import (LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC) from can.ctypesutil import CLibrary, HANDLE, PHANDLE, HRESULT as ctypes_HRESULT -from can.interfaces.ixxat import constants, structures - -from .constants import VCI_MAX_ERRSTRLEN +from . import constants, structures from .exceptions import * __all__ = ["VCITimeout", "VCIError", "VCIDeviceNotFoundError", "IXXATBus", "vciFormatError"] @@ -30,9 +28,9 @@ try: # since Python 3.3 - _timer_function = time.perf_counter + from time import perf_counter as _timer_function except AttributeError: - _timer_function = time.clock + from time import clock as _timer_function # Hack to have vciFormatError as a free function, see below vciFormatError = None @@ -86,9 +84,9 @@ def __vciFormatError(library_instance, function, HRESULT): :return: Formatted string """ - buf = ctypes.create_string_buffer(VCI_MAX_ERRSTRLEN) - ctypes.memset(buf, 0, VCI_MAX_ERRSTRLEN) - library_instance.vciFormatError(HRESULT, buf, VCI_MAX_ERRSTRLEN) + buf = ctypes.create_string_buffer(constants.VCI_MAX_ERRSTRLEN) + ctypes.memset(buf, 0, constants.VCI_MAX_ERRSTRLEN) + library_instance.vciFormatError(HRESULT, buf, constants.VCI_MAX_ERRSTRLEN) return "function {} failed ({})".format(function._name, buf.value.decode('utf-8', 'replace')) From ab4b3a0364d34f28a3ffdff72fa89885a26625a9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 12:33:22 +0200 Subject: [PATCH 105/168] new methods for IXXAT interface --- can/interfaces/ixxat/canlib.py | 41 ++++++++++++++++++++++++++-------- can/interfaces/nican.py | 1 + 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 4e6f7876e..933351fef 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -5,6 +5,13 @@ Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems Copyright (C) 2016 Giuseppe Corbelli + +TODO: We could implement this interface such that setting other filters + could work when the initial filters were set to zero using the + software fallback. Or could the software filters even be changed + after the connection was opened? We need to document that bahaviour! + See also the NICAN interface. + """ from __future__ import absolute_import, division @@ -229,6 +236,14 @@ def __check_status(result, function, arguments): class IXXATBus(BusABC): """The CAN Bus implemented for the IXXAT interface. + + .. warning:: + + This interface does implement efficient filtering of messages, but + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. + """ CHANNEL_BITRATES = { @@ -262,9 +277,7 @@ def __init__(self, channel, can_filters=None, **config): The Channel id to create this bus with. :param list can_filters: - A list of dictionaries each containing a "can_id" and a "can_mask". - - >>> [{"can_id": 0x11, "can_mask": 0x21}] + See :meth:`can.BusABC.set_filters`. :param int UniqueHardwareId: UniqueHardwareId to connect (optional, will use the first found if not supplied) @@ -342,7 +355,7 @@ def __init__(self, channel, can_filters=None, **config): self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor) # Setup filters before starting the channel - if can_filters is not None and len(can_filters): + if can_filters: log.info("The IXXAT VCI backend is filtering messages") # Disable every message coming in for extended in (0, 1): @@ -377,7 +390,7 @@ def __init__(self, channel, can_filters=None, **config): except (VCITimeout, VCIRxQueueEmptyError): break - super(IXXATBus, self).__init__() + super(IXXATBus, self).__init__(channel=channel, can_filters=None, **config) def _inWaiting(self): try: @@ -392,7 +405,7 @@ def flush_tx_buffer(self): # TODO #64: no timeout? _canlib.canChannelWaitTxEvent(self._channel_handle, constants.INFINITE) - def recv(self, timeout=None): + def _recv_internal(self, timeout): """ Read a message from IXXAT device. """ # TODO: handling CAN error messages? @@ -403,7 +416,7 @@ def recv(self, timeout=None): try: _canlib.canChannelPeekMessage(self._channel_handle, ctypes.byref(self._message)) except (VCITimeout, VCIRxQueueEmptyError): - return None + return None, True else: if self._message.uMsgInfo.Bits.type == constants.CAN_MSGTYPE_DATA: data_received = True @@ -447,7 +460,7 @@ def recv(self, timeout=None): if not data_received: # Timed out / can message type is not DATA - return None + return None, True # The _message.dwTime is a 32bit tick value and will overrun, # so expect to see the value restarting from 0 @@ -462,7 +475,7 @@ def recv(self, timeout=None): ) log.debug('Recv()ed message %s', rx_msg) - return rx_msg + return rx_msg, True def send(self, msg, timeout=None): log.debug("Sending message: %s", msg) @@ -505,6 +518,16 @@ def shutdown(self): _canlib.canControlClose(self._control_handle) _canlib.vciDeviceClose(self._device_handle) + __set_filters_has_been_called = False + def set_filters(self, can_filers=None): + """Unsupported. See note on :class:`~can.interfaces.ixxat.IXXATBus`. + """ + if self.__set_filters_has_been_called: + log.warn("using filters is not supported like this, see note on IXXATBus") + else: + # allow the constructor to call this without causing a warning + self.__set_filters_has_been_called = True + class CyclicSendTask(LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC): diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index a35aacb02..a2364767d 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -12,6 +12,7 @@ could work when the initial filters were set to zero using the software fallback. Or could the software filters even be changed after the connection was opened? We need to document that bahaviour! + See also the IXXAT interface. """ From 0b6d3cae501d4e542fbc3b7ae2c57d2016abc42d Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 12:37:47 +0200 Subject: [PATCH 106/168] new methods for iscan --- can/interfaces/iscan.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/can/interfaces/iscan.py b/can/interfaces/iscan.py index 8bfb89b87..7f127b241 100644 --- a/can/interfaces/iscan.py +++ b/can/interfaces/iscan.py @@ -5,6 +5,8 @@ Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH. """ +from __future__ import absolute_import, division + import ctypes import time import logging @@ -76,16 +78,21 @@ def __init__(self, channel, bitrate=500000, poll_interval=0.01, **kwargs): """ if iscan is None: raise ImportError("Could not load isCAN driver") + self.channel = ctypes.c_ubyte(int(channel)) self.channel_info = "IS-CAN: %s" % channel + if bitrate not in self.BAUDRATES: valid_bitrates = ", ".join(str(bitrate) for bitrate in self.BAUDRATES) raise ValueError("Invalid bitrate, choose one of " + valid_bitrates) + self.poll_interval = poll_interval iscan.isCAN_DeviceInitEx(self.channel, self.BAUDRATES[bitrate]) - super(IscanBus, self).__init__(channel, **kwargs) - def recv(self, timeout=None): + super(IscanBus, self).__init__(channel=channel, bitrate=bitrate, + poll_interval=poll_interval, **kwargs) + + def _recv_internal(self, timeout): raw_msg = MessageExStruct() end_time = time.time() + timeout if timeout is not None else None while True: @@ -97,19 +104,21 @@ def recv(self, timeout=None): raise if end_time is not None and time.time() > end_time: # No message within timeout - return None + return None, False # Sleep a short time to avoid hammering time.sleep(self.poll_interval) else: # A message was received break - return Message(arbitration_id=raw_msg.message_id, - extended_id=bool(raw_msg.is_extended), - timestamp=time.time(), # Better than nothing... - is_remote_frame=bool(raw_msg.remote_req), - dlc=raw_msg.data_len, - data=raw_msg.data[:raw_msg.data_len], - channel=self.channel.value) + + msg = Message(arbitration_id=raw_msg.message_id, + extended_id=bool(raw_msg.is_extended), + timestamp=time.time(), # Better than nothing... + is_remote_frame=bool(raw_msg.remote_req), + dlc=raw_msg.data_len, + data=raw_msg.data[:raw_msg.data_len], + channel=self.channel.value) + return msg, False def send(self, msg, timeout=None): raw_msg = MessageExStruct(msg.arbitration_id, @@ -124,6 +133,7 @@ def shutdown(self): class IscanError(CanError): + # TODO: document ERROR_CODES = { 1: "No access to device", From 0bb21e6e034ed9b2ee1f7f95e1420822073a93ae Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 16 May 2018 12:51:37 +0200 Subject: [PATCH 107/168] bugfixes, simplifications, optimizations & new methods for serial can --- can/interfaces/serial/serial_can.py | 91 ++++++++++++++++------------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index b7c2f7c0e..0184d2abc 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -8,11 +8,12 @@ recording CAN traces. """ +from __future__ import absolute_import, division + import logging import struct -from can.bus import BusABC -from can.message import Message +from can import BusABC, Message logger = logging.getLogger('can.serial') @@ -27,32 +28,35 @@ class SerialBus(BusABC): """ Enable basic can communication over a serial device. + + .. note:: See :meth:`can.interfaces.serial.SerialBus._recv_internal` + for some special semantics. + """ - def __init__(self, channel, *args, **kwargs): + def __init__(self, channel, baudrate=115200, timeout=0.1, *args, **kwargs): """ :param str channel: The serial device to open. For example "/dev/ttyS1" or "/dev/ttyUSB0" on Linux or "COM1" on Windows systems. + :param int baudrate: Baud rate of the serial device in bit/s (default 115200). - .. note:: Some serial port implementations don't care about the baud - rate. + .. warning:: + Some serial port implementations don't care about the baudrate. :param float timeout: Timeout for the serial device in seconds (default 0.1). - """ - if channel == '': + """ + if not channel: raise ValueError("Must specify a serial port.") - else: - self.channel_info = "Serial interface: " + channel - baudrate = kwargs.get('baudrate', 115200) - timeout = kwargs.get('timeout', 0.1) - self.ser = serial.Serial(channel, baudrate=baudrate, - timeout=timeout) - super(SerialBus, self).__init__(*args, **kwargs) + + self.channel_info = "Serial interface: " + channel + self.ser = serial.Serial(channel, baudrate=baudrate, timeout=timeout) + + super(SerialBus, self).__init__(channel=channel, *args, **kwargs) def shutdown(self): """ @@ -67,24 +71,24 @@ def send(self, msg, timeout=None): :param can.Message msg: Message to send. - .. note:: Flags like extended_id, is_remote_frame and is_error_frame - will be ignored. + .. note:: Flags like ``extended_id``, ``is_remote_frame`` and + ``is_error_frame`` will be ignored. - .. note:: If the timestamp a float value it will be convert to an - integer. + .. note:: If the timestamp is a float value it will be converted + to an integer. :param timeout: This parameter will be ignored. The timeout value of the channel is - used. - """ + used instead. + """ try: - timestamp = struct.pack(' Date: Wed, 16 May 2018 12:53:04 +0200 Subject: [PATCH 108/168] small fix for IXXAT import on Python 2 --- can/interfaces/ixxat/canlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 933351fef..a34a23407 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -36,7 +36,7 @@ try: # since Python 3.3 from time import perf_counter as _timer_function -except AttributeError: +except ImportError: from time import clock as _timer_function # Hack to have vciFormatError as a free function, see below From 64e8e660fab036c3b32bb1e3d26c36914faf2858 Mon Sep 17 00:00:00 2001 From: Mathias Giacomuzzi Date: Tue, 22 May 2018 12:29:11 +0200 Subject: [PATCH 109/168] Add a possibility to use LISTEN ONLY for the CAN Hardware. (#235) * add interface example definition for beginners * add a state for the bus to handle active and passive mode, currently only implemented for pcan * decode the right way * use ACTIVE state as default for getter, raise NotImplementedError on base setter --- can/bus.py | 21 +++++++++++++++++++++ can/interfaces/pcan/pcan.py | 32 +++++++++++++++++++++++++++++--- can/logger.py | 12 ++++++++++++ examples/receive_all.py | 26 ++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 examples/receive_all.py diff --git a/can/bus.py b/can/bus.py index 5bb370382..993afdf24 100644 --- a/can/bus.py +++ b/can/bus.py @@ -10,12 +10,16 @@ from abc import ABCMeta, abstractmethod import logging import threading +from collections import namedtuple from can.broadcastmanager import ThreadBasedCyclicSendTask logger = logging.getLogger(__name__) +BusState = namedtuple('BusState', 'ACTIVE, PASSIVE, ERROR') + + class BusABC(object): """CAN Bus Abstract Base Class @@ -151,6 +155,23 @@ def shutdown(self): """ self.flush_tx_buffer() + @property + def state(self): + """ + Return the current state of the hardware + :return: ACTIVE, PASSIVE or ERROR + :rtype: NamedTuple + """ + return BusState.ACTIVE + + @state.setter + def state(self, new_state): + """ + Set the new state of the hardware + :param new_state: BusState.ACTIVE, BusState.PASSIVE or BusState.ERROR + """ + raise NotImplementedError("Property is not implemented.") + @staticmethod def _detect_available_configs(): """Detect all configurations/channels that this interface could diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 4f705b302..abc352677 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -11,7 +11,7 @@ import can from can import CanError -from can.bus import BusABC +from can.bus import BusABC, BusState from can.message import Message from can.interfaces.pcan.PCANBasic import * @@ -67,7 +67,7 @@ class PcanBus(BusABC): - def __init__(self, channel, *args, **kwargs): + def __init__(self, channel, state=BusState.ACTIVE, *args, **kwargs): """A PCAN USB interface to CAN. On top of the usual :class:`~can.Bus` methods provided, @@ -76,10 +76,13 @@ def __init__(self, channel, *args, **kwargs): :param str channel: The can interface name. An example would be PCAN_USBBUS1 + :param BusState state: + BusState of the channel. + Default is ACTIVE + :param int bitrate: Bitrate of channel in bit/s. Default is 500 Kbs - """ if channel is None or channel == '': raise ArgumentError("Must specify a PCAN channel") @@ -96,6 +99,11 @@ def __init__(self, channel, *args, **kwargs): self.m_objPCANBasic = PCANBasic() self.m_PcanHandle = globals()[channel] + if state is BusState.ACTIVE or BusState.PASSIVE: + self._state = state + else: + raise ArgumentError("BusState must be Active or Passive") + result = self.m_objPCANBasic.Initialize(self.m_PcanHandle, pcan_bitrate, hwtype, ioport, interrupt) if result != PCAN_ERROR_OK: @@ -262,6 +270,24 @@ def flash(self, flash): def shutdown(self): self.m_objPCANBasic.Uninitialize(self.m_PcanHandle) + @property + def state(self): + return self._state + + @state.setter + def state(self, new_state): + + self._state = new_state + + if new_state is BusState.ACTIVE: + self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_OFF) + + if new_state is BusState.PASSIVE: + # When this mode is set, the CAN controller does not take part on active events (eg. transmit CAN messages) + # but stays in a passive mode (CAN monitor), in which it can analyse the traffic on the CAN bus used by a + # PCAN channel. See also the Philips Data Sheet "SJA1000 Stand-alone CAN controller". + self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_LISTEN_ONLY, PCAN_PARAMETER_ON) + class PcanError(CanError): pass diff --git a/can/logger.py b/can/logger.py index bacc0b66f..75a52272f 100644 --- a/can/logger.py +++ b/can/logger.py @@ -24,6 +24,7 @@ import socket import can +from can.bus import BusState from can.io.logger import Logger @@ -57,6 +58,10 @@ def main(): parser.add_argument('-b', '--bitrate', type=int, help='''Bitrate to use for the CAN bus.''') + group = parser.add_mutually_exclusive_group(required=False) + group.add_argument('--active', action='store_true') + group.add_argument('--passive', action='store_true') + results = parser.parse_args() verbosity = results.verbosity @@ -83,6 +88,13 @@ def main(): if results.bitrate: config["bitrate"] = results.bitrate bus = can.interface.Bus(results.channel, **config) + + if results.active: + bus.state = BusState.ACTIVE + + if results.passive: + bus.state = BusState.PASSIVE + print('Connected to {}: {}'.format(bus.__class__.__name__, bus.channel_info)) print('Can Logger (Started on {})\n'.format(datetime.datetime.now())) logger = Logger(results.log_file) diff --git a/examples/receive_all.py b/examples/receive_all.py new file mode 100644 index 000000000..6801f481d --- /dev/null +++ b/examples/receive_all.py @@ -0,0 +1,26 @@ +from __future__ import print_function + +import can +from can.bus import BusState + + +def receive_all(): + + bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) + #bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + #bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) + + bus.state = BusState.ACTIVE + #bus.state = BusState.PASSIVE + + try: + while True: + msg = bus.recv(1) + if msg is not None: + print(msg) + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + receive_all() From 188a4391eed1b70b427eee0963ede63fc5f7d388 Mon Sep 17 00:00:00 2001 From: AntonioCohimbra <38559979+AntonioCohimbra@users.noreply.github.com> Date: Fri, 18 May 2018 13:24:42 +0200 Subject: [PATCH 110/168] add initial version of CAN FD support for Vector --- can/interfaces/vector/canlib.py | 188 ++++++++++++++++++++++++-------- 1 file changed, 145 insertions(+), 43 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 44b72be62..229a38ce4 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -49,7 +49,7 @@ class VectorBus(BusABC): def __init__(self, channel, can_filters=None, poll_interval=0.01, receive_own_messages=False, - bitrate=None, rx_queue_size=256, app_name="CANalyzer", **config): + bitrate=None, rx_queue_size=256, app_name="CANalyzer", fd=False, data_bitrate=None, **config): """ :param list channel: The channel indexes to create this bus with. @@ -62,6 +62,11 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, Number of messages in receive queue. :param str app_name: Name of application in Hardware Config. + :param bool fd: + If CAN-FD frames should be supported. + :param int data_bitrate: + Which bitrate to use for data phase in CAN FD. + Defaults to arbitration bitrate. """ if vxlapi is None: raise ImportError("The Vector API has not been loaded") @@ -80,6 +85,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, vxlapi.xlOpenDriver() self.port_handle = vxlapi.XLportHandle(vxlapi.XL_INVALID_PORTHANDLE) self.mask = 0 + self.fd = fd # Get channels masks for channel in self.channels: hw_type = ctypes.c_uint(0) @@ -96,18 +102,46 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, permission_mask = vxlapi.XLaccess() # Set mask to request channel init permission if needed - if bitrate: + if bitrate or fd: permission_mask.value = self.mask - vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, - permission_mask, rx_queue_size, - vxlapi.XL_INTERFACE_VERSION, vxlapi.XL_BUS_TYPE_CAN) + if fd: + vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, + permission_mask, 16000, + vxlapi.XL_INTERFACE_VERSION_V4, vxlapi.XL_BUS_TYPE_CAN) + else: + vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, + permission_mask, rx_queue_size, + vxlapi.XL_INTERFACE_VERSION, vxlapi.XL_BUS_TYPE_CAN) LOG.debug( 'Open Port: PortHandle: %d, PermissionMask: 0x%X', self.port_handle.value, permission_mask.value) - if bitrate: - if permission_mask.value != self.mask: - LOG.info('Can not set bitrate since no init access') - vxlapi.xlCanSetChannelBitrate(self.port_handle, permission_mask, bitrate) + + if permission_mask.value == self.mask: + if fd: + self.canFdConf = vxlapi.XLcanFdConf() + if bitrate: + self.canFdConf.arbitrationBitRate = ctypes.c_uint(bitrate) + else: + self.canFdConf.arbitrationBitRate = ctypes.c_uint(500000) + self.canFdConf.sjwAbr = ctypes.c_uint(2) + self.canFdConf.tseg1Abr = ctypes.c_uint(6) + self.canFdConf.tseg2Abr = ctypes.c_uint(3) + if data_bitrate: + self.canFdConf.dataBitRate = ctypes.c_uint(data_bitrate) + else: + self.canFdConf.dataBitRate = self.canFdConf.arbitrationBitRate + self.canFdConf.sjwDbr = ctypes.c_uint(2) + self.canFdConf.tseg1Dbr = ctypes.c_uint(6) + self.canFdConf.tseg2Dbr = ctypes.c_uint(3) + + vxlapi.xlCanFdSetConfiguration(self.port_handle, self.mask, self.canFdConf) + LOG.info('SetFdConfig.: ABaudr.=%u, DBaudr.=%u', self.canFdConf.arbitrationBitRate, self.canFdConf.dataBitRate) + else: + if bitrate: + vxlapi.xlCanSetChannelBitrate(self.port_handle, permission_mask, bitrate) + LOG.info('SetChannelBitrate: baudr.=%u',bitrate) + else: + LOG.info('No init access!') # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 @@ -154,31 +188,64 @@ def set_filters(self, can_filters=None): def recv(self, timeout=None): end_time = time.time() + timeout if timeout is not None else None - event = vxlapi.XLevent(0) - event_count = ctypes.c_uint() + + if self.fd: + event = vxlapi.XLcanRxEvent() + else: + event = vxlapi.XLevent() + event_count = ctypes.c_uint() + while True: - event_count.value = 1 - try: - vxlapi.xlReceive(self.port_handle, event_count, event) - except VectorError as exc: - if exc.error_code != vxlapi.XL_ERR_QUEUE_IS_EMPTY: - raise + if self.fd: + try: + vxlapi.xlCanReceive(self.port_handle, event) + except VectorError as exc: + if exc.error_code != vxlapi.XL_ERR_QUEUE_IS_EMPTY: + raise + else: + if event.tag == vxlapi.XL_CAN_EV_TAG_RX_OK or event.tag == vxlapi.XL_CAN_EV_TAG_TX_OK: + msg_id = event.tagData.canRxOkMsg.canId + dlc = self.map_dlc(event.tagData.canRxOkMsg.dlc) + flags = event.tagData.canRxOkMsg.msgFlags + timestamp = event.timeStamp * 1e-9 + msg = Message( + timestamp=timestamp + self._time_offset, + arbitration_id=msg_id & 0x1FFFFFFF, + extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), + is_remote_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_RTR), + is_error_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EF), + is_fd=True, + error_state_indicator=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_ESI), + #extended data length (XL_CAN_RXMSG_FLAG_EDL) not in msg class + bitrate_switch=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_BRS), + dlc=dlc, + data=event.tagData.canRxOkMsg.data[:dlc], + channel=event.chanIndex) + return msg else: - if event.tag == vxlapi.XL_RECEIVE_MSG: - msg_id = event.tagData.msg.id - dlc = event.tagData.msg.dlc - flags = event.tagData.msg.flags - timestamp = event.timeStamp * 1e-9 - msg = Message( - timestamp=timestamp + self._time_offset, - arbitration_id=msg_id & 0x1FFFFFFF, - extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), - is_remote_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME), - is_error_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_ERROR_FRAME), - dlc=dlc, - data=event.tagData.msg.data[:dlc], - channel=event.chanIndex) - return msg + event_count.value = 1 + try: + vxlapi.xlReceive(self.port_handle, event_count, event) + except VectorError as exc: + if exc.error_code != vxlapi.XL_ERR_QUEUE_IS_EMPTY: + raise + else: + if event.tag == vxlapi.XL_RECEIVE_MSG: + msg_id = event.tagData.msg.id + dlc = event.tagData.msg.dlc + flags = event.tagData.msg.flags + timestamp = event.timeStamp * 1e-9 + msg = Message( + timestamp=timestamp + self._time_offset, + arbitration_id=msg_id & 0x1FFFFFFF, + extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), + is_remote_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME), + is_error_frame=bool(flags & vxlapi.XL_CAN_MSG_FLAG_ERROR_FRAME), + is_fd=False, + dlc=dlc, + data=event.tagData.msg.data[:dlc], + channel=event.chanIndex) + return msg if end_time is not None and time.time() > end_time: return None @@ -196,22 +263,52 @@ def recv(self, timeout=None): time.sleep(self.poll_interval) def send(self, msg, timeout=None): - message_count = ctypes.c_uint(1) msg_id = msg.arbitration_id + if msg.id_type: msg_id |= vxlapi.XL_CAN_EXT_MSG_ID + flags = 0 - if msg.is_remote_frame: - flags |= vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME - xl_event = vxlapi.XLevent() - xl_event.tag = vxlapi.XL_TRANSMIT_MSG - xl_event.tagData.msg.id = msg_id - xl_event.tagData.msg.dlc = msg.dlc - xl_event.tagData.msg.flags = flags - for idx, value in enumerate(msg.data): - xl_event.tagData.msg.data[idx] = value - vxlapi.xlCanTransmit(self.port_handle, self.mask, message_count, xl_event) + if self.fd: + if msg.dlc > vxlapi.MAX_MSG_LEN: #extended data length (XL_CAN_RXMSG_FLAG_EDL) not in msg class + flags |= vxlapi.XL_CAN_TXMSG_FLAG_EDL + if msg.bitrate_switch: + flags |= vxlapi.XL_CAN_TXMSG_FLAG_BRS + if msg.is_remote_frame: + flags |= vxlapi.XL_CAN_TXMSG_FLAG_RTR + + message_count = 1 + MsgCntSent = ctypes.c_uint(1) + + XLcanTxEvent = vxlapi.XLcanTxEvent() + XLcanTxEvent.tag = vxlapi.XL_CAN_EV_TAG_TX_MSG + XLcanTxEvent.transId = 0xffff + + XLcanTxEvent.tagData.canMsg.canId = msg_id + XLcanTxEvent.tagData.canMsg.msgFlags = flags + XLcanTxEvent.tagData.canMsg.dlc = msg.dlc + for idx, value in enumerate(msg.data): + XLcanTxEvent.tagData.canMsg.data[idx] = value + vxlapi.xlCanTransmitEx(self.port_handle, self.mask, message_count, MsgCntSent, XLcanTxEvent) + + else: + if msg.is_remote_frame: + flags |= vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME + + message_count = ctypes.c_uint(1) + + xl_event = vxlapi.XLevent() + xl_event.tag = vxlapi.XL_TRANSMIT_MSG + + xl_event.tagData.msg.id = msg_id + xl_event.tagData.msg.dlc = msg.dlc + xl_event.tagData.msg.flags = flags + for idx, value in enumerate(msg.data): + xl_event.tagData.msg.data[idx] = value + vxlapi.xlCanTransmit(self.port_handle, self.mask, message_count, xl_event) + + def flush_tx_buffer(self): vxlapi.xlCanFlushTransmitQueue(self.port_handle, self.mask) @@ -224,3 +321,8 @@ def reset(self): vxlapi.xlDeactivateChannel(self.port_handle, self.mask) vxlapi.xlActivateChannel(self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0) + + def map_dlc(self,dlc_in): + can_fd_dlc = [0,1,2,3,4,5,6,7,8,12,16,20,24,32,48,64] + return can_fd_dlc[dlc_in] + From f588b1bccc04e5022e19fa2624b163e98f789bbf Mon Sep 17 00:00:00 2001 From: AntonioCohimbra <38559979+AntonioCohimbra@users.noreply.github.com> Date: Fri, 18 May 2018 13:25:36 +0200 Subject: [PATCH 111/168] add initial version of CAN FD support for Vector --- can/interfaces/vector/vxlapi.py | 98 ++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index 6612351b3..187dd9dd5 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -29,13 +29,25 @@ XL_ERR_QUEUE_IS_EMPTY = 10 XL_RECEIVE_MSG = 1 +XL_CAN_EV_TAG_RX_OK = 1024 +XL_CAN_EV_TAG_TX_OK = 1028 XL_TRANSMIT_MSG = 10 +XL_CAN_EV_TAG_TX_MSG = 1088 XL_CAN_EXT_MSG_ID = 0x80000000 XL_CAN_MSG_FLAG_ERROR_FRAME = 0x01 XL_CAN_MSG_FLAG_REMOTE_FRAME = 0x10 XL_CAN_MSG_FLAG_TX_COMPLETED = 0x40 +XL_CAN_TXMSG_FLAG_EDL = 0x0001 +XL_CAN_TXMSG_FLAG_BRS = 0x0002 +XL_CAN_TXMSG_FLAG_RTR = 0x0010 +XL_CAN_RXMSG_FLAG_EDL = 0x0001 +XL_CAN_RXMSG_FLAG_BRS = 0x0002 +XL_CAN_RXMSG_FLAG_ESI = 0x0004 +XL_CAN_RXMSG_FLAG_RTR = 0x0010 +XL_CAN_RXMSG_FLAG_EF = 0x0200 + XL_CAN_STD = 1 XL_CAN_EXT = 2 @@ -45,8 +57,13 @@ MAX_MSG_LEN = 8 +XL_CAN_MAX_DATA_LEN = 64 + # current version XL_INTERFACE_VERSION = 3 +XL_INTERFACE_VERSION_V4 = 4 + +XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 0x80000000 # structure for XL_RECEIVE_MSG, XL_TRANSMIT_MSG class s_xl_can_msg(ctypes.Structure): @@ -54,11 +71,50 @@ class s_xl_can_msg(ctypes.Structure): ('dlc', ctypes.c_ushort), ('res1', XLuint64), ('data', ctypes.c_ubyte * MAX_MSG_LEN), ('res2', XLuint64)] + + +class s_xl_can_ev_error(ctypes.Structure): + _fields_ = [('errorCode', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 95)] + +class s_xl_can_ev_chip_state(ctypes.Structure): + _fields_ = [('busStatus', ctypes.c_ubyte), ('txErrorCounter', ctypes.c_ubyte), + ('rxErrorCounter', ctypes.c_ubyte),('reserved', ctypes.c_ubyte), + ('reserved0', ctypes.c_uint)] + +class s_xl_can_ev_sync_pulse(ctypes.Structure): + _fields_ = [('triggerSource', ctypes.c_uint), ('reserved', ctypes.c_uint), + ('time', XLuint64)] + # BASIC bus message structure class s_xl_tag_data(ctypes.Union): _fields_ = [('msg', s_xl_can_msg)] - +# CAN FD messages +class s_xl_can_ev_rx_msg(ctypes.Structure): + _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), + ('crc', ctypes.c_uint), ('reserved1', ctypes.c_ubyte * 12), + ('totalBitCnt', ctypes.c_ushort), ('dlc', ctypes.c_ubyte), + ('reserved', ctypes.c_ubyte * 5), ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] + +class s_xl_can_ev_tx_request(ctypes.Structure): + _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), + ('dlc', ctypes.c_ubyte),('txAttemptConf', ctypes.c_ubyte), + ('reserved', ctypes.c_ushort), ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] + +class s_xl_can_tx_msg(ctypes.Structure): + _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), + ('dlc', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 7), + ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] + +class s_rxTagData(ctypes.Union): + _fields_ = [('canRxOkMsg', s_xl_can_ev_rx_msg), ('canTxOkMsg', s_xl_can_ev_rx_msg), + ('canTxRequest', s_xl_can_ev_tx_request),('canError', s_xl_can_ev_error), + ('canChipState', s_xl_can_ev_chip_state),('canSyncPulse', s_xl_can_ev_sync_pulse)] + +class s_txTagData(ctypes.Union): + _fields_ = [('canMsg', s_xl_can_tx_msg)] + +# BASIC events XLeventTag = ctypes.c_ubyte class XLevent(ctypes.Structure): @@ -67,6 +123,27 @@ class XLevent(ctypes.Structure): ('flags', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte), ('timeStamp', XLuint64), ('tagData', s_xl_tag_data)] +# CAN FD events +class XLcanRxEvent(ctypes.Structure): + _fields_ = [('size',ctypes.c_int),('tag', ctypes.c_ushort), + ('chanIndex', ctypes.c_ubyte),('reserved', ctypes.c_ubyte), + ('userHandle', ctypes.c_int),('flagsChip', ctypes.c_ushort), + ('reserved0', ctypes.c_ushort),('reserved1', XLuint64), + ('timeStamp', XLuint64),('tagData', s_rxTagData)] + +class XLcanTxEvent(ctypes.Structure): + _fields_ = [('tag', ctypes.c_ushort), ('transId', ctypes.c_ushort), + ('chanIndex', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 3), + ('tagData', s_txTagData)] + +# CAN FD configuration structure +class XLcanFdConf(ctypes.Structure): + _fields_ = [('arbitrationBitRate', ctypes.c_uint), ('sjwAbr', ctypes.c_uint), + ('tseg1Abr', ctypes.c_uint), ('tseg2Abr', ctypes.c_uint), + ('dataBitRate', ctypes.c_uint), ('sjwDbr', ctypes.c_uint), + ('tseg1Dbr', ctypes.c_uint), ('tseg2Dbr', ctypes.c_uint), + ('reserved', ctypes.c_uint * 2)] + # driver status XLstatus = ctypes.c_short @@ -150,6 +227,11 @@ def check_status(result, function, arguments): xlDeactivateChannel.restype = XLstatus xlDeactivateChannel.errcheck = check_status +xlCanFdSetConfiguration = _xlapi_dll.xlCanFdSetConfiguration +xlCanFdSetConfiguration.argtypes = [XLportHandle, XLaccess, ctypes.POINTER(XLcanFdConf)] +xlCanFdSetConfiguration.restype = XLstatus +xlCanFdSetConfiguration.errcheck = check_status + xlReceive = _xlapi_dll.xlReceive xlReceive.argtypes = [ XLportHandle, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(XLevent) @@ -157,6 +239,13 @@ def check_status(result, function, arguments): xlReceive.restype = XLstatus xlReceive.errcheck = check_status +xlCanReceive = _xlapi_dll.xlCanReceive +xlCanReceive.argtypes = [ + XLportHandle, ctypes.POINTER(XLcanRxEvent) +] +xlCanReceive.restype = XLstatus +xlCanReceive.errcheck = check_status + xlGetErrorString = _xlapi_dll.xlGetErrorString xlGetErrorString.argtypes = [XLstatus] xlGetErrorString.restype = ctypes.c_char_p @@ -173,6 +262,13 @@ def check_status(result, function, arguments): xlCanTransmit.restype = XLstatus xlCanTransmit.errcheck = check_status +xlCanTransmitEx = _xlapi_dll.xlCanTransmitEx +xlCanTransmitEx.argtypes = [ + XLportHandle, XLaccess, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(XLcanTxEvent) +] +xlCanTransmitEx.restype = XLstatus +xlCanTransmitEx.errcheck = check_status + xlCanFlushTransmitQueue = _xlapi_dll.xlCanFlushTransmitQueue xlCanFlushTransmitQueue.argtypes = [XLportHandle, XLaccess] xlCanFlushTransmitQueue.restype = XLstatus From de59ce517fa41f8dd03b4a43e30b923a7c079db7 Mon Sep 17 00:00:00 2001 From: Tobias Oeser Date: Wed, 23 May 2018 11:40:00 +0200 Subject: [PATCH 112/168] add review changes --- can/interfaces/vector/canlib.py | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 229a38ce4..9627c8fd1 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -30,6 +30,7 @@ # Import Modules # ============== from can import BusABC, Message +from can.util import len2dlc, dlc2len from .exceptions import VectorError # Define Module Logger @@ -49,7 +50,7 @@ class VectorBus(BusABC): def __init__(self, channel, can_filters=None, poll_interval=0.01, receive_own_messages=False, - bitrate=None, rx_queue_size=256, app_name="CANalyzer", fd=False, data_bitrate=None, **config): + bitrate=None, rx_queue_size=2**14, app_name="CANalyzer", fd=False, data_bitrate=None, sjwAbr=2, tseg1Abr=6, tseg2Abr=3, sjwDbr=2, tseg1Dbr=6, tseg2Dbr=3, **config): """ :param list channel: The channel indexes to create this bus with. @@ -59,7 +60,9 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, :param int bitrate: Bitrate in bits/s. :param int rx_queue_size: - Number of messages in receive queue. + Number of messages in receive queue (power of 2). + CAN: range 16…32768 + CAN-FD: range 8192…524288 :param str app_name: Name of application in Hardware Config. :param bool fd: @@ -106,7 +109,7 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, permission_mask.value = self.mask if fd: vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, - permission_mask, 16000, + permission_mask, rx_queue_size, vxlapi.XL_INTERFACE_VERSION_V4, vxlapi.XL_BUS_TYPE_CAN) else: vxlapi.xlOpenPort(self.port_handle, self._app_name, self.mask, @@ -123,19 +126,21 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.canFdConf.arbitrationBitRate = ctypes.c_uint(bitrate) else: self.canFdConf.arbitrationBitRate = ctypes.c_uint(500000) - self.canFdConf.sjwAbr = ctypes.c_uint(2) - self.canFdConf.tseg1Abr = ctypes.c_uint(6) - self.canFdConf.tseg2Abr = ctypes.c_uint(3) + self.canFdConf.sjwAbr = ctypes.c_uint(sjwAbr) + self.canFdConf.tseg1Abr = ctypes.c_uint(tseg1Abr) + self.canFdConf.tseg2Abr = ctypes.c_uint(tseg2Abr) if data_bitrate: self.canFdConf.dataBitRate = ctypes.c_uint(data_bitrate) else: self.canFdConf.dataBitRate = self.canFdConf.arbitrationBitRate - self.canFdConf.sjwDbr = ctypes.c_uint(2) - self.canFdConf.tseg1Dbr = ctypes.c_uint(6) - self.canFdConf.tseg2Dbr = ctypes.c_uint(3) + self.canFdConf.sjwDbr = ctypes.c_uint(sjwDbr) + self.canFdConf.tseg1Dbr = ctypes.c_uint(tseg1Dbr) + self.canFdConf.tseg2Dbr = ctypes.c_uint(tseg2Dbr) vxlapi.xlCanFdSetConfiguration(self.port_handle, self.mask, self.canFdConf) LOG.info('SetFdConfig.: ABaudr.=%u, DBaudr.=%u', self.canFdConf.arbitrationBitRate, self.canFdConf.dataBitRate) + LOG.info('SetFdConfig.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u', self.canFdConf.sjwAbr, self.canFdConf.tseg1Abr, self.canFdConf.tseg2Abr) + LOG.info('SetFdConfig.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u', self.canFdConf.sjwDbr, self.canFdConf.tseg1Dbr, self.canFdConf.tseg2Dbr) else: if bitrate: vxlapi.xlCanSetChannelBitrate(self.port_handle, permission_mask, bitrate) @@ -205,7 +210,7 @@ def recv(self, timeout=None): else: if event.tag == vxlapi.XL_CAN_EV_TAG_RX_OK or event.tag == vxlapi.XL_CAN_EV_TAG_TX_OK: msg_id = event.tagData.canRxOkMsg.canId - dlc = self.map_dlc(event.tagData.canRxOkMsg.dlc) + dlc = dlc2len(event.tagData.canRxOkMsg.dlc) flags = event.tagData.canRxOkMsg.msgFlags timestamp = event.timeStamp * 1e-9 msg = Message( @@ -214,9 +219,8 @@ def recv(self, timeout=None): extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), is_remote_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_RTR), is_error_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EF), - is_fd=True, + is_fd=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EDL), error_state_indicator=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_ESI), - #extended data length (XL_CAN_RXMSG_FLAG_EDL) not in msg class bitrate_switch=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_BRS), dlc=dlc, data=event.tagData.canRxOkMsg.data[:dlc], @@ -271,7 +275,7 @@ def send(self, msg, timeout=None): flags = 0 if self.fd: - if msg.dlc > vxlapi.MAX_MSG_LEN: #extended data length (XL_CAN_RXMSG_FLAG_EDL) not in msg class + if msg.is_fd: flags |= vxlapi.XL_CAN_TXMSG_FLAG_EDL if msg.bitrate_switch: flags |= vxlapi.XL_CAN_TXMSG_FLAG_BRS @@ -287,7 +291,7 @@ def send(self, msg, timeout=None): XLcanTxEvent.tagData.canMsg.canId = msg_id XLcanTxEvent.tagData.canMsg.msgFlags = flags - XLcanTxEvent.tagData.canMsg.dlc = msg.dlc + XLcanTxEvent.tagData.canMsg.dlc = len2dlc(msg.dlc) for idx, value in enumerate(msg.data): XLcanTxEvent.tagData.canMsg.data[idx] = value vxlapi.xlCanTransmitEx(self.port_handle, self.mask, message_count, MsgCntSent, XLcanTxEvent) @@ -321,8 +325,4 @@ def reset(self): vxlapi.xlDeactivateChannel(self.port_handle, self.mask) vxlapi.xlActivateChannel(self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0) - - def map_dlc(self,dlc_in): - can_fd_dlc = [0,1,2,3,4,5,6,7,8,12,16,20,24,32,48,64] - return can_fd_dlc[dlc_in] - + From 86aabcdce56f9dace28ef157e7654ccd397eb974 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 00:05:36 +0200 Subject: [PATCH 113/168] various cleanups an removed some rotten code --- can/CAN.py | 4 ++-- can/__init__.py | 30 +++++++++++++++-------------- can/broadcastmanager.py | 15 ++++++++++----- can/interface.py | 35 ++++++---------------------------- can/interfaces/__init__.py | 30 +++++++++++++++++++++-------- can/io/__init__.py | 9 +++++++-- can/io/{log.py => canutils.py} | 6 ++++-- can/io/logger.py | 4 ++-- can/io/player.py | 13 +++++++------ can/player.py | 2 ++ doc/conf.py | 26 ++----------------------- 11 files changed, 80 insertions(+), 94 deletions(-) rename can/io/{log.py => canutils.py} (97%) diff --git a/can/CAN.py b/can/CAN.py index e1d0f496f..fde1cf3d6 100644 --- a/can/CAN.py +++ b/can/CAN.py @@ -7,8 +7,8 @@ however all functionality has been refactored out. This API is left intact for version 2.0 to 2.3 to aide with migration. -WARNING: -This module is deprecated an will get removed in version 2.4. +WARNING: +This module is deprecated an will get removed in version 2.4. Please use `import can` instead. """ diff --git a/can/__init__.py b/can/__init__.py index f784ae1b1..ce126ff95 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -2,7 +2,7 @@ # coding: utf-8 """ -can is an object-orient Controller Area Network interface module. +``can`` is an object-orient Controller Area Network (CAN) interface module. """ from __future__ import absolute_import @@ -15,25 +15,27 @@ rc = dict() - class CanError(IOError): + """ + Indicates an error with the CAN network. + """ pass -from can.listener import Listener, BufferedReader, RedirectReader +from .listener import Listener, BufferedReader, RedirectReader -from can.io import Logger, Printer, LogReader -from can.io import ASCWriter, ASCReader -from can.io import BLFReader, BLFWriter -from can.io import CanutilsLogReader, CanutilsLogWriter -from can.io import CSVWriter, CSVReader -from can.io import SqliteWriter, SqliteReader +from .io import Logger, Printer, LogReader, MessageSync +from .io import ASCWriter, ASCReader +from .io import BLFReader, BLFWriter +from .io import CanutilsLogReader, CanutilsLogWriter +from .io import CSVWriter, CSVReader +from .io import SqliteWriter, SqliteReader -from can.util import set_logging_level +from .util import set_logging_level -from can.message import Message -from can.bus import BusABC -from can.notifier import Notifier -from can.interfaces import VALID_INTERFACES +from .message import Message +from .bus import BusABC +from .notifier import Notifier +from .interfaces import VALID_INTERFACES from . import interface from .interface import Bus, detect_available_configs diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 689f2777d..e25a9312f 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -3,17 +3,20 @@ """ Exposes several methods for transmitting cyclic messages. + +The main entry point to these classes should be through +:meth:`can.BusABC.send_periodic`. """ -import can import abc import logging import sched import threading import time +import can + log = logging.getLogger('can.bcm') -log.debug("Loading base broadcast manager functionality") class CyclicTask(object): @@ -39,6 +42,7 @@ def __init__(self, message, period): """ self.message = message self.can_id = message.arbitration_id + self.arbitration_id = message.arbitration_id self.period = period super(CyclicSendTaskABC, self).__init__() @@ -131,9 +135,10 @@ def _run(self): time.sleep(max(0.0, delay)) -def send_periodic(bus, message, period): +def send_periodic(bus, message, period, *args, **kwargs): """ Send a message every `period` seconds on the given channel. - """ - return can.interface.CyclicSendTask(bus, message, period) + log.warn("The method `can.send_periodic` is deprecated and will " + "be removed in version 2.3. Please use `can.Bus.send_periodic` instead.") + return bus.send_periodic(message, period, *args, **kwargs) diff --git a/can/interface.py b/can/interface.py index f6ba2bc56..a4303b6cb 100644 --- a/can/interface.py +++ b/can/interface.py @@ -11,44 +11,21 @@ import sys import importlib -from pkg_resources import iter_entry_points import logging import can from .bus import BusABC from .broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC from .util import load_config +from .interfaces import BACKENDS +# Required by "detect_available_configs" for argument interpretation if sys.version_info.major > 2: basestring = str - log = logging.getLogger('can.interface') log_autodetect = log.getChild('detect_available_configs') -# interface_name => (module, classname) -BACKENDS = { - 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), - 'socketcan_ctypes': ('can.interfaces.socketcan', 'SocketcanCtypes_Bus'), - 'socketcan_native': ('can.interfaces.socketcan', 'SocketcanNative_Bus'), - 'serial': ('can.interfaces.serial.serial_can','SerialBus'), - 'pcan': ('can.interfaces.pcan', 'PcanBus'), - 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), - 'ixxat': ('can.interfaces.ixxat', 'IXXATBus'), - 'nican': ('can.interfaces.nican', 'NicanBus'), - 'iscan': ('can.interfaces.iscan', 'IscanBus'), - 'virtual': ('can.interfaces.virtual', 'VirtualBus'), - 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), - 'vector': ('can.interfaces.vector', 'VectorBus'), - 'slcan': ('can.interfaces.slcan', 'slcanBus') -} - -BACKENDS.update({ - interface.name: (interface.module_name, interface.attrs[0]) - for interface in iter_entry_points('python_can.interface') -}) - - def _get_class_for_interface(interface): """ Returns the main bus class for the given interface. @@ -189,8 +166,8 @@ def detect_available_configs(interfaces=None): class CyclicSendTask(CyclicSendTaskABC): - @classmethod - def __new__(cls, other, channel, *args, **kwargs): + @staticmethod + def __new__(cls, channel, *args, **kwargs): config = load_config(config={'channel': channel}) @@ -209,8 +186,8 @@ def __new__(cls, other, channel, *args, **kwargs): class MultiRateCyclicSendTask(MultiRateCyclicSendTaskABC): - @classmethod - def __new__(cls, other, channel, *args, **kwargs): + @staticmethod + def __new__(cls, channel, *args, **kwargs): config = load_config(config={'channel': channel}) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index b4a1e83e6..8046d4c2b 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -7,12 +7,26 @@ from pkg_resources import iter_entry_points -# TODO: isn't this a unnecessary information duplicate of `can/interface.py :: BACKENDS`? -VALID_INTERFACES = set(['kvaser', 'serial', 'pcan', 'socketcan_native', - 'socketcan_ctypes', 'socketcan', 'usb2can', 'ixxat', - 'nican', 'iscan', 'vector', 'virtual', 'neovi', - 'slcan']) +# interface_name => (module, classname) +BACKENDS = { + 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), + 'socketcan_ctypes': ('can.interfaces.socketcan', 'SocketcanCtypes_Bus'), + 'socketcan_native': ('can.interfaces.socketcan', 'SocketcanNative_Bus'), + 'serial': ('can.interfaces.serial.serial_can','SerialBus'), + 'pcan': ('can.interfaces.pcan', 'PcanBus'), + 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), + 'ixxat': ('can.interfaces.ixxat', 'IXXATBus'), + 'nican': ('can.interfaces.nican', 'NicanBus'), + 'iscan': ('can.interfaces.iscan', 'IscanBus'), + 'virtual': ('can.interfaces.virtual', 'VirtualBus'), + 'neovi': ('can.interfaces.ics_neovi', 'NeoViBus'), + 'vector': ('can.interfaces.vector', 'VectorBus'), + 'slcan': ('can.interfaces.slcan', 'slcanBus') +} -VALID_INTERFACES.update(set([ - interface.name for interface in iter_entry_points('python_can.interface') -])) +BACKENDS.update({ + interface.name: (interface.module_name, interface.attrs[0]) + for interface in iter_entry_points('can.interface') +}) + +VALID_INTERFACES = frozenset(list(BACKENDS.keys()) + ['socketcan']) diff --git a/can/io/__init__.py b/can/io/__init__.py index f256fb918..9dcd0b350 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -6,9 +6,14 @@ and Writers based off the file extension. """ +from __future__ import absolute_import + +# Generic from .logger import Logger -from .player import LogReader -from .log import CanutilsLogReader, CanutilsLogWriter +from .player import LogReader, MessageSync + +# Format specific +from .canutils import CanutilsLogReader, CanutilsLogWriter from .asc import ASCWriter, ASCReader from .blf import BLFReader, BLFWriter from .csv import CSVWriter, CSVReader diff --git a/can/io/log.py b/can/io/canutils.py similarity index 97% rename from can/io/log.py rename to can/io/canutils.py index e8a822f34..682a9d2fe 100644 --- a/can/io/log.py +++ b/can/io/canutils.py @@ -29,8 +29,10 @@ class CanutilsLogReader(object): """ Iterator over CAN messages from a .log Logging File (candump -L). - .log-format looks like this: - (0.0) vcan0 001#8d00100100820100 + .. note:: + .log-format looks for example like this: + + ``(0.0) vcan0 001#8d00100100820100`` """ def __init__(self, filename): diff --git a/can/io/logger.py b/can/io/logger.py index 9be09d82f..b6ac3f270 100755 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -34,8 +34,8 @@ class Logger(object): be created when instantiating this class. """ - @classmethod - def __new__(cls, other, filename): + @staticmethod + def __new__(cls, filename): if not filename: return Printer() elif filename.endswith(".asc"): diff --git a/can/io/player.py b/can/io/player.py index 4d4f17a93..d6c399273 100755 --- a/can/io/player.py +++ b/can/io/player.py @@ -7,7 +7,7 @@ in the recorded order an time intervals. """ -from __future__ import print_function +from __future__ import absolute_import, print_function import time import logging @@ -37,13 +37,14 @@ class LogReader(object): >>> for m in LogReader(my_file): ... print(m) - Note there are no time delays, if you want to reproduce - the measured delays between messages look at the - :class:`can.util.MessageSync` class. + .. note:: + There are no time delays, if you want to reproduce + the measured delays between messages look at the + :class:`can.util.MessageSync` class. """ - @classmethod - def __new__(cls, other, filename): + @staticmethod + def __new__(cls, filename): if not filename: raise TypeError("a filename must be given") elif filename.endswith(".asc"): diff --git a/can/player.py b/can/player.py index 582e5196c..984c971f9 100644 --- a/can/player.py +++ b/can/player.py @@ -7,7 +7,9 @@ Similar to canplayer in the can-utils package. """ + from __future__ import print_function + import argparse import datetime diff --git a/doc/conf.py b/doc/conf.py index f56298c53..c86010c6f 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -4,6 +4,8 @@ # # This file is execfile()d with the current directory set to its containing dir. +from __future__ import unicode_literals, absolute_import + import sys import os @@ -182,27 +184,3 @@ # Output file base name for HTML help builder. htmlhelp_basename = 'python-can' - - -class Mock(object): - - def __init__(self, *args, **kwargs): - pass - - def __call__(self, *args, **kwargs): - return Mock() - - @classmethod - def __getattr__(cls, name): - if name in ('__file__', '__path__'): - return '/dev/null' - elif name[0] == name[0].upper(): - mockType = type(name, (), {}) - mockType.__module__ = __name__ - return mockType - else: - return Mock() - -MOCK_MODULES = ['serial'] -for mod_name in MOCK_MODULES: - sys.modules[mod_name] = Mock() From e1e69311ec94eb1dc5db9177cd4fd16e87cf43e2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 00:13:10 +0200 Subject: [PATCH 114/168] small doc fixes --- can/bus.py | 6 +++--- can/interfaces/ics_neovi/neovi_bus.py | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/can/bus.py b/can/bus.py index 0c9b34938..26e538ce8 100644 --- a/can/bus.py +++ b/can/bus.py @@ -2,7 +2,7 @@ # coding: utf-8 """ -Contains the ABC bus implementation and it's documentation. +Contains the ABC bus implementation and its documentation. """ from __future__ import print_function, absolute_import @@ -62,8 +62,8 @@ class BusABC(object): def __init__(self, channel=None, can_filters=None, **config): """Construct and open a CAN bus instance of the specified type. - Subclasses should call though this one with all parameters as - it applies filters (for now). + Subclasses should call though this method with all given parameters + as it handles generic tasks like applying filters. :param channel: The can interface identifier. Expected type is backend dependent. diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index bc7a133bf..18ec93e96 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -30,7 +30,7 @@ class ICSApiError(CanError): """ - TODO add docs + Indicates an error with the ICS API. """ # A critical error which affects operation or accuracy. @@ -42,10 +42,8 @@ class ICSApiError(CanError): # An error which probably does not need attention. ICS_SPY_ERR_INFORMATION = 0x40 - def __init__( - self, error_number, description_short, description_long, - severity, restart_needed - ): + def __init__(self, error_number, description_short, description_long, + severity, restart_needed): super(ICSApiError, self).__init__(description_short) self.error_number = error_number self.description_short = description_short From 51eae1c672bb5210edea7ecc93b8d7d1e788cdf2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 00:37:12 +0200 Subject: [PATCH 115/168] added "Creating a new interface/backend" section to the documentation pages --- can/bus.py | 35 +++---------------------------- doc/development.rst | 51 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/can/bus.py b/can/bus.py index 26e538ce8..3d7f4c52d 100644 --- a/can/bus.py +++ b/can/bus.py @@ -22,37 +22,6 @@ class BusABC(object): """The CAN Bus Abstract Base Class that serves as the basis for all concrete interfaces. - - Concrete implementations *have to* implement the following: - * :meth:`~can.BusABC.send` to send individual messages - * :meth:`~can.BusABC._recv_internal` to receive individual messages - (see note below) - * set the :attr:`~can.BusABC.channel_info` attribute to a string describing - the underlying bus and/or channel - - They *might* implement the following: - * :meth:`~can.BusABC.flush_tx_buffer` to allow discrading any - messages yet to be sent - * :meth:`~can.BusABC.shutdown` to override how the bus should - shut down - * :meth:`~can.BusABC.send_periodic` to override the software based - periodic sending and push it down to the kernel or hardware - * :meth:`~can.BusABC._apply_filters` to apply efficient filters - to lower level systems like the OS kernel or hardware - * :meth:`~can.BusABC._detect_available_configs` to allow the interface - to report which configurations are currently available for new - connections - * :meth:`~can.BusABC.state` property to allow reading and/or changing - the bus state - - .. note:: - - Previously, concrete bus classes had to override :meth:`~can.BusABC.recv` - directly instead of :meth:`~can.BusABC._recv_internal`, but that has - changed to allow the abstract base class to handle in-software message - filtering as a fallback. Older (custom) interfaces might still be - implemented like that and thus might not provide message filtering. - """ #: a string describing the underlying bus and/or channel @@ -150,7 +119,9 @@ def _recv_internal(self, timeout): over time for some interfaces, like for example in the Kvaser interface. Thus it cannot be simplified to a constant value. - :param float timeout: seconds to wait for a message + :param float timeout: seconds to wait for a message, + see :meth:`can.BusABC.send` + :rtype: tuple[can.Message, bool] or tuple[None, bool] :return: 1. a message that was read or None on timeout diff --git a/doc/development.rst b/doc/development.rst index c3ebb0541..9605c9470 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -27,6 +27,55 @@ The following assumes that the commands are executed from the root of the reposi makes Sphinx complain about more subtle problems. +Creating a new interface/backend +-------------------------------- + +These steps are a guideline on how to add a new backend to python-can. + +- Create a module (either a ``*.py`` or an entire subdirctory depending + on the complexity) inside ``can.interfaces`` +- Implement the central part of the backend: the bus class that extends + :class:`can.BusABC`. See below for more info on this one! +- Register your backend bus class in ``can.interface.BACKENDS`` and + ``can.interfaces.VALID_INTERFACES``. +- Add docs where appropiate, like in ``doc/interfaces.rst`` and add + an entry in ``doc/interface/*``. +- Add tests in ``test/*`` where appropiate. + +About the ``BusABC`` class +========================== + +Concrete implementations *have to* implement the following: + * :meth:`~can.BusABC.send` to send individual messages + * :meth:`~can.BusABC._recv_internal` to receive individual messages + (see note below) + * set the :attr:`~can.BusABC.channel_info` attribute to a string describing + the underlying bus and/or channel + +They *might* implement the following: + * :meth:`~can.BusABC.flush_tx_buffer` to allow discrading any + messages yet to be sent + * :meth:`~can.BusABC.shutdown` to override how the bus should + shut down + * :meth:`~can.BusABC.send_periodic` to override the software based + periodic sending and push it down to the kernel or hardware + * :meth:`~can.BusABC._apply_filters` to apply efficient filters + to lower level systems like the OS kernel or hardware + * :meth:`~can.BusABC._detect_available_configs` to allow the interface + to report which configurations are currently available for new + connections + * :meth:`~can.BusABC.state` property to allow reading and/or changing + the bus state + +.. note:: + + Previously, concrete bus classes had to override :meth:`~can.BusABC.recv` + directly instead of :meth:`~can.BusABC._recv_internal`, but that has + changed to allow the abstract base class to handle in-software message + filtering as a fallback. Older (custom) interfaces might still be + implemented like that and thus might not provide message filtering. + + Creating a Release ------------------ @@ -41,7 +90,7 @@ Creating a Release - Upload with twine ``twine upload dist/python-can-X.Y.Z*`` - In a new virtual env check that the package can be installed with pip: ``pip install python-can==X.Y.Z`` - Create a new tag in the repository. -- Check the release on PyPi, readthedocs and github. +- Check the release on PyPi, Read the Docs and GitHub. Code Structure From b1d1f4f972185adb8835e46ff8e80e8f58e2587f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 00:40:52 +0200 Subject: [PATCH 116/168] better development docs --- doc/development.rst | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/development.rst b/doc/development.rst index c3ebb0541..1b6942ea9 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -27,23 +27,6 @@ The following assumes that the commands are executed from the root of the reposi makes Sphinx complain about more subtle problems. -Creating a Release ------------------- - -- Release from the ``master`` branch. -- Update the library version in ``__init__.py`` using `semantic versioning `__. -- Run all tests and examples against available hardware. -- Update `CONTRIBUTORS.txt` with any new contributors. -- Sanity check that documentation has stayed inline with code. For large changes update ``doc/history.rst`` -- Create a temporary virtual environment. Run ``python setup.py install`` and ``python setup.py test`` -- Create and upload the distribution: ``python setup.py sdist bdist_wheel`` -- Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl`` -- Upload with twine ``twine upload dist/python-can-X.Y.Z*`` -- In a new virtual env check that the package can be installed with pip: ``pip install python-can==X.Y.Z`` -- Create a new tag in the repository. -- Check the release on PyPi, readthedocs and github. - - Code Structure -------------- @@ -56,9 +39,6 @@ The modules in ``python-can`` are: +---------------------------------+------------------------------------------------------+ |:doc:`bus ` | Contains the interface independent Bus object. | +---------------------------------+------------------------------------------------------+ -|:doc:`CAN ` | Contains modules to emulate a CAN system, such as a | -| | time stamps, read/write streams and listeners. | -+---------------------------------+------------------------------------------------------+ |:doc:`message ` | Contains the interface independent Message object. | +---------------------------------+------------------------------------------------------+ |:doc:`io ` | Contains a range of file readers and writers. | @@ -66,3 +46,23 @@ The modules in ``python-can`` are: |:doc:`broadcastmanager ` | Contains interface independent broadcast manager | | | code. | +---------------------------------+------------------------------------------------------+ +|:doc:`CAN ` | Legacy API. Depecated. | ++---------------------------------+------------------------------------------------------+ + + +Creating a new Release +---------------------- + +- Release from the ``master`` branch. +- Update the library version in ``__init__.py`` using `semantic versioning `__. +- Run all tests and examples against available hardware. +- Update `CONTRIBUTORS.txt` with any new contributors. +- For larger changes update ``doc/history.rst``. +- Sanity check that documentation has stayed inline with code. +- Create a temporary virtual environment. Run ``python setup.py install`` and ``python setup.py test`` +- Create and upload the distribution: ``python setup.py sdist bdist_wheel`` +- Sign the packages with gpg ``gpg --detach-sign -a dist/python_can-X.Y.Z-py3-none-any.whl`` +- Upload with twine ``twine upload dist/python-can-X.Y.Z*`` +- In a new virtual env check that the package can be installed with pip: ``pip install python-can==X.Y.Z`` +- Create a new tag in the repository. +- Check the release on PyPi, Read the docs and GitHub. From 3490c251307b148f9ef4c88e6ad75c63fd8ad4f1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 00:45:53 +0200 Subject: [PATCH 117/168] documented the removal of the functional API for send_periodic --- doc/bcm.rst | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/doc/bcm.rst b/doc/bcm.rst index 2df11818b..d5615c58d 100644 --- a/doc/bcm.rst +++ b/doc/bcm.rst @@ -1,4 +1,3 @@ - Broadcast Manager ================= @@ -6,10 +5,10 @@ The broadcast manager isn't yet supported by all interfaces. Currently SockerCAN and IXXAT are supported at least partially. It allows the user to setup periodic message jobs. -If periodic transmission is not supported natively, a software thread based -scheduler is used as a fallback. +If periodic transmission is not supported natively, a software thread +based scheduler is used as a fallback. -This example shows the ctypes socketcan using the broadcast manager: +This example shows the socketcan_ctypes backend using the broadcast manager: .. literalinclude:: ../examples/cyclic.py @@ -17,10 +16,9 @@ This example shows the ctypes socketcan using the broadcast manager: :linenos: -Functional API --------------- - -.. autofunction:: can.send_periodic +.. note:: + The functional APi in :meth:`can.send_periodic` is now deprected. + Use the object oriented APi in :meth:`can.BusABC.send_periodic` instead. Class based API From 6a527fb2b7c9034abc6307ce9453b06e1167296e Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 00:51:27 +0200 Subject: [PATCH 118/168] moved some docs of the BusABC to the development section --- can/bus.py | 8 -------- doc/development.rst | 10 +++++++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/can/bus.py b/can/bus.py index 3d7f4c52d..9e70cb67e 100644 --- a/can/bus.py +++ b/can/bus.py @@ -51,14 +51,6 @@ def __str__(self): def recv(self, timeout=None): """Block waiting for a message from the Bus. - If the concrete bus does not override it, this method makes sure - that all filters have been applied. That is the case for all - internal interfaces. - - To enable receiving for an interface, please override - :meth:`~can.BusABC._recv_internal` instead of this one. - Overriding this method is deprecated. - :param float timeout: seconds to wait for a message or None to wait indefinitely diff --git a/doc/development.rst b/doc/development.rst index 9605c9470..1ff57c05a 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -48,7 +48,7 @@ About the ``BusABC`` class Concrete implementations *have to* implement the following: * :meth:`~can.BusABC.send` to send individual messages * :meth:`~can.BusABC._recv_internal` to receive individual messages - (see note below) + (see note below!) * set the :attr:`~can.BusABC.channel_info` attribute to a string describing the underlying bus and/or channel @@ -69,11 +69,15 @@ They *might* implement the following: .. note:: + *TL;DR*: Only override :meth:`~can.BusABC._recv_internal`, + never :meth:`~can.BusABC.recv` directly. + Previously, concrete bus classes had to override :meth:`~can.BusABC.recv` directly instead of :meth:`~can.BusABC._recv_internal`, but that has changed to allow the abstract base class to handle in-software message - filtering as a fallback. Older (custom) interfaces might still be - implemented like that and thus might not provide message filtering. + filtering as a fallback. All internal interfaces now implement that new + behaviour. Older (custom) interfaces might still be implemented like that + and thus might not provide message filtering: Creating a Release From 220bd287c8a9caaa98d06ce381e03918c8e81d1a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 01:00:38 +0200 Subject: [PATCH 119/168] fix some imports in can.io.* --- can/io/__init__.py | 2 +- can/io/logger.py | 2 +- can/io/player.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/can/io/__init__.py b/can/io/__init__.py index 9dcd0b350..1dc412d52 100644 --- a/can/io/__init__.py +++ b/can/io/__init__.py @@ -13,9 +13,9 @@ from .player import LogReader, MessageSync # Format specific -from .canutils import CanutilsLogReader, CanutilsLogWriter from .asc import ASCWriter, ASCReader from .blf import BLFReader, BLFWriter +from .canutils import CanutilsLogReader, CanutilsLogWriter from .csv import CSVWriter, CSVReader from .sqlite import SqliteReader, SqliteWriter from .stdout import Printer diff --git a/can/io/logger.py b/can/io/logger.py index b6ac3f270..c4b27815e 100755 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -9,8 +9,8 @@ from .asc import ASCWriter from .blf import BLFWriter +from .canutils import CanutilsLogWriter from .csv import CSVWriter -from .log import CanutilsLogWriter from .sqlite import SqliteWriter from .stdout import Printer diff --git a/can/io/player.py b/can/io/player.py index d6c399273..958f6a8dd 100755 --- a/can/io/player.py +++ b/can/io/player.py @@ -14,8 +14,8 @@ from .asc import ASCReader from .blf import BLFReader +from .canutils import CanutilsLogReader from .csv import CSVReader -from .log import CanutilsLogReader from .sqlite import SqliteReader log = logging.getLogger('can.io.player') From 6ebe61064752032001bad741ff02a19459f20fca Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 07:46:04 +0200 Subject: [PATCH 120/168] cleanups, fixes & docs in Bus.__new__() and util.load_config() (#309) --- can/interface.py | 47 ++++++++++++++++++++------------------- can/util.py | 58 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/can/interface.py b/can/interface.py index 2413d72f1..492323b96 100644 --- a/can/interface.py +++ b/can/interface.py @@ -7,7 +7,7 @@ CyclicSendTasks. """ -from __future__ import absolute_import +from __future__ import absolute_import, print_function import sys import importlib @@ -98,36 +98,37 @@ class Bus(BusABC): configuration file from default locations. """ - @classmethod - def __new__(cls, other, channel=None, *args, **kwargs): + @staticmethod + def __new__(cls, *args, **config): """ - Takes the same arguments as :class:`can.BusABC` with the addition of: + Takes the same arguments as :class:`can.BusABC.__init__` with the addition of: - :param kwargs: - Should contain a bustype key with a valid interface name. + :param dict config: + Should contain an ``interface`` key with a valid interface name. If not, + it is completed using :meth:`can.util.load_config`. - :raises: - NotImplementedError if the bustype isn't recognized - :raises: - ValueError if the bustype or channel isn't either passed as an argument - or set in the can.rc config. + :raises: NotImplementedError + if the ``interface`` isn't recognized + :raises: ValueError + if the ``channel`` could not be determined """ - # Figure out the configuration - config = load_config(config={ - 'interface': kwargs.get('bustype', kwargs.get('interface')), - 'channel': channel - }) - - # remove the bustype & interface so it doesn't get passed to the backend - if 'bustype' in kwargs: - del kwargs['bustype'] - if 'interface' in kwargs: - del kwargs['interface'] + # figure out the rest of the configuration; this might raise an error + config = load_config(config=config) + # resolve the bus class to use for that interface cls = _get_class_for_interface(config['interface']) - return cls(channel=config['channel'], *args, **kwargs) + + # remove the 'interface' key so it doesn't get passed to the backend + del config['interface'] + + # make sure the bus can handle this config + if 'channel' not in config: + raise ValueError("channel argument missing") + + # the channel attribute should be present in **config + return cls(*args, **config) def detect_available_configs(interfaces=None): diff --git a/can/util.py b/can/util.py index 20c59bf0a..505bd36e4 100644 --- a/can/util.py +++ b/can/util.py @@ -121,10 +121,17 @@ def load_config(path=None, config=None): If you pass ``"socketcan"`` this automatically selects between the native and ctypes version. + .. note:: + + The key ``bustype`` is copied to ``interface`` if that one is missing + and does never appear in the result. + :param path: Optional path to config file. + :param config: A dict which may set the 'interface', and/or the 'channel', or neither. + It may set other values that are passed through. :return: A config dictionary that should contain 'interface' & 'channel':: @@ -132,46 +139,61 @@ def load_config(path=None, config=None): { 'interface': 'python-can backend interface to use', 'channel': 'default channel to use', + # possibly more } Note ``None`` will be used if all the options are exhausted without finding a value. + + All unused values are passed from ``config`` over to this. + + :raises: + NotImplementedError if the ``interface`` isn't recognized """ - if config is None: - config = {} - system_config = {} - configs = [ - config, + # start with an empty dict to apply filtering to all sources + given_config = config + config = {} + + # use the given dict for default values + config_sources = [ + given_config, can.rc, load_environment_config, lambda: load_file_config(path) ] # Slightly complex here to only search for the file config if required - for cfg in configs: + for cfg in config_sources: if callable(cfg): cfg = cfg() + # remove legacy operator (and copy to interface if not already present) + if 'bustype' in cfg: + if 'interface' not in cfg or not cfg['interface']: + cfg['interface'] = cfg['bustype'] + del cfg['bustype'] + # copy all new parameters for key in cfg: - if key not in system_config and cfg[key] is not None: - system_config[key] = cfg[key] + if key not in config: + config[key] = cfg[key] # substitute None for all values not found for key in REQUIRED_KEYS: - if key not in system_config: - system_config[key] = None + if key not in config: + config[key] = None - if system_config['interface'] == 'socketcan': - system_config['interface'] = choose_socketcan_implementation() + # this is done later too but better safe than sorry + if config['interface'] == 'socketcan': + config['interface'] = choose_socketcan_implementation() - if system_config['interface'] not in VALID_INTERFACES: - raise NotImplementedError('Invalid CAN Bus Type - {}'.format(system_config['interface'])) + if config['interface'] not in VALID_INTERFACES: + raise NotImplementedError('Invalid CAN Bus Type - {}'.format(config['interface'])) - if 'bitrate' in system_config: - system_config['bitrate'] = int(system_config['bitrate']) + if 'bitrate' in config: + config['bitrate'] = int(config['bitrate']) - can.log.debug("can config: {}".format(system_config)) - return system_config + can.log.debug("loaded can config: {}".format(config)) + return config def choose_socketcan_implementation(): From 9f354d0655d3a55d170d43b68f4501c7faa6675c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 08:48:51 +0200 Subject: [PATCH 121/168] Add a general timeout for tests (#313) --- .appveyor.yml | 14 ++++++++------ .travis.yml | 2 +- test/test_kvaser.py | 14 ++++++++------ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index c724c96a2..505ea9808 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -24,14 +24,16 @@ matrix: - PYTHON: "C:\\Python33-x64" install: + # Prepend Python installation to PATH + - set PATH=%PYTHON_INSTALL%;%PATH% + + # Prepend Python scripts to PATH (e.g. py.test) + - set PATH=%PYTHON_INSTALL%\\Scripts;%PATH% + # We need to install the python-can library itself - - "%PYTHON%\\python.exe -m pip install .[test]" + - "python -m pip install .[test]" build: off test_script: - # Note that you must use the environment variable %PYTHON% to refer to - # the interpreter you're using - Appveyor does not do anything special - # to put the Python version you want to use on PATH. - - "%PYTHON%\\python.exe setup.py test -v" # --timeout=300 -> TODO: - # need to switch to pytest like on Unix first, but that's for another PR + - "pytest -v --timeout=300" diff --git a/.travis.yml b/.travis.yml index 1e6f4be50..4ff088391 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,4 +51,4 @@ install: - travis_retry pip install .[test] script: - - py.test -v --timeout=300 + - pytest -v --timeout=300 diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 5bcd59103..9bf211f21 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -5,19 +5,20 @@ """ import ctypes -import unittest import time import logging - -import can -from can.interfaces.kvaser import canlib -from can.interfaces.kvaser import constants - +import unittest try: from unittest.mock import Mock, patch except ImportError: from mock import patch, Mock +import pytest + +import can +from can.interfaces.kvaser import canlib +from can.interfaces.kvaser import constants + class KvaserTest(unittest.TestCase): @@ -122,6 +123,7 @@ def test_send_standard(self): self.assertEqual(self.msg['flags'], constants.canMSG_STD) self.assertSequenceEqual(self.msg['data'], [50, 51]) + @pytest.mark.timeout(3.0) def test_recv_no_message(self): self.assertEqual(self.bus.recv(timeout=0.5), None) From 00f36d15f55358bca32fd949c014c6168df1bd1c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 14:47:40 +0200 Subject: [PATCH 122/168] added a ThreadSafeBus based on the wrapt library --- can/bus.py | 6 +-- can/thread_safe_bus.py | 96 ++++++++++++++++++++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 can/thread_safe_bus.py diff --git a/can/bus.py b/can/bus.py index 9e70cb67e..9271c0030 100644 --- a/can/bus.py +++ b/can/bus.py @@ -169,10 +169,10 @@ def send_periodic(self, msg, period, duration=None): least *duration* seconds. """ - if not hasattr(self, "_lock"): + if not hasattr(self, "_lock_send_periodic"): # Create a send lock for this bus - self._lock = threading.Lock() - return ThreadBasedCyclicSendTask(self, self._lock, msg, period, duration) + self._lock_send_periodic = threading.Lock() + return ThreadBasedCyclicSendTask(self, self._lock_send_periodic, msg, period, duration) def __iter__(self): """Allow iteration on messages as they are received. diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py new file mode 100644 index 000000000..10bc2c0f1 --- /dev/null +++ b/can/thread_safe_bus.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +""" + +from __future__ import print_function, absolute_import + +from threading import RLock + +from wrapt import ObjectProxy, synchronized + +from can import Bus, BusABC + + +class NullContextManager(object): + """ + A context manager that does nothing at all. + """ + + def __init__(self, resource=None): + self.resource = resource + + def __enter__(self): + return self.resource + + def __exit__(self, *args): + pass + + +class ThreadSafeBus(ObjectProxy, BusABC): + """ + Contains a thread safe :class:`can.BusABC` implementation that + wraps around an existing interface instance. All public methods + of that base class are now safe to be called from multiple threads. + + Use this as a drop in replacement for :class:`~can.BusABC`. + + .. note:: + + This approach assumes that both :meth:`~can.BusABC.send` and + :meth:`~can.BusABC._recv_internal` of the underlying bus instance can be + called simultaneously, and that the methods uses :meth:`~can.BusABC._recv_internal` + instead of :meth:`~can.BusABC.recv` directly. + """ + + # init locks for sending and receiving + _lock_send = RLock() + _lock_recv = RLock() + + def __init__(self, *args, **kwargs): + # now, BusABC.send_periodic() does not need a lock anymore, but the + # implementation still requires a context manager + self.__wrapped__._lock_send_periodic = NullContextManager() + + def recv(self, timeout=None, *args, **kwargs): + with self._lock_recv: + return self.__wrapped__.recv(timeout=timeout, *args, **kwargs) + + def send(self, msg, timeout=None, *args, **kwargs): + with self._lock_send: + return self.__wrapped__.send(msg, timeout=timeout, *args, **kwargs) + + # send_periodic does not need a lock, see that method and the comment in this __init__ + + @property + def filters(self): + with self._lock_recv: + return self.__wrapped__.filters + + @filters.setter + def filters(self, filters): + with self._lock_recv: + self.__wrapped__.filters = filters + + def set_filters(self, can_filters=None, *args, **kwargs): + with self._lock_recv: + return self.__wrapped__.set_filters(can_filters=can_filters, *args, **kwargs) + + def flush_tx_buffer(self, *args, **kwargs): + with self._lock_send: + return self.__wrapped__.flush_tx_buffer(*args, **kwargs) + + def shutdown(self, *args, **kwargs): + with self._lock_send, self._lock_recv: + return self.__wrapped__.shutdown(*args, **kwargs) + + @property + def state(self): + with self._lock_send, self._lock_recv: + return self.__wrapped__.state + + @state.setter + def state(self, new_state): + with self._lock_send, self._lock_recv: + self.__wrapped__.state = new_state diff --git a/setup.py b/setup.py index 0af5e8577..c70c985ea 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,7 @@ python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", install_requires=[ 'setuptools', + 'wrapt ~= 1.10', ] + (['subprocess32 ~= 3.2.7'] if version_info.major < 3 else []), extras_require={ 'serial': ['pyserial >= 3.0'], From e9370834476ff7765c5416eebb720021fbd48dd0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 15:03:22 +0200 Subject: [PATCH 123/168] add TheradSafeBus to root __init__.py --- can/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/__init__.py b/can/__init__.py index ce126ff95..3fa8c01d1 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -34,6 +34,7 @@ class CanError(IOError): from .message import Message from .bus import BusABC +from .thread_safe_bus import ThreadSafeBus from .notifier import Notifier from .interfaces import VALID_INTERFACES from . import interface From 62ecb4ec69e2be6da0e12f403562d27dc0b4096b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 15:28:28 +0200 Subject: [PATCH 124/168] added tests for ThreadSafeBus --- can/thread_safe_bus.py | 3 +- test/back2back_test.py | 71 +++++++++++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 10bc2c0f1..1b7d6aae2 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -10,7 +10,8 @@ from wrapt import ObjectProxy, synchronized -from can import Bus, BusABC +from .interface import Bus +from .bus import BusABC class NullContextManager(object): diff --git a/test/back2back_test.py b/test/back2back_test.py index 304bec690..e9ebc698a 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -10,10 +10,11 @@ import sys import unittest from time import sleep +from multiprocessing.dummy import Pool as ThreadPool -import can +import pytest -from .data.example_data import generate_message +import can from .config import * from .data.example_data import generate_message @@ -35,16 +36,16 @@ class Back2BackTestCase(unittest.TestCase): """ def setUp(self): - self.bus1 = can.interface.Bus(channel=CHANNEL_1, - bustype=INTERFACE_1, - bitrate=BITRATE, - fd=TEST_CAN_FD, - single_handle=True) - self.bus2 = can.interface.Bus(channel=CHANNEL_2, - bustype=INTERFACE_2, - bitrate=BITRATE, - fd=TEST_CAN_FD, - single_handle=True) + self.bus1 = can.Bus(channel=CHANNEL_1, + bustype=INTERFACE_1, + bitrate=BITRATE, + fd=TEST_CAN_FD, + single_handle=True) + self.bus2 = can.Bus(channel=CHANNEL_2, + bustype=INTERFACE_2, + bitrate=BITRATE, + fd=TEST_CAN_FD, + single_handle=True) def tearDown(self): self.bus1.shutdown() @@ -170,5 +171,51 @@ def test_basics(self): notifier.stop() +class TestThreadSafeBus(Back2BackTestCase): + """Does some testing that is better than nothing. + """ + + def setUp(self): + self.bus1 = can.ThreadSafeBus(channel=CHANNEL_1, + bustype=INTERFACE_1, + bitrate=BITRATE, + fd=TEST_CAN_FD, + single_handle=True) + self.bus2 = can.ThreadSafeBus(channel=CHANNEL_2, + bustype=INTERFACE_2, + bitrate=BITRATE, + fd=TEST_CAN_FD, + single_handle=True) + + @pytest.mark.timeout(5.0) + def test_concurrent_writes(self): + sender_pool = ThreadPool(100) + receiver_pool = ThreadPool(100) + + message = can.Message( + arbitration_id=0x123, + extended_id=True, + timestamp=121334.365, + data=[254, 255, 1, 2] + ) + workload = 1000 * [message] + + def sender(msg): + self.bus1.send(msg) + + def receiver(_): + result = self.bus2.recv(timeout=2.0) + self.assertIsNotNone(result) + self.assertEqual(result, message) + + sender_pool.map_async(sender, workload) + receiver_pool.map_async(receiver, len(workload) * [None]) + + sender_pool.close() + sender_pool.join() + receiver_pool.close() + receiver_pool.join() + + if __name__ == '__main__': unittest.main() From cd147dc4647a16d5c889c97ecf48e2a7a1e65352 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 15:40:07 +0200 Subject: [PATCH 125/168] try to fix constructor of ThreadSafeBus --- can/thread_safe_bus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 1b7d6aae2..2ffb8eda7 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -50,6 +50,8 @@ class ThreadSafeBus(ObjectProxy, BusABC): _lock_recv = RLock() def __init__(self, *args, **kwargs): + super(ThreadSafeBus, self).__init__(Bus(*args, **kwargs)) + # now, BusABC.send_periodic() does not need a lock anymore, but the # implementation still requires a context manager self.__wrapped__._lock_send_periodic = NullContextManager() From 1b8414e6ca9879be551690847fff2e7ae8644943 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 15:53:09 +0200 Subject: [PATCH 126/168] try to use metaclasses correctly --- can/thread_safe_bus.py | 3 ++- setup.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 2ffb8eda7..133d118b1 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -8,6 +8,7 @@ from threading import RLock +import six from wrapt import ObjectProxy, synchronized from .interface import Bus @@ -29,7 +30,7 @@ def __exit__(self, *args): pass -class ThreadSafeBus(ObjectProxy, BusABC): +class ThreadSafeBus(six.with_metaclass(ObjectProxy, BusABC)): """ Contains a thread safe :class:`can.BusABC` implementation that wraps around an existing interface instance. All public methods diff --git a/setup.py b/setup.py index c70c985ea..9acfa1934 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ install_requires=[ 'setuptools', 'wrapt ~= 1.10', + 'six ~= 1.11', ] + (['subprocess32 ~= 3.2.7'] if version_info.major < 3 else []), extras_require={ 'serial': ['pyserial >= 3.0'], From 7b1f5c2f479324a95239eaa1a89fa4a776384245 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 16:00:45 +0200 Subject: [PATCH 127/168] explicitly require pytest --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 9acfa1934..e124b505e 100644 --- a/setup.py +++ b/setup.py @@ -21,10 +21,11 @@ # Dependencies tests_require = [ - 'mock >= 2.0.0', - 'nose >= 1.3.7', - 'pytest-timeout >= 1.2.1', - 'pyserial >= 3.0' + 'mock ~= 2.0', + 'nose ~= 1.3.7', + 'pytest ~= 3.6', + 'pytest-timeout ~= 1.2', + 'pyserial ~= 3.0' ] setup( From b66c610728d009b7e23d663c94e906a7dc54c5d9 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 17:26:41 +0200 Subject: [PATCH 128/168] attempt to fix metaclass in ThreadSafeBus --- can/thread_safe_bus.py | 8 +++++--- setup.py | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 133d118b1..507aa5b28 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -6,10 +6,10 @@ from __future__ import print_function, absolute_import +from abc import ABCMeta from threading import RLock -import six -from wrapt import ObjectProxy, synchronized +from wrapt import ObjectProxy from .interface import Bus from .bus import BusABC @@ -30,7 +30,7 @@ def __exit__(self, *args): pass -class ThreadSafeBus(six.with_metaclass(ObjectProxy, BusABC)): +class ThreadSafeBus(ObjectProxy, BusABC): """ Contains a thread safe :class:`can.BusABC` implementation that wraps around an existing interface instance. All public methods @@ -46,6 +46,8 @@ class ThreadSafeBus(six.with_metaclass(ObjectProxy, BusABC)): instead of :meth:`~can.BusABC.recv` directly. """ + __metaclass__ = ABCMeta + # init locks for sending and receiving _lock_send = RLock() _lock_recv = RLock() diff --git a/setup.py b/setup.py index e124b505e..04dab4463 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,6 @@ install_requires=[ 'setuptools', 'wrapt ~= 1.10', - 'six ~= 1.11', ] + (['subprocess32 ~= 3.2.7'] if version_info.major < 3 else []), extras_require={ 'serial': ['pyserial >= 3.0'], From 458e0542e59dc6b7eda31e9e050e8f14e594742c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 17:30:01 +0200 Subject: [PATCH 129/168] removed BusABC from ThreadSafeBus parent class --- can/thread_safe_bus.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 507aa5b28..4238f8eb5 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -30,7 +30,7 @@ def __exit__(self, *args): pass -class ThreadSafeBus(ObjectProxy, BusABC): +class ThreadSafeBus(ObjectProxy): """ Contains a thread safe :class:`can.BusABC` implementation that wraps around an existing interface instance. All public methods @@ -46,8 +46,6 @@ class ThreadSafeBus(ObjectProxy, BusABC): instead of :meth:`~can.BusABC.recv` directly. """ - __metaclass__ = ABCMeta - # init locks for sending and receiving _lock_send = RLock() _lock_recv = RLock() From b1761d58965958206b4939d724a5071a8903a4d6 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 28 May 2018 18:08:53 +0200 Subject: [PATCH 130/168] doc --- can/thread_safe_bus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 4238f8eb5..e62efc24e 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -46,7 +46,7 @@ class ThreadSafeBus(ObjectProxy): instead of :meth:`~can.BusABC.recv` directly. """ - # init locks for sending and receiving + # init locks for sending and receiving separately _lock_send = RLock() _lock_recv = RLock() From bd6c5d6b147cf61964f4b72f3e8b086afc34e19c Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 30 May 2018 18:51:14 +0200 Subject: [PATCH 131/168] added documentation --- can/bus.py | 4 ++++ can/thread_safe_bus.py | 8 +++++--- doc/api.rst | 6 ++++-- doc/bus.rst | 20 ++++++++++++++++++-- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/can/bus.py b/can/bus.py index 9271c0030..ffe48280b 100644 --- a/can/bus.py +++ b/can/bus.py @@ -191,6 +191,10 @@ def __iter__(self): @property def filters(self): + """ + Modify the filters of this bus. See :meth:`~can.BusABC.set_filters` + for details. + """ return self._filters @filters.setter diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index e62efc24e..b161d47c9 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -35,14 +35,15 @@ class ThreadSafeBus(ObjectProxy): Contains a thread safe :class:`can.BusABC` implementation that wraps around an existing interface instance. All public methods of that base class are now safe to be called from multiple threads. + The send and receive methods are synchronized separately. - Use this as a drop in replacement for :class:`~can.BusABC`. + Use this as a drop-in replacement for :class:`~can.BusABC`. .. note:: This approach assumes that both :meth:`~can.BusABC.send` and :meth:`~can.BusABC._recv_internal` of the underlying bus instance can be - called simultaneously, and that the methods uses :meth:`~can.BusABC._recv_internal` + called simultaneously, and that the methods use :meth:`~can.BusABC._recv_internal` instead of :meth:`~can.BusABC.recv` directly. """ @@ -65,7 +66,8 @@ def send(self, msg, timeout=None, *args, **kwargs): with self._lock_send: return self.__wrapped__.send(msg, timeout=timeout, *args, **kwargs) - # send_periodic does not need a lock, see that method and the comment in this __init__ + # send_periodic does not need a lock, since the underlying + # `send` method is already synchronized @property def filters(self): diff --git a/doc/api.rst b/doc/api.rst index 3eca7ddc8..13adb2903 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -25,9 +25,12 @@ Utilities .. automodule:: can.util :members: +.. automethod:: can.detect_available_configs .. _notifier: - + + + Notifier -------- @@ -35,4 +38,3 @@ The Notifier object is used as a message distributor for a bus. .. autoclass:: can.Notifier :members: - diff --git a/doc/bus.rst b/doc/bus.rst index d00cd211e..c71662289 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -4,7 +4,9 @@ Bus --- The :class:`~can.Bus` class, as the name suggests, provides an abstraction of a CAN bus. -The bus provides a wrapper around a physical or virtual CAN Bus. +The bus provides an abstract wrapper around a physical or virtual CAN Bus. + +A thread safe bus wrapper is also available, see "Thread safe bus". Filtering @@ -37,10 +39,24 @@ Receiving ''''''''' Reading from the bus is achieved by either calling the :meth:`~can.BusABC.recv` method or -by directly iterating over the bus:: +by directly iterating over the bus: for msg in bus: print(msg.data) Alternatively the :class:`~can.Listener` api can be used, which is a list of :class:`~can.Listener` subclasses that receive notifications when new messages arrive. + +Thread safe bus +--------------- + +This thread safe version of the bus class can be used by multiple threads at once. +Sending and receiving is locked seperatly to avoid unnessesary delays. + +It can be used exactly like the normal :class:`~can.Bus` class: + + my_bus = can.Bus(interface='socketcan', channel='vcan0') + my_bus.send(...) + +.. autoclass:: can.ThreadSafeBus + :members: From 7b1026d77e169ce5c57635a571941f425d752c51 Mon Sep 17 00:00:00 2001 From: Edoardo Bezzeccheri Date: Mon, 4 Jun 2018 15:23:08 +0200 Subject: [PATCH 132/168] Feature: Context manager (#283) Implemented context manager for Bus class and added some tests --- can/bus.py | 6 +++ can/interfaces/pcan/pcan.py | 1 + can/interfaces/virtual.py | 17 ++++++- test/back2back_test.py | 98 +++++++++++++++++++++++++++++++++++++ test/contextmanager_test.py | 36 ++++++++++++++ 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 test/contextmanager_test.py diff --git a/can/bus.py b/can/bus.py index 9e70cb67e..4207ad306 100644 --- a/can/bus.py +++ b/can/bus.py @@ -279,6 +279,12 @@ def shutdown(self): """ pass + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.shutdown() + @property def state(self): """ diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 4f8e19729..55009a623 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -273,6 +273,7 @@ def flash(self, flash): self.m_objPCANBasic.SetValue(self.m_PcanHandle, PCAN_CHANNEL_IDENTIFYING, bool(flash)) def shutdown(self): + super(PcanBus, self).shutdown() self.m_objPCANBasic.Uninitialize(self.m_PcanHandle) @property diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 46e393f54..32934da78 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -19,6 +19,7 @@ from random import randint from can.bus import BusABC +from can import CanError logger = logging.getLogger(__name__) @@ -49,6 +50,7 @@ def __init__(self, channel=None, receive_own_messages=False, **config): self.channel_id = channel self.channel_info = 'Virtual bus channel %s' % self.channel_id self.receive_own_messages = receive_own_messages + self._open = True with channels_lock: @@ -60,24 +62,35 @@ def __init__(self, channel=None, receive_own_messages=False, **config): self.queue = queue.Queue() self.channel.append(self.queue) + def _check_if_open(self): + """Raises CanError if the bus is not open. + + Has to be called in every method that accesses the bus. + """ + if not self._open: + raise CanError('Operation on closed bus') + def _recv_internal(self, timeout): + self._check_if_open() try: msg = self.queue.get(block=True, timeout=timeout) except queue.Empty: return None, False else: - #logger.log(9, 'Received message:\n%s', msg) return msg, False def send(self, msg, timeout=None): + self._check_if_open() msg.timestamp = time.time() # Add message to all listening on this channel for bus_queue in self.channel: if bus_queue is not self.queue or self.receive_own_messages: bus_queue.put(msg) - #logger.log(9, 'Transmitted message:\n%s', msg) def shutdown(self): + self._check_if_open() + self._open = False + with channels_lock: self.channel.remove(self.queue) diff --git a/test/back2back_test.py b/test/back2back_test.py index 304bec690..4b61a3782 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -170,5 +170,103 @@ def test_basics(self): notifier.stop() +class Back2BackTestCaseWithContext(unittest.TestCase): + """ + Use two interfaces connected to the same CAN bus and test them against + each other. + """ + + def _setup_with_context_manager(self, cm): + """Use a contextmanager to setUp a test case.""" + val = cm.__enter__() + self.addCleanup(cm.__exit__, None, None, None) # Same as tearDown call + return val + + def setUp(self): + self.bus1 = self._setup_with_context_manager(can.interface.Bus(channel=CHANNEL_1, + bustype=INTERFACE_1, + bitrate=BITRATE, + fd=TEST_CAN_FD, + single_handle=True)) + self.bus2 = self._setup_with_context_manager(can.interface.Bus(channel=CHANNEL_2, + bustype=INTERFACE_2, + bitrate=BITRATE, + fd=TEST_CAN_FD, + single_handle=True)) + + def _check_received_message(self, recv_msg, sent_msg): + self.assertIsNotNone(recv_msg, + "No message was received on %s" % INTERFACE_2) + self.assertEqual(recv_msg.arbitration_id, sent_msg.arbitration_id) + self.assertEqual(recv_msg.id_type, sent_msg.id_type) + self.assertEqual(recv_msg.is_remote_frame, sent_msg.is_remote_frame) + self.assertEqual(recv_msg.is_error_frame, sent_msg.is_error_frame) + self.assertEqual(recv_msg.is_fd, sent_msg.is_fd) + self.assertEqual(recv_msg.bitrate_switch, sent_msg.bitrate_switch) + self.assertEqual(recv_msg.dlc, sent_msg.dlc) + if not sent_msg.is_remote_frame: + self.assertSequenceEqual(recv_msg.data, sent_msg.data) + + def _send_and_receive(self, msg): + # Send with bus 1, receive with bus 2 + self.bus1.send(msg) + recv_msg = self.bus2.recv(TIMEOUT) + self._check_received_message(recv_msg, msg) + # Some buses may receive their own messages. Remove it from the queue + self.bus1.recv(0) + + # Send with bus 2, receive with bus 1 + # Add 1 to arbitration ID to make it a different message + msg.arbitration_id += 1 + self.bus2.send(msg) + recv_msg = self.bus1.recv(TIMEOUT) + self._check_received_message(recv_msg, msg) + + def test_no_message(self): + self.assertIsNone(self.bus1.recv(0.1)) + + def test_standard_message(self): + msg = can.Message(extended_id=False, + arbitration_id=0x100, + data=[1, 2, 3, 4, 5, 6, 7, 8]) + self._send_and_receive(msg) + + def test_extended_message(self): + msg = can.Message(extended_id=True, + arbitration_id=0x123456, + data=[10, 11, 12, 13, 14, 15, 16, 17]) + self._send_and_receive(msg) + + def test_remote_message(self): + msg = can.Message(extended_id=False, + arbitration_id=0x200, + is_remote_frame=True, + dlc=4) + self._send_and_receive(msg) + + def test_dlc_less_than_eight(self): + msg = can.Message(extended_id=False, + arbitration_id=0x300, + data=[4, 5, 6]) + self._send_and_receive(msg) + + @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") + def test_fd_message(self): + msg = can.Message(is_fd=True, + extended_id=True, + arbitration_id=0x56789, + data=[0xff] * 64) + self._send_and_receive(msg) + + @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") + def test_fd_message_with_brs(self): + msg = can.Message(is_fd=True, + bitrate_switch=True, + extended_id=True, + arbitration_id=0x98765, + data=[0xff] * 48) + self._send_and_receive(msg) + + if __name__ == '__main__': unittest.main() diff --git a/test/contextmanager_test.py b/test/contextmanager_test.py new file mode 100644 index 000000000..a69dfd5e4 --- /dev/null +++ b/test/contextmanager_test.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +This module tests the context manager of Bus and Notifier classes +""" + +import unittest +import can + + +class ContextManagerTest(unittest.TestCase): + + def setUp(self): + data = [0, 1, 2, 3, 4, 5, 6, 7] + self.msg_send = can.Message(extended_id=False, arbitration_id=0x100, data=data) + + def test_open_buses(self): + with can.interface.Bus(bustype='virtual') as bus_send, can.interface.Bus(bustype='virtual') as bus_recv: + bus_send.send(self.msg_send) + msg_recv = bus_recv.recv() + + # Receiving a frame with data should evaluate msg_recv to True + self.assertTrue(msg_recv) + + def test_use_closed_bus(self): + with can.interface.Bus(bustype='virtual') as bus_send, can.interface.Bus(bustype='virtual') as bus_recv: + bus_send.send(self.msg_send) + + # Receiving a frame after bus has been closed should raise a CanException + self.assertRaises(can.CanError, bus_recv.recv) + self.assertRaises(can.CanError, bus_send.send, self.msg_send) + + +if __name__ == '__main__': + unittest.main() From 3da5bd9c0cd72f8e0a1b9882865d07438b037efc Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 4 Jun 2018 16:08:56 +0200 Subject: [PATCH 133/168] cleanups of context manager tests --- test/back2back_test.py | 98 ------------------------------------------ 1 file changed, 98 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 4b61a3782..304bec690 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -170,103 +170,5 @@ def test_basics(self): notifier.stop() -class Back2BackTestCaseWithContext(unittest.TestCase): - """ - Use two interfaces connected to the same CAN bus and test them against - each other. - """ - - def _setup_with_context_manager(self, cm): - """Use a contextmanager to setUp a test case.""" - val = cm.__enter__() - self.addCleanup(cm.__exit__, None, None, None) # Same as tearDown call - return val - - def setUp(self): - self.bus1 = self._setup_with_context_manager(can.interface.Bus(channel=CHANNEL_1, - bustype=INTERFACE_1, - bitrate=BITRATE, - fd=TEST_CAN_FD, - single_handle=True)) - self.bus2 = self._setup_with_context_manager(can.interface.Bus(channel=CHANNEL_2, - bustype=INTERFACE_2, - bitrate=BITRATE, - fd=TEST_CAN_FD, - single_handle=True)) - - def _check_received_message(self, recv_msg, sent_msg): - self.assertIsNotNone(recv_msg, - "No message was received on %s" % INTERFACE_2) - self.assertEqual(recv_msg.arbitration_id, sent_msg.arbitration_id) - self.assertEqual(recv_msg.id_type, sent_msg.id_type) - self.assertEqual(recv_msg.is_remote_frame, sent_msg.is_remote_frame) - self.assertEqual(recv_msg.is_error_frame, sent_msg.is_error_frame) - self.assertEqual(recv_msg.is_fd, sent_msg.is_fd) - self.assertEqual(recv_msg.bitrate_switch, sent_msg.bitrate_switch) - self.assertEqual(recv_msg.dlc, sent_msg.dlc) - if not sent_msg.is_remote_frame: - self.assertSequenceEqual(recv_msg.data, sent_msg.data) - - def _send_and_receive(self, msg): - # Send with bus 1, receive with bus 2 - self.bus1.send(msg) - recv_msg = self.bus2.recv(TIMEOUT) - self._check_received_message(recv_msg, msg) - # Some buses may receive their own messages. Remove it from the queue - self.bus1.recv(0) - - # Send with bus 2, receive with bus 1 - # Add 1 to arbitration ID to make it a different message - msg.arbitration_id += 1 - self.bus2.send(msg) - recv_msg = self.bus1.recv(TIMEOUT) - self._check_received_message(recv_msg, msg) - - def test_no_message(self): - self.assertIsNone(self.bus1.recv(0.1)) - - def test_standard_message(self): - msg = can.Message(extended_id=False, - arbitration_id=0x100, - data=[1, 2, 3, 4, 5, 6, 7, 8]) - self._send_and_receive(msg) - - def test_extended_message(self): - msg = can.Message(extended_id=True, - arbitration_id=0x123456, - data=[10, 11, 12, 13, 14, 15, 16, 17]) - self._send_and_receive(msg) - - def test_remote_message(self): - msg = can.Message(extended_id=False, - arbitration_id=0x200, - is_remote_frame=True, - dlc=4) - self._send_and_receive(msg) - - def test_dlc_less_than_eight(self): - msg = can.Message(extended_id=False, - arbitration_id=0x300, - data=[4, 5, 6]) - self._send_and_receive(msg) - - @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") - def test_fd_message(self): - msg = can.Message(is_fd=True, - extended_id=True, - arbitration_id=0x56789, - data=[0xff] * 64) - self._send_and_receive(msg) - - @unittest.skipUnless(TEST_CAN_FD, "Don't test CAN-FD") - def test_fd_message_with_brs(self): - msg = can.Message(is_fd=True, - bitrate_switch=True, - extended_id=True, - arbitration_id=0x98765, - data=[0xff] * 48) - self._send_and_receive(msg) - - if __name__ == '__main__': unittest.main() From 51e8c4fe5e9111b812e385844eac6636ebfb4cb1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Mon, 4 Jun 2018 20:39:22 +0200 Subject: [PATCH 134/168] Fix possible problems in Bus constructor (#319) * small changes to listener tests * fix duplicate constructor arguments * made all interfaces and the __new__ constructor accept the channel attribute only as a positional argument and resolved errors for handling that correctly * change channel argument in __new__ to a keyword argument --- can/bus.py | 2 +- can/interface.py | 26 +++++++++---- can/interfaces/ics_neovi/neovi_bus.py | 2 +- can/interfaces/nican.py | 3 +- can/interfaces/socketcan/socketcan_ctypes.py | 5 +-- test/back2back_test.py | 40 ++++++++++---------- test/listener_test.py | 10 ++--- 7 files changed, 48 insertions(+), 40 deletions(-) diff --git a/can/bus.py b/can/bus.py index 4207ad306..8fba41d52 100644 --- a/can/bus.py +++ b/can/bus.py @@ -28,7 +28,7 @@ class BusABC(object): channel_info = 'unknown' @abstractmethod - def __init__(self, channel=None, can_filters=None, **config): + def __init__(self, channel, can_filters=None, **config): """Construct and open a CAN bus instance of the specified type. Subclasses should call though this method with all given parameters diff --git a/can/interface.py b/can/interface.py index 6bae7c0fc..291fec268 100644 --- a/can/interface.py +++ b/can/interface.py @@ -12,6 +12,7 @@ import sys import importlib import logging +import re import can from .bus import BusABC @@ -72,14 +73,21 @@ def _get_class_for_interface(interface): class Bus(BusABC): """ - Instantiates a CAN Bus of the given `bustype`, falls back to reading a + Instantiates a CAN Bus of the given ``interface``, falls back to reading a configuration file from default locations. """ @staticmethod - def __new__(cls, *args, **config): + def __new__(cls, channel=None, *args, **config): """ - Takes the same arguments as :class:`can.BusABC.__init__` with the addition of: + Takes the same arguments as :class:`can.BusABC.__init__`. + Some might have a special meaning, see below. + + :param channel: + Set to ``None`` to let it be reloved automatically from the default + configuration. That might fail, see below. + + Expected type is backend dependent. :param dict config: Should contain an ``interface`` key with a valid interface name. If not, @@ -93,6 +101,8 @@ def __new__(cls, *args, **config): """ # figure out the rest of the configuration; this might raise an error + if channel is not None: + config['channel'] = channel config = load_config(config=config) # resolve the bus class to use for that interface @@ -101,12 +111,14 @@ def __new__(cls, *args, **config): # remove the 'interface' key so it doesn't get passed to the backend del config['interface'] - # make sure the bus can handle this config + # make sure the bus can handle this config format if 'channel' not in config: - raise ValueError("channel argument missing") + raise ValueError("'channel' argument missing") + else: + channel = config['channel'] + del config['channel'] - # the channel attribute should be present in **config - return cls(*args, **config) + return cls(channel, *args, **config) def detect_available_configs(interfaces=None): diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 18ec93e96..68eef460f 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -82,7 +82,7 @@ class NeoViBus(BusABC): https://github.com/intrepidcs/python_ics """ - def __init__(self, channel=None, can_filters=None, **config): + def __init__(self, channel, can_filters=None, **config): """ :param int channel: The Channel id to create this bus with. diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index a2364767d..b9f01159b 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -130,8 +130,7 @@ class NicanBus(BusABC): """ - def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, - **kwargs): + def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **kwargs): """ :param str channel: Name of the object to open (e.g. 'CAN0') diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py index f859a78d6..044ae8d53 100644 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ b/can/interfaces/socketcan/socketcan_ctypes.py @@ -45,10 +45,7 @@ class SocketcanCtypes_Bus(BusABC): Implements :meth:`can.BusABC._detect_available_configs`. """ - def __init__(self, - channel='vcan0', - receive_own_messages=False, - *args, **kwargs): + def __init__(self, channel, receive_own_messages=False, *args, **kwargs): """ :param str channel: The can interface name with which to create this bus. An example channel diff --git a/test/back2back_test.py b/test/back2back_test.py index 304bec690..da715de4b 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -23,9 +23,9 @@ TIMEOUT = 0.1 INTERFACE_1 = 'virtual' -CHANNEL_1 = 'vcan0' +CHANNEL_1 = 'virtual_channel_0' INTERFACE_2 = 'virtual' -CHANNEL_2 = 'vcan0' +CHANNEL_2 = 'virtual_channel_0' class Back2BackTestCase(unittest.TestCase): @@ -35,16 +35,16 @@ class Back2BackTestCase(unittest.TestCase): """ def setUp(self): - self.bus1 = can.interface.Bus(channel=CHANNEL_1, - bustype=INTERFACE_1, - bitrate=BITRATE, - fd=TEST_CAN_FD, - single_handle=True) - self.bus2 = can.interface.Bus(channel=CHANNEL_2, - bustype=INTERFACE_2, - bitrate=BITRATE, - fd=TEST_CAN_FD, - single_handle=True) + self.bus1 = can.Bus(channel=CHANNEL_1, + bustype=INTERFACE_1, + bitrate=BITRATE, + fd=TEST_CAN_FD, + single_handle=True) + self.bus2 = can.Bus(channel=CHANNEL_2, + bustype=INTERFACE_2, + bitrate=BITRATE, + fd=TEST_CAN_FD, + single_handle=True) def tearDown(self): self.bus1.shutdown() @@ -146,14 +146,14 @@ def setUp(self): print("testing python-can's socketcan version:", socketcan_version) - self.bus1 = can.interface.Bus(channel="vcan0", - bustype=socketcan_version, - bitrate=250000, - fd=TEST_CAN_FD) - self.bus2 = can.interface.Bus(channel="vcan0", - bustype=socketcan_version, - bitrate=250000, - fd=TEST_CAN_FD) + self.bus1 = can.Bus(channel="vcan0", + bustype=socketcan_version, + bitrate=250000, + fd=TEST_CAN_FD) + self.bus2 = can.Bus(channel="vcan0", + bustype=socketcan_version, + bitrate=250000, + fd=TEST_CAN_FD) def tearDown(self): self.bus1.shutdown() diff --git a/test/listener_test.py b/test/listener_test.py index 2df0ab9a8..b2a80382c 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -4,7 +4,7 @@ """ """ -from __future__ import absolute_import +from __future__ import absolute_import, print_function from time import sleep import unittest @@ -18,12 +18,12 @@ from .data.example_data import generate_message -channel = 'vcan0' +channel = 'virtual_channel_0' can.rc['interface'] = 'virtual' logging.getLogger('').setLevel(logging.DEBUG) -# make tests more reproducible +# makes the random number generator deterministic random.seed(13339115) @@ -54,7 +54,7 @@ def testClassesImportable(self): self.assertTrue(hasattr(can, 'LogReader')) - self.assertTrue(hasattr(can.io.player, 'MessageSync')) + self.assertTrue(hasattr(can, 'MessageSync')) class BusTest(unittest.TestCase): @@ -106,7 +106,7 @@ def test_filetype_to_instance(extension, klass): # test file extensions that are not supported with self.assertRaisesRegexp(NotImplementedError, "xyz_42"): test_filetype_to_instance("xyz_42", can.Printer) - with self.assertRaises(BaseException): + with self.assertRaises(Exception): test_filetype_to_instance(None, can.Printer) def testLoggerTypeResolution(self): From 6b8a9b401efb6c0127f465826c04007d83dc87e5 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 5 Jun 2018 12:21:01 +0200 Subject: [PATCH 135/168] Add support for new BLF files (#316) * Add support for v2 object headers and CAN message objects. * Warn on unknown header versions * Support CAN message extended type --- can/io/blf.py | 88 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/can/io/blf.py b/can/io/blf.py index 588f56b32..93f91f171 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -19,30 +19,40 @@ import zlib import datetime import time +import logging from can.message import Message from can.listener import Listener from can.util import len2dlc, dlc2len +class BLFParseError(Exception): + """BLF file could not be parsed correctly.""" + pass + +LOG = logging.getLogger(__name__) + # 0 = unknown, 2 = CANoe APPLICATION_ID = 5 -# Header must be 144 bytes in total # signature ("LOGG"), header size, # application ID, application major, application minor, application build, # bin log major, bin log minor, bin log build, bin log patch, # file size, uncompressed size, count of objects, count of objects read, # time start (SYSTEMTIME), time stop (SYSTEMTIME) -FILE_HEADER_STRUCT = struct.Struct("<4sLBBBBBBBBQQLL8H8H72x") +FILE_HEADER_STRUCT = struct.Struct("<4sLBBBBBBBBQQLL8H8H") -# signature ("LOBJ"), header size, header version (1), object size, object type +# Pad file header to this size +FILE_HEADER_SIZE = 144 + +# signature ("LOBJ"), header size, header version, object size, object type OBJ_HEADER_BASE_STRUCT = struct.Struct("<4sHHLL") # flags, client index, object version, timestamp -OBJ_HEADER_STRUCT = struct.Struct(" len(data): + # Calculate position of next object + next_pos = pos + obj_size + (obj_size % 4) + if next_pos > len(data): # Object continues in next log container break pos += OBJ_HEADER_BASE_STRUCT.size + # Read rest of header - header += OBJ_HEADER_STRUCT.unpack_from(data, pos) - pos += OBJ_HEADER_STRUCT.size + header_version = header[2] + if header_version == 1: + flags, _, _, timestamp = OBJ_HEADER_V1_STRUCT.unpack_from(data, pos) + pos += OBJ_HEADER_V1_STRUCT.size + elif header_version == 2: + flags, _, _, timestamp, _ = OBJ_HEADER_V2_STRUCT.unpack_from(data, pos) + pos += OBJ_HEADER_V2_STRUCT.size + else: + # Unknown header version + LOG.warning("Unknown object header version (%d)", header_version) + pos = next_pos + continue + + if flags == TIME_TEN_MICS: + factor = 10 * 1e-6 + else: + factor = 1e-9 + timestamp = timestamp * factor + self.start_timestamp obj_type = header[4] - timestamp = header[8] * 1e-9 + self.start_timestamp - - if obj_type == CAN_MESSAGE: + # Both CAN message types have the same starting content + if obj_type in (CAN_MESSAGE, CAN_MESSAGE2): (channel, flags, dlc, can_id, can_data) = CAN_MSG_STRUCT.unpack_from(data, pos) msg = Message(timestamp=timestamp, @@ -204,9 +243,7 @@ def __iter__(self): channel=channel - 1) yield msg - pos += obj_size - HEADER_SIZE - # Add padding bytes - pos += obj_size % 4 + pos = next_pos # Save remaing data that could not be processed tail = data[pos:] @@ -229,11 +266,11 @@ def __init__(self, filename, channel=1): self.fp = open(filename, "wb") self.channel = channel # Header will be written after log is done - self.fp.write(b"\x00" * FILE_HEADER_STRUCT.size) + self.fp.write(b"\x00" * FILE_HEADER_SIZE) self.cache = [] self.cache_size = 0 self.count_of_objects = 0 - self.uncompressed_size = FILE_HEADER_STRUCT.size + self.uncompressed_size = FILE_HEADER_SIZE self.start_timestamp = None self.stop_timestamp = None @@ -298,10 +335,11 @@ def _add_object(self, obj_type, data, timestamp=None): self.start_timestamp = timestamp self.stop_timestamp = timestamp timestamp = int((timestamp - self.start_timestamp) * 1e9) - obj_size = HEADER_SIZE + len(data) + header_size = OBJ_HEADER_BASE_STRUCT.size + OBJ_HEADER_V1_STRUCT.size + obj_size = header_size + len(data) base_header = OBJ_HEADER_BASE_STRUCT.pack( - b"LOBJ", HEADER_SIZE, 1, obj_size, obj_type) - obj_header = OBJ_HEADER_STRUCT.pack(2, 0, 0, max(timestamp, 0)) + b"LOBJ", header_size, 1, obj_size, obj_type) + obj_header = OBJ_HEADER_V1_STRUCT.pack(TIME_ONE_NANS, 0, 0, max(timestamp, 0)) self.cache.append(base_header) self.cache.append(obj_header) @@ -330,7 +368,7 @@ def _flush(self): self.cache_size = len(tail) compressed_data = zlib.compress(uncompressed_data, self.COMPRESSION_LEVEL) - obj_size = (OBJ_HEADER_STRUCT.size + LOG_CONTAINER_STRUCT.size + + obj_size = (OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size + len(compressed_data)) base_header = OBJ_HEADER_BASE_STRUCT.pack( b"LOBJ", OBJ_HEADER_BASE_STRUCT.size, 1, obj_size, LOG_CONTAINER) @@ -341,7 +379,7 @@ def _flush(self): self.fp.write(compressed_data) # Write padding bytes self.fp.write(b"\x00" * (obj_size % 4)) - self.uncompressed_size += OBJ_HEADER_STRUCT.size + LOG_CONTAINER_STRUCT.size + self.uncompressed_size += OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size self.uncompressed_size += len(uncompressed_data) def stop(self): @@ -353,7 +391,7 @@ def stop(self): self.fp.close() # Write header in the beginning of the file - header = [b"LOGG", FILE_HEADER_STRUCT.size, + header = [b"LOGG", FILE_HEADER_SIZE, APPLICATION_ID, 0, 0, 0, 2, 6, 8, 1] # The meaning of "count of objects read" is unknown header.extend([filesize, self.uncompressed_size, From 09754ef59e53c189931cf23a526311c871a6eea2 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Wed, 6 Jun 2018 20:16:32 +0200 Subject: [PATCH 136/168] Build wheels compatible with py2 and py3 --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..2a9acf13d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 From 78368f33af7696aa1e7b4e9bfbf1d7af5f63bd62 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 6 Jun 2018 23:39:24 +0200 Subject: [PATCH 137/168] fix proposed doc chnages --- doc/bus.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/bus.rst b/doc/bus.rst index c71662289..621baed8f 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -39,7 +39,7 @@ Receiving ''''''''' Reading from the bus is achieved by either calling the :meth:`~can.BusABC.recv` method or -by directly iterating over the bus: +by directly iterating over the bus:: for msg in bus: print(msg.data) @@ -55,7 +55,7 @@ Sending and receiving is locked seperatly to avoid unnessesary delays. It can be used exactly like the normal :class:`~can.Bus` class: - my_bus = can.Bus(interface='socketcan', channel='vcan0') + my_bus = can.ThreadSafeBus(interface='socketcan', channel='vcan0') my_bus.send(...) .. autoclass:: can.ThreadSafeBus From c82eea69e22e899019c6f01573abc47c83f6356a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 6 Jun 2018 23:44:19 +0200 Subject: [PATCH 138/168] remove setuptools dependency --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 04dab4463..34a952850 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,6 @@ # see https://www.python.org/dev/peps/pep-0345/#version-specifiers python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", install_requires=[ - 'setuptools', 'wrapt ~= 1.10', ] + (['subprocess32 ~= 3.2.7'] if version_info.major < 3 else []), extras_require={ From efa99dc3249e2b5914955191fb25abfc42d78f88 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 6 Jun 2018 23:44:37 +0200 Subject: [PATCH 139/168] debugging commit for CI server --- can/interfaces/socketcan/socketcan_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/socketcan/socketcan_common.py b/can/interfaces/socketcan/socketcan_common.py index 4e904f774..54412d04e 100644 --- a/can/interfaces/socketcan/socketcan_common.py +++ b/can/interfaces/socketcan/socketcan_common.py @@ -10,7 +10,7 @@ import errno import struct import sys -if sys.version_info[0] < 3 and os.name == 'posix': +if False: # TODO (sys.version_info[0] < 3 and os.name == 'posix'): import subprocess32 as subprocess else: import subprocess From b68afa4ea4b7ca6af17942e72ec281c7fa3da246 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 6 Jun 2018 23:53:16 +0200 Subject: [PATCH 140/168] removed the subprocess32 dependency --- can/interfaces/socketcan/socketcan_common.py | 5 +---- setup.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/can/interfaces/socketcan/socketcan_common.py b/can/interfaces/socketcan/socketcan_common.py index 54412d04e..1b3cec9e5 100644 --- a/can/interfaces/socketcan/socketcan_common.py +++ b/can/interfaces/socketcan/socketcan_common.py @@ -10,10 +10,7 @@ import errno import struct import sys -if False: # TODO (sys.version_info[0] < 3 and os.name == 'posix'): - import subprocess32 as subprocess -else: - import subprocess +import subprocess import re from can.interfaces.socketcan.socketcan_constants import CAN_EFF_FLAG diff --git a/setup.py b/setup.py index 34a952850..60fee70d4 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ python_requires=">=2.7,!=3.0,!=3.1,!=3.2,!=3.3", install_requires=[ 'wrapt ~= 1.10', - ] + (['subprocess32 ~= 3.2.7'] if version_info.major < 3 else []), + ], extras_require={ 'serial': ['pyserial >= 3.0'], 'neovi': ['python-ics >= 2.8'], From fdd5d2ae4e42b5117e6cac139fbdb1a193a4dbd2 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Wed, 6 Jun 2018 23:53:55 +0200 Subject: [PATCH 141/168] fix typo in test file name --- test/{sockectan_helpers.py => socketcan_helpers.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/{sockectan_helpers.py => socketcan_helpers.py} (96%) diff --git a/test/sockectan_helpers.py b/test/socketcan_helpers.py similarity index 96% rename from test/sockectan_helpers.py rename to test/socketcan_helpers.py index 846de8647..a8e3488d8 100644 --- a/test/sockectan_helpers.py +++ b/test/socketcan_helpers.py @@ -21,7 +21,7 @@ class TestSocketCanHelpers(unittest.TestCase): def test_error_code_to_str(self): """ Check that the function does not crash & always - returns a least one character. + returns at least one character. """ # all possible & also some invalid error codes From 976276f7c03b79b428d5c60173a17794d993dbf7 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 7 Jun 2018 00:01:42 +0200 Subject: [PATCH 142/168] debugging commit --- test/socketcan_helpers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/socketcan_helpers.py b/test/socketcan_helpers.py index a8e3488d8..8ec52d5fe 100644 --- a/test/socketcan_helpers.py +++ b/test/socketcan_helpers.py @@ -17,6 +17,9 @@ class TestSocketCanHelpers(unittest.TestCase): + def test_nothing(self): + pass # added so that this TestCase gets recognized + @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_error_code_to_str(self): """ From 06bc2a100f0dd72eadb063f6ddfa53491ddbf0a3 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Thu, 7 Jun 2018 00:11:25 +0200 Subject: [PATCH 143/168] undo last debugging commit --- test/socketcan_helpers.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/socketcan_helpers.py b/test/socketcan_helpers.py index 8ec52d5fe..a8e3488d8 100644 --- a/test/socketcan_helpers.py +++ b/test/socketcan_helpers.py @@ -17,9 +17,6 @@ class TestSocketCanHelpers(unittest.TestCase): - def test_nothing(self): - pass # added so that this TestCase gets recognized - @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_error_code_to_str(self): """ From 2809518643bde1b3512059b33168d9e8e9e05adb Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Thu, 7 Jun 2018 10:14:08 +0200 Subject: [PATCH 144/168] Add support for Kvaser config detection (#321) --- can/interfaces/kvaser/canlib.py | 12 ++++++++++++ test/test_kvaser.py | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 070038997..0e0b4b93c 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -575,6 +575,18 @@ def shutdown(self): canBusOff(self._write_handle) canClose(self._write_handle) + @staticmethod + def _detect_available_configs(): + num_channels = ctypes.c_int(0) + try: + canGetNumberOfChannels(ctypes.byref(num_channels)) + except Exception: + pass + return [ + {'interface': 'kvaser', 'channel': channel} + for channel in range(num_channels.value) + ] + def get_channel_info(channel): name = ctypes.create_string_buffer(80) diff --git a/test/test_kvaser.py b/test/test_kvaser.py index 9bf211f21..229381934 100644 --- a/test/test_kvaser.py +++ b/test/test_kvaser.py @@ -23,7 +23,7 @@ class KvaserTest(unittest.TestCase): def setUp(self): - canlib.canGetNumberOfChannels = Mock(return_value=1) + canlib.canGetNumberOfChannels = KvaserTest.canGetNumberOfChannels canlib.canOpenChannel = Mock(return_value=0) canlib.canIoCtl = Mock(return_value=0) canlib.kvReadTimer = Mock() @@ -49,7 +49,6 @@ def tearDown(self): def test_bus_creation(self): self.assertIsInstance(self.bus, canlib.KvaserBus) - self.assertTrue(canlib.canGetNumberOfChannels.called) self.assertTrue(canlib.canOpenChannel.called) self.assertTrue(canlib.canBusOn.called) @@ -152,6 +151,18 @@ def test_recv_standard(self): self.assertEqual(msg.dlc, 2) self.assertEqual(msg.id_type, False) self.assertSequenceEqual(msg.data, [100, 101]) + + def test_available_configs(self): + configs = canlib.KvaserBus._detect_available_configs() + expected = [ + {'interface': 'kvaser', 'channel': 0}, + {'interface': 'kvaser', 'channel': 1} + ] + self.assertListEqual(configs, expected) + + @staticmethod + def canGetNumberOfChannels(count): + count._obj.value = 2 def canWrite(self, handle, arb_id, buf, dlc, flags): self.msg['arb_id'] = arb_id From f3cae06614c68f4b3cc20af122adeba41effe7b1 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 8 Jun 2018 15:17:27 +0200 Subject: [PATCH 145/168] renamed socketcan helpers test --- test/{socketcan_helpers.py => test_socketcan_helpers.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{socketcan_helpers.py => test_socketcan_helpers.py} (100%) diff --git a/test/socketcan_helpers.py b/test/test_socketcan_helpers.py similarity index 100% rename from test/socketcan_helpers.py rename to test/test_socketcan_helpers.py From 26ffd06dd09913d8c5878023ec89b0f7b9b5e88b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 8 Jun 2018 15:26:07 +0200 Subject: [PATCH 146/168] documentation improvements --- doc/bus.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/bus.rst b/doc/bus.rst index 621baed8f..4aca097bb 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -6,7 +6,7 @@ Bus The :class:`~can.Bus` class, as the name suggests, provides an abstraction of a CAN bus. The bus provides an abstract wrapper around a physical or virtual CAN Bus. -A thread safe bus wrapper is also available, see "Thread safe bus". +A thread safe bus wrapper is also available, see `Thread safe bus`_. Filtering @@ -16,7 +16,6 @@ Message filtering can be set up for each bus. Where the interface supports it, t out in the hardware or kernel layer - not in Python. - API '''' @@ -35,6 +34,7 @@ Transmitting Writing to the bus is done by calling the :meth:`~can.BusABC.send()` method and passing a :class:`~can.Message` object. + Receiving ''''''''' @@ -47,16 +47,20 @@ by directly iterating over the bus:: Alternatively the :class:`~can.Listener` api can be used, which is a list of :class:`~can.Listener` subclasses that receive notifications when new messages arrive. + Thread safe bus --------------- -This thread safe version of the bus class can be used by multiple threads at once. +This thread safe version of the :class:`~can.Bus` class can be used by multiple threads at once. Sending and receiving is locked seperatly to avoid unnessesary delays. +Conflicting calls are executed by blocking until the bus is accessible. -It can be used exactly like the normal :class:`~can.Bus` class: +It can be used exactly like the normal :class:`~can.Bus`: + # 'socketcan' is only an exemple interface, it works with all the others too my_bus = can.ThreadSafeBus(interface='socketcan', channel='vcan0') my_bus.send(...) + my_bus.recv(...) .. autoclass:: can.ThreadSafeBus :members: From db0b20cd780debe73f3567c28773b5da3c3bb1d0 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 8 Jun 2018 15:27:27 +0200 Subject: [PATCH 147/168] corrected unit test --- test/test_socketcan_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index a8e3488d8..bc28806ac 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -25,7 +25,7 @@ def test_error_code_to_str(self): """ # all possible & also some invalid error codes - test_data = range(0, 256) + (-1, 256, 5235, 346264) + test_data = [range(0, 256)] + (-1, 256, 5235, 346264) for error_code in test_data: string = error_code_to_str(error_code) From 3737bb38f75bcbeae8569ac3baa9d6d020ec8c6f Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 8 Jun 2018 15:42:15 +0200 Subject: [PATCH 148/168] fix unit test #2 --- test/test_socketcan_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index bc28806ac..8cd1066d3 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -25,7 +25,7 @@ def test_error_code_to_str(self): """ # all possible & also some invalid error codes - test_data = [range(0, 256)] + (-1, 256, 5235, 346264) + test_data = [range(0, 256)] + [-1, 256, 5235, 346264] for error_code in test_data: string = error_code_to_str(error_code) From 0142bb8db905eeccbcdfdc9fad21b8111cfad48b Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 8 Jun 2018 15:52:49 +0200 Subject: [PATCH 149/168] fix unit test #3 --- test/test_socketcan_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index 8cd1066d3..f33a4e28a 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -25,7 +25,7 @@ def test_error_code_to_str(self): """ # all possible & also some invalid error codes - test_data = [range(0, 256)] + [-1, 256, 5235, 346264] + test_data = list(range(0, 256)) + [-1, 256, 5235, 346264] for error_code in test_data: string = error_code_to_str(error_code) From fbf0594b7eb37c7669ae85c0b3b4df37fd361663 Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Sat, 9 Jun 2018 12:16:23 +0200 Subject: [PATCH 150/168] Removed Python 3.3 CI testing (#322) Removed Python 3.3 CI testing --- .appveyor.yml | 10 +--------- .travis.yml | 7 ++----- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 505ea9808..56c97233d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,5 +1,4 @@ environment: - matrix: # For Python versions available on Appveyor, see @@ -14,14 +13,7 @@ environment: - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36-x64" - # officially unsupported - - PYTHON: "C:\\Python33" - - PYTHON: "C:\\Python33-x64" - -matrix: - allow_failures: - - PYTHON: "C:\\Python33" - - PYTHON: "C:\\Python33-x64" + # Python 3.3 has reached EOL install: # Prepend Python installation to PATH diff --git a/.travis.yml b/.travis.yml index 4ff088391..917e135c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: python python: # CPython: - "2.7" - - "3.3" # but allowed to fail + # Python 3.3 has reached EOL and pytest fails there - "3.4" - "3.5" - "3.6" @@ -11,7 +11,7 @@ python: - "nightly" # PyPy: - "pypy" - - "pypy3" + - "pypy3.5" os: - linux # Linux is officially supported and we test the library under @@ -43,9 +43,6 @@ matrix: # we do not allow dev builds to fail, since these builds are considered stable enough - # Python 3.3 tests are allowed to fail since Python 3.3 is not offically supported any more - - python: "3.3" - install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi - travis_retry pip install .[test] From 3e4b93cc2b27e9be9e3d02ca7159851fbbb3688d Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 12 Jun 2018 21:03:39 +0200 Subject: [PATCH 151/168] SocketCAN (native) cyclic improvements (#323) Keep periodic transmission on data update Use BCM for setting duration Fixes #298. --- can/interfaces/socketcan/socketcan_native.py | 45 ++++++++++++-------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 6198493dc..03ee1cb20 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -9,7 +9,6 @@ """ import logging -import threading import os import select @@ -143,6 +142,10 @@ def split_time(value): return build_bcm_header(opcode, flags, count, ival1_seconds, ival1_usec, ival2_seconds, ival2_usec, can_id, nframes) +def build_bcm_update_header(can_id, msg_flags): + return build_bcm_header(CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, 1) + + def dissect_can_frame(frame): can_id, can_dlc, flags = CAN_FRAME_HEADER_STRUCT.unpack_from(frame) if len(frame) != CANFD_MTU: @@ -224,13 +227,15 @@ class CyclicSendTask(SocketCanBCMBase, LimitedDurationCyclicSendTaskABC, """ - def __init__(self, channel, message, period): + def __init__(self, channel, message, period, duration=None): """ - :param channel: The name of the CAN channel to connect to. - :param message: The message to be sent periodically. - :param period: The rate in seconds at which to send the message. + :param str channel: The name of the CAN channel to connect to. + :param can.Message message: The message to be sent periodically. + :param float period: The rate in seconds at which to send the message. + :param float duration: Approximate duration in seconds to send the message. """ - super(CyclicSendTask, self).__init__(channel, message, period, duration=None) + super(CyclicSendTask, self).__init__(channel, message, period, duration) + self.duration = duration self._tx_setup(message) self.message = message @@ -238,8 +243,16 @@ def _tx_setup(self, message): # Create a low level packed frame to pass to the kernel self.can_id_with_flags = _add_flags_to_can_id(message) self.flags = CAN_FD_FRAME if message.is_fd else 0 - header = build_bcm_transmit_header(self.can_id_with_flags, 0, 0.0, - self.period, self.flags) + if self.duration: + count = int(self.duration / self.period) + ival1 = self.period + ival2 = 0 + else: + count = 0 + ival1 = 0 + ival2 = self.period + 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) @@ -259,10 +272,14 @@ def stop(self): def modify_data(self, message): """Update the contents of this periodically sent message. - Note the Message must have the same :attr:`~can.Message.arbitration_id`. + Note the Message must have the same :attr:`~can.Message.arbitration_id` + like the first message. """ assert message.arbitration_id == self.can_id, "You cannot modify the can identifier" - self._tx_setup(message) + self.message = message + header = build_bcm_update_header(self.can_id_with_flags, self.flags) + frame = build_can_frame(message) + send_bcm(self.bcm_socket, header + frame) def start(self): self._tx_setup(self.message) @@ -481,13 +498,7 @@ def send(self, msg, timeout=None): raise can.CanError("can.socketcan_native failed to transmit: {}".format(error_message)) def send_periodic(self, msg, period, duration=None): - task = CyclicSendTask(self.channel, msg, period) - - if duration is not None: - stop_timer = threading.Timer(duration, task.stop) - stop_timer.start() - - return task + return CyclicSendTask(self.channel, msg, period, duration) def _apply_filters(self, filters): try: From e6d9e76651d9b815e6eafa5fc3669fbdaa14be9d Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Wed, 13 Jun 2018 15:41:08 -0400 Subject: [PATCH 152/168] Setting _filters to None when filter Iterator is empty (#325) Changes the semantics of can_filters==[] to reflect what the buses are doing --- can/bus.py | 6 +++--- test/test_message_filtering.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/can/bus.py b/can/bus.py index b934886d4..749ef1a4e 100644 --- a/can/bus.py +++ b/can/bus.py @@ -205,8 +205,8 @@ def set_filters(self, filters=None): """Apply filtering to all messages received by this Bus. All messages that match at least one filter are returned. - If `filters` is `None`, all messages are matched. - If it is a zero size interable, no messages are matched. + If `filters` is `None` or a zero length sequence, all + messages are matched. Calling without passing any filters will reset the applied filters to `None`. @@ -223,7 +223,7 @@ def set_filters(self, filters=None): only on the arbitration ID and mask. """ - self._filters = filters + self._filters = filters or None self._apply_filters(self._filters) def _apply_filters(self, filters): diff --git a/test/test_message_filtering.py b/test/test_message_filtering.py index 278c92187..e08e6acfd 100644 --- a/test/test_message_filtering.py +++ b/test/test_message_filtering.py @@ -46,10 +46,10 @@ def test_match_all(self): self.bus.set_filters(None) self.assertTrue(self.bus._matches_filters(EXAMPLE_MSG)) - def test_match_nothing(self): + def test_match_filters_is_empty(self): self.bus.set_filters([]) for msg in TEST_ALL_MESSAGES: - self.assertFalse(self.bus._matches_filters(msg)) + self.assertTrue(self.bus._matches_filters(msg)) def test_match_example_message(self): self.bus.set_filters(MATCH_EXAMPLE) From 022cb487f583c2bbeb3105470073a604dc1edfea Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Thu, 14 Jun 2018 16:01:04 -0400 Subject: [PATCH 153/168] Adding detect_available_configs support to neovi (#327) --- can/interfaces/ics_neovi/neovi_bus.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 68eef460f..f905e8bb0 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -179,6 +179,22 @@ def shutdown(self): super(NeoViBus, self).shutdown() self.opened = False ics.close_device(self.dev) + + @staticmethod + def _detect_available_configs(): + """Detect all configurations/channels that this interface could + currently connect with. + + :rtype: Iterator[dict] + :return: an iterable of dicts, each being a configuration suitable + for usage in the interface's bus constructor. + """ + if ics is None: + return [] + # TODO: add the channel(s) + return [{ + 'serial': NeoViBus.get_serial_number(device) + } for device in ics.find_devices()] def _find_device(self, type_filter=None, serial=None): if type_filter is not None: From cc3f5f74f5f3c1f3fda055ed2ef46fa1f064944e Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Thu, 14 Jun 2018 16:05:13 -0400 Subject: [PATCH 154/168] Adding proper bitrate and timestamp support for neovi (#324) * Adding proper bitrate and timestamp support for neovi * Update the requirement for the python-ics version to 2.12 * Removing dead code --- can/interfaces/ics_neovi/neovi_bus.py | 108 +++++--------------------- setup.py | 2 +- 2 files changed, 20 insertions(+), 90 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index f905e8bb0..f192559d0 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -42,8 +42,10 @@ class ICSApiError(CanError): # An error which probably does not need attention. ICS_SPY_ERR_INFORMATION = 0x40 - def __init__(self, error_number, description_short, description_long, - severity, restart_needed): + def __init__( + self, error_number, description_short, description_long, + severity, restart_needed + ): super(ICSApiError, self).__init__(description_short) self.error_number = error_number self.description_short = description_short @@ -59,23 +61,6 @@ def is_critical(self): return self.severity == self.ICS_SPY_ERR_CRITICAL -BAUDRATE_SETTING = { - 20000: 0, - 33333: 1, - 50000: 2, - 62500: 3, - 83333: 4, - 100000: 5, - 125000: 6, - 250000: 7, - 500000: 8, - 800000: 9, - 1000000: 10, -} -VALID_BITRATES = list(BAUDRATE_SETTING.keys()) -VALID_BITRATES.sort() - - class NeoViBus(BusABC): """ The CAN Bus implemented for the python_ics interface @@ -84,6 +69,7 @@ class NeoViBus(BusABC): def __init__(self, channel, can_filters=None, **config): """ + :param int channel: The Channel id to create this bus with. :param list can_filters: @@ -102,14 +88,15 @@ def __init__(self, channel, can_filters=None, **config): if ics is None: raise ImportError('Please install python-ics') - super(NeoViBus, self).__init__(channel=channel, can_filters=can_filters, **config) + super(NeoViBus, self).__init__( + channel=channel, can_filters=can_filters, **config) logger.info("CAN Filters: {}".format(can_filters)) logger.info("Got configuration of: {}".format(config)) - self._use_system_timestamp = bool(config.get('use_system_timestamp', False)) - - # TODO: Add support for multiple channels + self._use_system_timestamp = bool( + config.get('use_system_timestamp', False) + ) try: channel = int(channel) except ValueError: @@ -120,30 +107,8 @@ def __init__(self, channel, can_filters=None, **config): self.dev = self._find_device(type_filter, serial) ics.open_device(self.dev) - bitrate = config.get('bitrate') - - # Default auto baud setting - settings = { - 'SetBaudrate': ics.AUTO, - 'Baudrate': BAUDRATE_SETTING[500000], # Default baudrate setting - 'auto_baud': 1 - } - - if bitrate is not None: - bitrate = int(bitrate) - if bitrate not in VALID_BITRATES: - raise ValueError( - 'Invalid bitrate. Valid bitrates are {}'.format( - VALID_BITRATES - ) - ) - baud_rate_setting = BAUDRATE_SETTING[bitrate] - settings = { - 'SetBaudrate': ics.AUTO, - 'Baudrate': baud_rate_setting, - 'auto_baud': 0, - } - self._set_can_settings(channel, settings) + if 'bitrate' in config: + ics.set_bit_rate(self.dev, config.get('bitrate'), channel) self.channel_info = '%s %s CH:%s' % ( self.dev.Name, @@ -153,15 +118,8 @@ def __init__(self, channel, can_filters=None, **config): logger.info("Using device: {}".format(self.channel_info)) self.rx_buffer = deque() - self.opened = True - self.network = channel if channel is not None else None - # TODO: Change the scaling based on the device type - self.ts_scaling = ( - ics.NEOVI6_VCAN_TIMESTAMP_1, ics.NEOVI6_VCAN_TIMESTAMP_2 - ) - @staticmethod def get_serial_number(device): """Decode (if needed) and return the ICS device serial string @@ -177,7 +135,6 @@ def get_serial_number(device): def shutdown(self): super(NeoViBus, self).shutdown() - self.opened = False ics.close_device(self.dev) @staticmethod @@ -217,29 +174,7 @@ def _find_device(self, type_filter=None, serial=None): raise Exception(' '.join(msg)) return dev - def _get_can_settings(self, channel): - """Return the CanSettings for channel - - :param channel: can channel number - :return: ics.CanSettings - """ - device_settings = ics.get_device_settings(self.dev) - return getattr(device_settings, 'can{}'.format(channel)) - - def _set_can_settings(self, channel, setting): - """Applies can settings to channel - - :param channel: can channel number - :param setting: settings dictionary (only the settings to update) - :return: None - """ - device_settings = ics.get_device_settings(self.dev) - channel_settings = getattr(device_settings, 'can{}'.format(channel)) - for setting, value in setting.items(): - setattr(channel_settings, setting, value) - ics.set_device_settings(self.dev, device_settings) - - def _process_msg_queue(self, timeout): + def _process_msg_queue(self, timeout=0.1): try: messages, errors = ics.get_messages(self.dev, False, timeout) except ics.RuntimeError: @@ -273,11 +208,7 @@ def _get_timestamp_for_msg(self, ics_msg): return ics_msg.TimeSystem else: # This is the hardware time stamp. - # The TimeStamp is reset to zero every time the OpenPort method is - # called. - return \ - float(ics_msg.TimeHardware2) * self.ts_scaling[1] + \ - float(ics_msg.TimeHardware) * self.ts_scaling[0] + return ics.get_timestamp_for_msg(self.dev, ics_msg) def _ics_msg_to_message(self, ics_msg): return Message( @@ -294,20 +225,19 @@ def _ics_msg_to_message(self, ics_msg): channel=ics_msg.NetworkID ) - def _recv_internal(self, timeout): + def _recv_internal(self, timeout=0.1): if not self.rx_buffer: - self._process_msg_queue(timeout) + self._process_msg_queue(timeout=timeout) try: ics_msg = self.rx_buffer.popleft() msg = self._ics_msg_to_message(ics_msg) except IndexError: return None, False - else: - return msg, False + return msg, False def send(self, msg, timeout=None): - if not self.opened: - raise CanError("bus not yet opened") + if not self.dev.IsOpen: + raise CanError("bus not open") flags = 0 if msg.is_extended_id: diff --git a/setup.py b/setup.py index 60fee70d4..68afd8538 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ ], extras_require={ 'serial': ['pyserial >= 3.0'], - 'neovi': ['python-ics >= 2.8'], + 'neovi': ['python-ics >= 2.12'], 'test': tests_require }, From 920a384a82823e33db817df6a375beb21711e22b Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 15 Jun 2018 17:21:43 -0400 Subject: [PATCH 155/168] Adding NeoVi interface information in history (#329) --- doc/history.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/history.rst b/doc/history.rst index b5d843463..dfc7ad532 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -33,6 +33,9 @@ a C++ library by Toby Lorenz. The slcan interface, ASCII listener and log logger and listener were contributed by Eduard Bröcker in 2017. +The NeoVi interface for ICS (Intrepid Control Systems) devices was contributed +by Pierre-Luc Tessier Gagné in 2017. + Support for CAN within Python ----------------------------- From b865a93558a38663cb4fd9762459642d55940c1c Mon Sep 17 00:00:00 2001 From: AntonioCohimbra <38559979+AntonioCohimbra@users.noreply.github.com> Date: Mon, 18 Jun 2018 23:31:46 +0200 Subject: [PATCH 156/168] Fix 328: Allow more than one filter for vector (#330) --- can/interfaces/vector/canlib.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 7aecd9dd6..4e4b82c84 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -180,17 +180,17 @@ def _apply_filters(self, filters): # Only up to one filter per ID type allowed if len(filters) == 1 or (len(filters) == 2 and filters[0].get("extended") != filters[1].get("extended")): - for can_filter in filters: - try: + try: + for can_filter in filters: vxlapi.xlCanSetChannelAcceptance(self.port_handle, self.mask, can_filter["can_id"], can_filter["can_mask"], vxlapi.XL_CAN_EXT if can_filter.get("extended") else vxlapi.XL_CAN_STD) - except VectorError as exc: - LOG.warning("Could not set filters: %s", exc) - # go to fallback - else: - self._is_filtered = True - return + except VectorError as exc: + LOG.warning("Could not set filters: %s", exc) + # go to fallback + else: + self._is_filtered = True + return else: LOG.warning("Only up to one filter per extended or standard ID allowed") # go to fallback From 100ebcabf16fdda41d27c1ea53ab48d81ba0bfa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Wed, 20 Jun 2018 12:29:28 -0400 Subject: [PATCH 157/168] Fix shadowing built-in name "filter" --- can/bus.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/can/bus.py b/can/bus.py index 749ef1a4e..87c362b58 100644 --- a/can/bus.py +++ b/can/bus.py @@ -253,15 +253,15 @@ def _matches_filters(self, msg): if self._filters is None: return True - for filter in self._filters: + for _filter in self._filters: # check if this filter even applies to the message - if 'extended' in filter and \ - filter['extended'] != msg.is_extended_id: + if 'extended' in _filter and \ + _filter['extended'] != msg.is_extended_id: continue # then check for the mask and id - can_id = filter['can_id'] - can_mask = filter['can_mask'] + can_id = _filter['can_id'] + can_mask = _filter['can_mask'] # basically, we compute `msg.arbitration_id & can_mask == can_id & can_mask` # by using the shorter, but equivalent from below: From dfae0bbf2d28e527d6db517ec74a1bdb8bd703c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Luc=20Tessier=20Gagn=C3=A9?= Date: Wed, 20 Jun 2018 11:21:44 -0400 Subject: [PATCH 158/168] Fixing inconsistent indentation (use of a mixture of tabs and spaces) --- can/interfaces/vector/vxlapi.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index 187dd9dd5..fbdf442e9 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -74,30 +74,30 @@ class s_xl_can_msg(ctypes.Structure): class s_xl_can_ev_error(ctypes.Structure): - _fields_ = [('errorCode', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 95)] + _fields_ = [('errorCode', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 95)] class s_xl_can_ev_chip_state(ctypes.Structure): - _fields_ = [('busStatus', ctypes.c_ubyte), ('txErrorCounter', ctypes.c_ubyte), + _fields_ = [('busStatus', ctypes.c_ubyte), ('txErrorCounter', ctypes.c_ubyte), ('rxErrorCounter', ctypes.c_ubyte),('reserved', ctypes.c_ubyte), ('reserved0', ctypes.c_uint)] class s_xl_can_ev_sync_pulse(ctypes.Structure): - _fields_ = [('triggerSource', ctypes.c_uint), ('reserved', ctypes.c_uint), + _fields_ = [('triggerSource', ctypes.c_uint), ('reserved', ctypes.c_uint), ('time', XLuint64)] - + # BASIC bus message structure class s_xl_tag_data(ctypes.Union): _fields_ = [('msg', s_xl_can_msg)] # CAN FD messages class s_xl_can_ev_rx_msg(ctypes.Structure): - _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), + _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), ('crc', ctypes.c_uint), ('reserved1', ctypes.c_ubyte * 12), - ('totalBitCnt', ctypes.c_ushort), ('dlc', ctypes.c_ubyte), + ('totalBitCnt', ctypes.c_ushort), ('dlc', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 5), ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] - + class s_xl_can_ev_tx_request(ctypes.Structure): - _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), + _fields_ = [('canId', ctypes.c_uint), ('msgFlags', ctypes.c_uint), ('dlc', ctypes.c_ubyte),('txAttemptConf', ctypes.c_ubyte), ('reserved', ctypes.c_ushort), ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] @@ -107,12 +107,12 @@ class s_xl_can_tx_msg(ctypes.Structure): ('data', ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN)] class s_rxTagData(ctypes.Union): - _fields_ = [('canRxOkMsg', s_xl_can_ev_rx_msg), ('canTxOkMsg', s_xl_can_ev_rx_msg), + _fields_ = [('canRxOkMsg', s_xl_can_ev_rx_msg), ('canTxOkMsg', s_xl_can_ev_rx_msg), ('canTxRequest', s_xl_can_ev_tx_request),('canError', s_xl_can_ev_error), ('canChipState', s_xl_can_ev_chip_state),('canSyncPulse', s_xl_can_ev_sync_pulse)] class s_txTagData(ctypes.Union): - _fields_ = [('canMsg', s_xl_can_tx_msg)] + _fields_ = [('canMsg', s_xl_can_tx_msg)] # BASIC events XLeventTag = ctypes.c_ubyte @@ -123,7 +123,7 @@ class XLevent(ctypes.Structure): ('flags', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte), ('timeStamp', XLuint64), ('tagData', s_xl_tag_data)] -# CAN FD events +# CAN FD events class XLcanRxEvent(ctypes.Structure): _fields_ = [('size',ctypes.c_int),('tag', ctypes.c_ushort), ('chanIndex', ctypes.c_ubyte),('reserved', ctypes.c_ubyte), @@ -143,7 +143,7 @@ class XLcanFdConf(ctypes.Structure): ('dataBitRate', ctypes.c_uint), ('sjwDbr', ctypes.c_uint), ('tseg1Dbr', ctypes.c_uint), ('tseg2Dbr', ctypes.c_uint), ('reserved', ctypes.c_uint * 2)] - + # driver status XLstatus = ctypes.c_short From 60391e62fdf6212e3c5cb6394b34571f15757019 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 22 Jun 2018 10:55:38 -0400 Subject: [PATCH 159/168] Fixing mismatched parameters in a docstring (#334) --- can/interfaces/socketcan/socketcan_common.py | 2 +- can/interfaces/socketcan/socketcan_native.py | 4 ++-- can/io/asc.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/can/interfaces/socketcan/socketcan_common.py b/can/interfaces/socketcan/socketcan_common.py index 1b3cec9e5..156888db6 100644 --- a/can/interfaces/socketcan/socketcan_common.py +++ b/can/interfaces/socketcan/socketcan_common.py @@ -72,7 +72,7 @@ def error_code_to_str(code): """ Converts a given error code (errno) to a useful and human readable string. - :param int error_code: a possibly invalid/unknown error code + :param int code: a possibly invalid/unknown error code :rtype: str :returns: a string explaining and containing the given error code, or a string explaining that the errorcode is unknown if that is the case diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan_native.py index 03ee1cb20..716330b77 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan_native.py @@ -172,7 +172,7 @@ def send_bcm(bcm_socket, data): """ Send raw frame to a BCM socket and handle errors. - :param socket: + :param bcm_socket: :param data: :return: """ @@ -339,7 +339,7 @@ def bind_socket(sock, channel='can0'): """ Binds the given socket to the given interface. - :param Socket socketID: + :param Socket sock: The ID of the socket to be bound :raise: :class:`OSError` if the specified interface isn't found. diff --git a/can/io/asc.py b/can/io/asc.py index 5636469f9..2dbf70622 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -148,7 +148,7 @@ def log_event(self, message, timestamp=None): """Add a message to the log file. :param str message: an arbitrary message - :param float message: the absolute timestamp of the event + :param float timestamp: the absolute timestamp of the event """ if not message: # if empty or None From 1bdfc56d37d422c30ce78decd2ec240e798c28ca Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Mon, 25 Jun 2018 11:24:35 -0400 Subject: [PATCH 160/168] Stop raising accuracy error for NeoVi (#336) Will only keep logging the error that ICS lib consider critical and no longer raise them. In most case, the error is affecting the accuracy but the device can still be use normally (i.e. Rx buffer overflow). In these cases, we do not want for example to kill a notifier using this bus, etc. --- can/interfaces/ics_neovi/neovi_bus.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index f192559d0..6b164a0b0 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -188,8 +188,6 @@ def _process_msg_queue(self, timeout=0.1): for msg in ics.get_error_messages(self.dev): error = ICSApiError(*msg) - if error.is_critical: - raise error logger.warning(error) def _get_timestamp_for_msg(self, ics_msg): From b2c03b268fc804a80529eb375e188bbb56116014 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Sun, 24 Jun 2018 20:56:30 +0200 Subject: [PATCH 161/168] Make can importable without wrapt --- can/thread_safe_bus.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index b161d47c9..f9779a32b 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -9,7 +9,14 @@ from abc import ABCMeta from threading import RLock -from wrapt import ObjectProxy +try: + # Only raise an exception on instantiation but allow module + # to be imported + from wrapt import ObjectProxy + import_exc = None +except ImportError as exc: + ObjectProxy = object + import_exc = exc from .interface import Bus from .bus import BusABC @@ -52,6 +59,9 @@ class ThreadSafeBus(ObjectProxy): _lock_recv = RLock() def __init__(self, *args, **kwargs): + if import_exc is not None: + raise import_exc + super(ThreadSafeBus, self).__init__(Bus(*args, **kwargs)) # now, BusABC.send_periodic() does not need a lock anymore, but the From c4752b2083a038bbe90dea35b47083ed0b316f49 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Mon, 25 Jun 2018 21:04:33 +0200 Subject: [PATCH 162/168] Improved channel handling (#332) Make Notifier support multiple buses Add support for channels in more formats, interfaces, and loggers --- can/interfaces/nican.py | 2 + can/interfaces/vector/canlib.py | 23 ++++++++--- can/interfaces/virtual.py | 20 ++++++++-- can/io/asc.py | 9 ++++- can/io/blf.py | 11 ++++-- can/io/canutils.py | 20 +++++----- can/message.py | 2 +- can/notifier.py | 67 ++++++++++++++++++++------------- can/util.py | 21 +++++++++++ doc/listeners.rst | 10 +++++ test/logformats_test.py | 2 + test/notifier_test.py | 39 +++++++++++++++++++ 12 files changed, 174 insertions(+), 52 deletions(-) create mode 100644 test/notifier_test.py diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index b9f01159b..a5d92373c 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -154,6 +154,7 @@ def __init__(self, channel, can_filters=None, bitrate=None, log_errors=True, **k raise ImportError("The NI-CAN driver could not be loaded. " "Check that you are using 32-bit Python on Windows.") + self.channel = channel self.channel_info = "NI-CAN: " + channel if not isinstance(channel, bytes): channel = channel.encode() @@ -242,6 +243,7 @@ def _recv_internal(self, timeout): arb_id &= 0x1FFFFFFF dlc = raw_msg.dlc msg = Message(timestamp=timestamp, + channel=self.channel, is_remote_frame=is_remote_frame, is_error_frame=is_error_frame, extended_id=is_extended, diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 4e4b82c84..3d7f6c119 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -90,6 +90,8 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, self.mask = 0 self.fd = fd # Get channels masks + self.channel_masks = {} + self.index_to_channel = {} for channel in self.channels: hw_type = ctypes.c_uint(0) hw_index = ctypes.c_uint(0) @@ -97,10 +99,13 @@ def __init__(self, channel, can_filters=None, poll_interval=0.01, vxlapi.xlGetApplConfig(self._app_name, channel, hw_type, hw_index, hw_channel, vxlapi.XL_BUS_TYPE_CAN) LOG.debug('Channel index %d found', channel) - mask = vxlapi.xlGetChannelMask(hw_type.value, hw_index.value, + idx = vxlapi.xlGetChannelIndex(hw_type.value, hw_index.value, hw_channel.value) - LOG.debug('Channel %d, Type: %d, Mask: %d', + mask = 1 << idx + LOG.debug('Channel %d, Type: %d, Mask: 0x%X', hw_channel.value, hw_type.value, mask) + self.channel_masks[channel] = mask + self.index_to_channel[idx] = channel self.mask |= mask permission_mask = vxlapi.XLaccess() @@ -225,6 +230,7 @@ def _recv_internal(self, timeout): dlc = dlc2len(event.tagData.canRxOkMsg.dlc) flags = event.tagData.canRxOkMsg.msgFlags timestamp = event.timeStamp * 1e-9 + channel = self.index_to_channel.get(event.chanIndex) msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, @@ -236,7 +242,7 @@ def _recv_internal(self, timeout): bitrate_switch=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_BRS), dlc=dlc, data=event.tagData.canRxOkMsg.data[:dlc], - channel=event.chanIndex) + channel=channel) return msg, self._is_filtered else: event_count.value = 1 @@ -251,6 +257,7 @@ def _recv_internal(self, timeout): dlc = event.tagData.msg.dlc flags = event.tagData.msg.flags timestamp = event.timeStamp * 1e-9 + channel = self.index_to_channel.get(event.chanIndex) msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, @@ -260,7 +267,7 @@ def _recv_internal(self, timeout): is_fd=False, dlc=dlc, data=event.tagData.msg.data[:dlc], - channel=event.chanIndex) + channel=channel) return msg, self._is_filtered if end_time is not None and time.time() > end_time: @@ -286,6 +293,10 @@ def send(self, msg, timeout=None): flags = 0 + # If channel has been specified, try to send only to that one. + # Otherwise send to all channels + mask = self.channel_masks.get(msg.channel, self.mask) + if self.fd: if msg.is_fd: flags |= vxlapi.XL_CAN_TXMSG_FLAG_EDL @@ -306,7 +317,7 @@ def send(self, msg, timeout=None): XLcanTxEvent.tagData.canMsg.dlc = len2dlc(msg.dlc) for idx, value in enumerate(msg.data): XLcanTxEvent.tagData.canMsg.data[idx] = value - vxlapi.xlCanTransmitEx(self.port_handle, self.mask, message_count, MsgCntSent, XLcanTxEvent) + vxlapi.xlCanTransmitEx(self.port_handle, mask, message_count, MsgCntSent, XLcanTxEvent) else: if msg.is_remote_frame: @@ -322,7 +333,7 @@ def send(self, msg, timeout=None): xl_event.tagData.msg.flags = flags for idx, value in enumerate(msg.data): xl_event.tagData.msg.data[idx] = value - vxlapi.xlCanTransmit(self.port_handle, self.mask, message_count, xl_event) + vxlapi.xlCanTransmit(self.port_handle, mask, message_count, xl_event) def flush_tx_buffer(self): diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index 32934da78..c53d0cf21 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -9,6 +9,7 @@ and reside in the same process will receive the same messages. """ +import copy import logging import time try: @@ -42,7 +43,8 @@ class VirtualBus(BusABC): behaves here. """ - def __init__(self, channel=None, receive_own_messages=False, **config): + def __init__(self, channel=None, receive_own_messages=False, + rx_queue_size=0, **config): super(VirtualBus, self).__init__(channel=channel, receive_own_messages=receive_own_messages, **config) @@ -59,7 +61,7 @@ def __init__(self, channel=None, receive_own_messages=False, **config): channels[self.channel_id] = [] self.channel = channels[self.channel_id] - self.queue = queue.Queue() + self.queue = queue.Queue(rx_queue_size) self.channel.append(self.queue) def _check_if_open(self): @@ -81,11 +83,21 @@ def _recv_internal(self, timeout): def send(self, msg, timeout=None): self._check_if_open() - msg.timestamp = time.time() + # Create a shallow copy for this channel + msg_copy = copy.copy(msg) + msg_copy.timestamp = time.time() + msg_copy.data = bytearray(msg.data) + msg_copy.channel = self.channel_id + all_sent = True # Add message to all listening on this channel for bus_queue in self.channel: if bus_queue is not self.queue or self.receive_own_messages: - bus_queue.put(msg) + try: + bus_queue.put(msg_copy, block=True, timeout=timeout) + except queue.Full: + all_sent = False + if not all_sent: + raise CanError('Could not send message to one or more recipients') def shutdown(self): self._check_if_open() diff --git a/can/io/asc.py b/can/io/asc.py index 2dbf70622..7da32d067 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -13,6 +13,7 @@ from can.listener import Listener from can.message import Message +from can.util import channel2int CAN_MSG_EXT = 0x80000000 CAN_ID_MASK = 0x1FFFFFFF @@ -197,8 +198,12 @@ def on_message_received(self, msg): if msg.is_extended_id: arb_id += 'x' - # Many interfaces start channel numbering at 0 which is invalid - channel = msg.channel + 1 if isinstance(msg.channel, int) else self.channel + channel = channel2int(msg.channel) + if channel is None: + channel = self.channel + else: + # Many interfaces start channel numbering at 0 which is invalid + channel += 1 serialized = self.FORMAT_MESSAGE.format(channel=channel, id=arb_id, diff --git a/can/io/blf.py b/can/io/blf.py index 93f91f171..8115e04e2 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -23,7 +23,7 @@ from can.message import Message from can.listener import Listener -from can.util import len2dlc, dlc2len +from can.util import len2dlc, dlc2len, channel2int class BLFParseError(Exception): @@ -275,8 +275,13 @@ def __init__(self, filename, channel=1): self.stop_timestamp = None def on_message_received(self, msg): - # Many interfaces start channel numbering at 0 which is invalid in BLF - channel = msg.channel + 1 if isinstance(msg.channel, int) else self.channel + channel = channel2int(msg.channel) + if channel is None: + channel = self.channel + else: + # Many interfaces start channel numbering at 0 which is invalid + channel += 1 + arb_id = msg.arbitration_id if msg.id_type: arb_id |= CAN_MSG_EXT diff --git a/can/io/canutils.py b/can/io/canutils.py index 682a9d2fe..564b386e1 100644 --- a/can/io/canutils.py +++ b/can/io/canutils.py @@ -5,8 +5,6 @@ This module works with CAN data in ASCII log files (*.log). It is is compatible with "candump -L" from the canutils program (https://github.com/linux-can/can-utils). - -TODO: "channel" is not uesed by CanutilsLogWriter. Is that supposed to be like that? """ import time @@ -44,9 +42,11 @@ def __iter__(self): if temp: - (timestamp, bus, frame) = temp.split() + (timestamp, channel, frame) = temp.split() timestamp = float(timestamp[1:-1]) (canId, data) = frame.split('#') + if channel.isdigit(): + channel = int(channel) if len(canId) > 3: isExtended = True @@ -73,7 +73,7 @@ def __iter__(self): else: msg = Message(timestamp=timestamp, arbitration_id=canId & 0x1FFFFFFF, extended_id=isExtended, is_remote_frame=isRemoteFrame, - dlc=dlc, data=dataBin) + dlc=dlc, data=dataBin, channel=channel) yield msg @@ -113,20 +113,22 @@ def on_message_received(self, msg): timestamp = self.last_timestamp else: timestamp = msg.timestamp + + channel = msg.channel if msg.channel is not None else self.channel if msg.is_error_frame: - self.log_file.write("(%f) vcan0 %08X#0000000000000000\n" % (timestamp, CAN_ERR_FLAG | CAN_ERR_BUSERROR)) + self.log_file.write("(%f) %s %08X#0000000000000000\n" % (timestamp, channel, CAN_ERR_FLAG | CAN_ERR_BUSERROR)) elif msg.is_remote_frame: data = [] if msg.is_extended_id: - self.log_file.write("(%f) vcan0 %08X#R\n" % (timestamp, msg.arbitration_id)) + self.log_file.write("(%f) %s %08X#R\n" % (timestamp, channel, msg.arbitration_id)) else: - self.log_file.write("(%f) vcan0 %03X#R\n" % (timestamp, msg.arbitration_id)) + self.log_file.write("(%f) %s %03X#R\n" % (timestamp, channel, msg.arbitration_id)) else: data = ["{:02X}".format(byte) for byte in msg.data] if msg.is_extended_id: - self.log_file.write("(%f) vcan0 %08X#%s\n" % (timestamp, msg.arbitration_id, ''.join(data))) + self.log_file.write("(%f) %s %08X#%s\n" % (timestamp, channel, msg.arbitration_id, ''.join(data))) else: - self.log_file.write("(%f) vcan0 %03X#%s\n" % (timestamp, msg.arbitration_id, ''.join(data))) + self.log_file.write("(%f) %s %03X#%s\n" % (timestamp, channel, msg.arbitration_id, ''.join(data))) diff --git a/can/message.py b/can/message.py index e9806c12f..9154bc0b5 100644 --- a/can/message.py +++ b/can/message.py @@ -123,7 +123,7 @@ def __repr__(self): "dlc={}".format(self.dlc), "data=[{}]".format(", ".join(data))] if self.channel is not None: - args.append("channel={}".format(self.channel)) + args.append("channel={!r}".format(self.channel)) if self.is_fd: args.append("is_fd=True") args.append("bitrate_switch={}".format(self.bitrate_switch)) diff --git a/can/notifier.py b/can/notifier.py index c3ca1118b..5e4c23e7f 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -7,63 +7,76 @@ import threading import logging +import time logger = logging.getLogger('can.Notifier') class Notifier(object): - def __init__(self, bus, listeners, timeout=None): - """Manages the distribution of **Messages** from a given bus to a + def __init__(self, bus, listeners, timeout=1): + """Manages the distribution of **Messages** from a given bus/buses to a list of listeners. - :param bus: The :ref:`bus` to listen too. - :param listeners: An iterable of :class:`~can.Listener`s - :param timeout: An optional maximum number of seconds to wait for any message. + :param can.Bus bus: The :ref:`bus` or a list of buses to listen to. + :param list listeners: An iterable of :class:`~can.Listener`s + :param float timeout: An optional maximum number of seconds to wait for any message. """ self.listeners = listeners self.bus = bus self.timeout = timeout - # exception raised in thread + #: Exception raised in thread self.exception = None - self._running = threading.Event() - self._running.set() + self._running = True + self._lock = threading.Lock() - self._reader = threading.Thread(target=self._rx_thread, - name='can.notifier for bus "{}"'.format(self.bus.channel_info)) - self._reader.daemon = True - self._reader.start() + self._readers = [] + buses = self.bus if isinstance(self.bus, list) else [self.bus] + for bus in buses: + reader = threading.Thread(target=self._rx_thread, args=(bus,), + name='can.notifier for bus "{}"'.format(bus.channel_info)) + reader.daemon = True + reader.start() + self._readers.append(reader) - def stop(self): + def stop(self, timeout=5): """Stop notifying Listeners when new :class:`~can.Message` objects arrive and call :meth:`~can.Listener.stop` on each Listener. - """ - self._running.clear() - if self.timeout is not None: - self._reader.join(self.timeout + 0.1) - def _rx_thread(self): + :param float timeout: + Max time in seconds to wait for receive threads to finish. + Should be longer than timeout given at instantiation. + """ + self._running = False + end_time = time.time() + timeout + for reader in self._readers: + now = time.time() + if now < end_time: + reader.join(end_time - now) + for listener in self.listeners: + listener.stop() + + def _rx_thread(self, bus): + msg = None try: - while self._running.is_set(): - msg = self.bus.recv(self.timeout) + while self._running: if msg is not None: - for callback in self.listeners: - callback(msg) + with self._lock: + for callback in self.listeners: + callback(msg) + msg = bus.recv(self.timeout) except Exception as exc: self.exception = exc raise - finally: - for listener in self.listeners: - listener.stop() def add_listener(self, listener): """Add new Listener to the notification list. If it is already present, it will be called two times each time a message arrives. - :param listener: a :class:`~can.Listener` object to be added to + :param can.Listener listener: Listener to be added to the list to be notified """ self.listeners.append(listener) @@ -73,7 +86,7 @@ def remove_listener(self, listener): trows an exception if the given listener is not part of the stored listeners. - :param listener: a :class:`~can.Listener` object to be removed from + :param can.Listener listener: Listener to be removed from the list to be notified :raises ValueError: if `listener` was never added to this notifier """ diff --git a/can/util.py b/can/util.py index 505bd36e4..2424af6b0 100644 --- a/can/util.py +++ b/can/util.py @@ -289,6 +289,27 @@ def dlc2len(dlc): return CAN_FD_DLC[dlc] if dlc <= 15 else 64 +def channel2int(channel): + """Try to convert the channel to an integer. + + :param channel: + Channel string (e.g. can0, CAN1) or integer + + :returns: Channel integer or `None` if unsuccessful + :rtype: int + """ + if channel is None: + return None + if isinstance(channel, int): + return channel + # String and byte objects have a lower() method + if hasattr(channel, "lower"): + match = re.match(r'.*(\d+)$', channel) + if match: + return int(match.group(1)) + return None + + if __name__ == "__main__": print("Searching for configuration named:") print("\n".join(CONFIG_FILES)) diff --git a/doc/listeners.rst b/doc/listeners.rst index af3567643..3f2b57425 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -86,6 +86,12 @@ Since no official specification exists for the format, it has been reverse- engineered from existing log files. One description of the format can be found `here `_. + +.. note:: + + Channels will be converted to integers. + + .. autoclass:: can.ASCWriter :members: @@ -124,6 +130,10 @@ CAN log format from Vector Informatik GmbH. The data is stored in a compressed format which makes it very compact. +.. note:: + + Channels will be converted to integers. + .. autoclass:: can.BLFWriter :members: diff --git a/test/logformats_test.py b/test/logformats_test.py index 095a8946d..e7d8b4bf7 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -227,11 +227,13 @@ def test_reader(self): extended_id=False, arbitration_id=0x64, data=[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])) + self.assertEqual(messages[0].channel, 0) self.assertEqual(messages[1], can.Message( is_error_frame=True, extended_id=True, arbitration_id=0x1FFFFFFF)) + self.assertEqual(messages[1].channel, 0) if __name__ == '__main__': diff --git a/test/notifier_test.py b/test/notifier_test.py new file mode 100644 index 000000000..b11a97b56 --- /dev/null +++ b/test/notifier_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# coding: utf-8 +import unittest +import time + +import can + + +class NotifierTest(unittest.TestCase): + + def test_single_bus(self): + bus = can.Bus('test', bustype='virtual', receive_own_messages=True) + reader = can.BufferedReader() + notifier = can.Notifier(bus, [reader], 0.1) + msg = can.Message() + bus.send(msg) + self.assertIsNotNone(reader.get_message(1)) + notifier.stop() + + def test_multiple_bus(self): + bus1 = can.Bus(0, bustype='virtual', receive_own_messages=True) + bus2 = can.Bus(1, bustype='virtual', receive_own_messages=True) + reader = can.BufferedReader() + notifier = can.Notifier([bus1, bus2], [reader], 0.1) + msg = can.Message() + bus1.send(msg) + time.sleep(0.1) + bus2.send(msg) + recv_msg = reader.get_message(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 0) + recv_msg = reader.get_message(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 1) + notifier.stop() + + +if __name__ == '__main__': + unittest.main() From 405ca77dc9757f3d1295c7f7a0e076d393da48be Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Mon, 25 Jun 2018 22:20:07 +0200 Subject: [PATCH 163/168] Exclude test package in distribution --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 68afd8538..a0db6fa57 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ # Code version=version, - packages=find_packages(), + packages=find_packages(exclude=["test", "test.*"]), # Author author="Brian Thorne", From 614bb04ff38879b28518d42ec9c76bbb270fe064 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Mon, 25 Jun 2018 22:21:15 +0200 Subject: [PATCH 164/168] Bump version to 2.2.0-dev --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index 3fa8c01d1..4e2099e7b 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -9,7 +9,7 @@ import logging -__version__ = "2.1.1-dev" +__version__ = "2.2.0-dev" log = logging.getLogger('can') From 66540e32157266cf1eca0cb8582c37fbf5c861c4 Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Tue, 26 Jun 2018 21:24:54 +0200 Subject: [PATCH 165/168] Merge socketcan_native and socketcan_ctypes into one (#326) Also support broadcast channel --- can/interface.py | 50 +- can/interfaces/__init__.py | 5 +- can/interfaces/socketcan/__init__.py | 4 +- .../{socketcan_constants.py => constants.py} | 1 + .../{socketcan_native.py => socketcan.py} | 318 +++++++---- can/interfaces/socketcan/socketcan_ctypes.py | 538 ------------------ .../{socketcan_common.py => utils.py} | 4 +- can/util.py | 67 +-- doc/interfaces/socketcan.rst | 67 ++- doc/interfaces/socketcan_ctypes.rst | 57 -- doc/interfaces/socketcan_native.rst | 46 -- test/back2back_test.py | 101 ++-- test/open_vcan.sh | 2 +- test/test_detect_available_configs.py | 2 +- test/test_socketcan_helpers.py | 2 +- 15 files changed, 321 insertions(+), 943 deletions(-) rename can/interfaces/socketcan/{socketcan_constants.py => constants.py} (98%) rename can/interfaces/socketcan/{socketcan_native.py => socketcan.py} (63%) delete mode 100644 can/interfaces/socketcan/socketcan_ctypes.py rename can/interfaces/socketcan/{socketcan_common.py => utils.py} (95%) delete mode 100644 doc/interfaces/socketcan_ctypes.rst delete mode 100644 doc/interfaces/socketcan_native.rst diff --git a/can/interface.py b/can/interface.py index 291fec268..9a42da73f 100644 --- a/can/interface.py +++ b/can/interface.py @@ -20,6 +20,8 @@ from .util import load_config from .interfaces import BACKENDS +from can.interfaces.socketcan.socketcan import CyclicSendTask, MultiRateCyclicSendTask + # Required by "detect_available_configs" for argument interpretation if sys.version_info.major > 2: basestring = str @@ -37,14 +39,6 @@ def _get_class_for_interface(interface): ImportError if there was a problem while importing the interface or the bus class within that """ - - # filter out the socketcan special case - if interface == 'socketcan': - try: - interface = can.util.choose_socketcan_implementation() - except Exception as e: - raise ImportError("Cannot choose socketcan implementation: {}".format(e)) - # Find the correct backend try: module_name, class_name = BACKENDS[interface] @@ -175,43 +169,3 @@ def detect_available_configs(interfaces=None): result += available return result - - -class CyclicSendTask(CyclicSendTaskABC): - - @staticmethod - def __new__(cls, channel, *args, **kwargs): - - config = load_config(config={'channel': channel}) - - # Import the correct implementation of CyclicSendTask - if config['interface'] == 'socketcan_ctypes': - from can.interfaces.socketcan.socketcan_ctypes import CyclicSendTask as _ctypesCyclicSendTask - cls = _ctypesCyclicSendTask - elif config['interface'] == 'socketcan_native': - from can.interfaces.socketcan.socketcan_native import CyclicSendTask as _nativeCyclicSendTask - cls = _nativeCyclicSendTask - else: - raise can.CanError("Current CAN interface doesn't support CyclicSendTask") - - return cls(config['channel'], *args, **kwargs) - - -class MultiRateCyclicSendTask(MultiRateCyclicSendTaskABC): - - @staticmethod - def __new__(cls, channel, *args, **kwargs): - - config = load_config(config={'channel': channel}) - - # Import the correct implementation of CyclicSendTask - if config['interface'] == 'socketcan_ctypes': - from can.interfaces.socketcan.socketcan_ctypes import MultiRateCyclicSendTask as _ctypesMultiRateCyclicSendTask - cls = _ctypesMultiRateCyclicSendTask - elif config['interface'] == 'socketcan_native': - from can.interfaces.socketcan.socketcan_native import MultiRateCyclicSendTask as _nativeMultiRateCyclicSendTask - cls = _nativeMultiRateCyclicSendTask - else: - can.log.info("Current CAN interface doesn't support CyclicSendTask") - - return cls(config['channel'], *args, **kwargs) diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 8046d4c2b..6373d671d 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -10,8 +10,7 @@ # interface_name => (module, classname) BACKENDS = { 'kvaser': ('can.interfaces.kvaser', 'KvaserBus'), - 'socketcan_ctypes': ('can.interfaces.socketcan', 'SocketcanCtypes_Bus'), - 'socketcan_native': ('can.interfaces.socketcan', 'SocketcanNative_Bus'), + 'socketcan': ('can.interfaces.socketcan', 'SocketcanBus'), 'serial': ('can.interfaces.serial.serial_can','SerialBus'), 'pcan': ('can.interfaces.pcan', 'PcanBus'), 'usb2can': ('can.interfaces.usb2can', 'Usb2canBus'), @@ -29,4 +28,4 @@ for interface in iter_entry_points('can.interface') }) -VALID_INTERFACES = frozenset(list(BACKENDS.keys()) + ['socketcan']) +VALID_INTERFACES = frozenset(list(BACKENDS.keys()) + ['socketcan_native', 'socketcan_ctypes']) diff --git a/can/interfaces/socketcan/__init__.py b/can/interfaces/socketcan/__init__.py index a861b93f3..338946136 100644 --- a/can/interfaces/socketcan/__init__.py +++ b/can/interfaces/socketcan/__init__.py @@ -5,6 +5,4 @@ See: https://www.kernel.org/doc/Documentation/networking/can.txt """ -from can.interfaces.socketcan import socketcan_constants as constants -from can.interfaces.socketcan.socketcan_ctypes import SocketcanCtypes_Bus -from can.interfaces.socketcan.socketcan_native import SocketcanNative_Bus +from can.interfaces.socketcan.socketcan import SocketcanBus, CyclicSendTask, MultiRateCyclicSendTask diff --git a/can/interfaces/socketcan/socketcan_constants.py b/can/interfaces/socketcan/constants.py similarity index 98% rename from can/interfaces/socketcan/socketcan_constants.py rename to can/interfaces/socketcan/constants.py index fb9eb3cad..dc1b85ec3 100644 --- a/can/interfaces/socketcan/socketcan_constants.py +++ b/can/interfaces/socketcan/constants.py @@ -53,6 +53,7 @@ SOCK_DGRAM = 2 AF_CAN = PF_CAN +SIOCGIFNAME = 0x8910 SIOCGIFINDEX = 0x8933 SIOCGSTAMP = 0x8906 EXTFLG = 0x0004 diff --git a/can/interfaces/socketcan/socketcan_native.py b/can/interfaces/socketcan/socketcan.py similarity index 63% rename from can/interfaces/socketcan/socketcan_native.py rename to can/interfaces/socketcan/socketcan.py index 716330b77..cc52e87f8 100644 --- a/can/interfaces/socketcan/socketcan_native.py +++ b/can/interfaces/socketcan/socketcan.py @@ -1,22 +1,17 @@ #!/usr/bin/env python # coding: utf-8 - -""" -This implementation is for versions of Python that have native -can socket and can bcm socket support. - -See :meth:`can.util.choose_socketcan_implementation()`. -""" - import logging +import ctypes +import ctypes.util import os import select import socket import struct +import time import errno -log = logging.getLogger('can.socketcan.native') +log = logging.getLogger(__name__) log_tx = log.getChild("tx") log_rx = log.getChild("rx") @@ -27,19 +22,52 @@ except ImportError: log.error("fcntl not available on this platform") -try: - socket.CAN_RAW -except: - log.error("CAN_* properties not found in socket module. These are required to use native socketcan") import can from can import Message, BusABC from can.broadcastmanager import ModifiableCyclicTaskABC, \ RestartableCyclicTaskABC, LimitedDurationCyclicSendTaskABC -from can.interfaces.socketcan.socketcan_constants import * # CAN_RAW, CAN_*_FLAG -from can.interfaces.socketcan.socketcan_common import \ +from can.interfaces.socketcan.constants import * # CAN_RAW, CAN_*_FLAG +from can.interfaces.socketcan.utils import \ pack_filters, find_available_interfaces, error_code_to_str + +try: + socket.CAN_BCM +except AttributeError: + HAS_NATIVE_SUPPORT = False +else: + HAS_NATIVE_SUPPORT = True + + +if not HAS_NATIVE_SUPPORT: + def check_status(result, function, arguments): + if result < 0: + raise can.CanError(error_code_to_str(ctypes.get_errno())) + return result + + try: + libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) + libc.bind.errcheck = check_status + libc.connect.errcheck = check_status + libc.sendto.errcheck = check_status + libc.recvfrom.errcheck = check_status + except: + log.warning("libc is unavailable") + libc = None + + def get_addr(sock, channel): + """Get sockaddr for a channel.""" + if channel: + data = struct.pack("16si", channel.encode(), 0) + res = fcntl.ioctl(sock, SIOCGIFINDEX, data) + idx, = struct.unpack("16xi", res) + else: + # All channels + idx = 0 + return struct.pack("HiLL", AF_CAN, idx, 0, 0) + + # struct module defines a binary packing format: # https://docs.python.org/3/library/struct.html#struct-format-strings # The 32bit can id is directly followed by the 8bit data link count @@ -87,7 +115,7 @@ def build_can_frame(msg): if msg.error_state_indicator: flags |= CANFD_ESI max_len = 64 if msg.is_fd else 8 - data = msg.data.ljust(max_len, b'\x00') + data = bytes(msg.data).ljust(max_len, b'\x00') return CAN_FRAME_HEADER_STRUCT.pack(can_id, msg.dlc, flags) + data @@ -156,30 +184,23 @@ def dissect_can_frame(frame): def create_bcm_socket(channel): """create a broadcast manager socket and connect to the given interface""" - try: - s = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_BCM) - except AttributeError: - raise SystemExit("To use BCM sockets you need Python3.4 or higher") - try: + s = socket.socket(PF_CAN, socket.SOCK_DGRAM, CAN_BCM) + if HAS_NATIVE_SUPPORT: s.connect((channel,)) - except OSError as e: - log.error("Couldn't connect a broadcast manager socket") - raise e + else: + addr = get_addr(s, channel) + libc.connect(s.fileno(), addr, len(addr)) return s def send_bcm(bcm_socket, data): """ Send raw frame to a BCM socket and handle errors. - - :param bcm_socket: - :param data: - :return: """ try: return bcm_socket.send(data) except OSError as e: - base = "Couldn't send CAN BCM frame. OS Error {}: {}\n".format(e.errno, os.strerror(e.errno)) + base = "Couldn't send CAN BCM frame. OS Error {}: {}\n".format(e.errno, e.strerror) if e.errno == errno.EINVAL: raise can.CanError(base + "You are probably referring to a non-existing frame.") @@ -208,15 +229,7 @@ def _add_flags_to_can_id(message): return can_id -class SocketCanBCMBase(object): - """Mixin to add a BCM socket""" - - def __init__(self, channel, *args, **kwargs): - self.bcm_socket = create_bcm_socket(channel) - super(SocketCanBCMBase, self).__init__(*args, **kwargs) - - -class CyclicSendTask(SocketCanBCMBase, LimitedDurationCyclicSendTaskABC, +class CyclicSendTask(LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC): """ A socketcan cyclic send task supports: @@ -234,12 +247,14 @@ def __init__(self, channel, message, period, duration=None): :param float period: The rate in seconds at which to send the message. :param float duration: Approximate duration in seconds to send the message. """ - super(CyclicSendTask, self).__init__(channel, message, period, duration) + super(CyclicSendTask, self).__init__(message, period, duration) + self.channel = channel self.duration = duration self._tx_setup(message) self.message = message def _tx_setup(self, message): + self.bcm_socket = create_bcm_socket(self.channel) # Create a low level packed frame to pass to the kernel self.can_id_with_flags = _add_flags_to_can_id(message) self.flags = CAN_FD_FRAME if message.is_fd else 0 @@ -268,6 +283,7 @@ def stop(self): stopframe = build_bcm_tx_delete_header(self.can_id_with_flags, self.flags) send_bcm(self.bcm_socket, stopframe) + self.bcm_socket.close() def modify_data(self, message): """Update the contents of this periodically sent message. @@ -308,27 +324,11 @@ def __init__(self, channel, message, count, initial_period, subsequent_period): send_bcm(self.bcm_socket, header + frame) -def create_socket(can_protocol=None): - """Creates a CAN socket. The socket can be BCM or RAW. The socket will +def create_socket(): + """Creates a raw CAN socket. The socket will be returned unbound to any interface. - - :param int can_protocol: - The protocol to use for the CAN socket, either: - * socket.CAN_RAW - * socket.CAN_BCM. - - :return: - * -1 if socket creation unsuccessful - * socketID - successful creation """ - if can_protocol is None or can_protocol == socket.CAN_RAW: - can_protocol = socket.CAN_RAW - socket_type = socket.SOCK_RAW - elif can_protocol == socket.CAN_BCM: - can_protocol = socket.CAN_BCM - socket_type = socket.SOCK_DGRAM - - sock = socket.socket(socket.PF_CAN, socket_type, can_protocol) + sock = socket.socket(PF_CAN, socket.SOCK_RAW, CAN_RAW) log.info('Created a socket') @@ -339,38 +339,56 @@ def bind_socket(sock, channel='can0'): """ Binds the given socket to the given interface. - :param Socket sock: - The ID of the socket to be bound - :raise: - :class:`OSError` if the specified interface isn't found. + :param socket.socket sock: + The socket to be bound + :raises OSError: + If the specified interface isn't found. """ log.debug('Binding socket to channel=%s', channel) - sock.bind((channel,)) + if HAS_NATIVE_SUPPORT: + sock.bind((channel,)) + else: + # For Python 2.7 + addr = get_addr(sock, channel) + libc.bind(sock.fileno(), addr, len(addr)) log.debug('Bound socket.') -def capture_message(sock): +def capture_message(sock, get_channel=False): """ Captures a message from given socket. - :param socket sock: + :param socket.socket sock: The socket to read a message from. + :param bool get_channel: + Find out which channel the message comes from. :return: The received message, or None on failure. """ # Fetching the Arb ID, DLC and Data try: - cf, addr = sock.recvfrom(CANFD_MTU) - except BlockingIOError: - log.debug('Captured no data, socket in non-blocking mode.') - return None - except socket.timeout: - log.debug('Captured no data, socket read timed out.') - return None - except OSError: - # something bad happened (e.g. the interface went down) - log.exception("Captured no data.") - return None + if get_channel: + if HAS_NATIVE_SUPPORT: + cf, addr = sock.recvfrom(CANFD_MTU) + channel = addr[0] if isinstance(addr, tuple) else addr + else: + data = ctypes.create_string_buffer(CANFD_MTU) + addr = ctypes.create_string_buffer(32) + addrlen = ctypes.c_int(len(addr)) + received = libc.recvfrom(sock.fileno(), data, len(data), 0, + addr, ctypes.byref(addrlen)) + cf = data.raw[:received] + # Figure out the channel name + family, ifindex = struct.unpack_from("Hi", addr.raw) + assert family == AF_CAN + data = struct.pack("16xi", ifindex) + res = fcntl.ioctl(sock, SIOCGIFNAME, data) + channel = ctypes.create_string_buffer(res).value.decode() + else: + cf = sock.recv(CANFD_MTU) + channel = None + except socket.error as exc: + raise can.CanError("Error receiving: %s" % exc) can_id, can_dlc, flags, data = dissect_can_frame(cf) log.debug('Received: can_id=%x, can_dlc=%x, data=%s', can_id, can_dlc, data) @@ -380,16 +398,16 @@ def capture_message(sock): res = fcntl.ioctl(sock, SIOCGSTAMP, struct.pack(binary_structure, 0, 0)) seconds, microseconds = struct.unpack(binary_structure, res) - timestamp = seconds + microseconds / 1000000 + timestamp = seconds + microseconds * 1e-6 # EXT, RTR, ERR flags -> boolean attributes # /* special address description flags for the CAN_ID */ # #define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */ # #define CAN_RTR_FLAG 0x40000000U /* remote transmission request */ # #define CAN_ERR_FLAG 0x20000000U /* error frame */ - is_extended_frame_format = bool(can_id & 0x80000000) - is_remote_transmission_request = bool(can_id & 0x40000000) - is_error_frame = bool(can_id & 0x20000000) + is_extended_frame_format = bool(can_id & CAN_EFF_FLAG) + is_remote_transmission_request = bool(can_id & CAN_RTR_FLAG) + is_error_frame = bool(can_id & CAN_ERR_FLAG) is_fd = len(cf) == CANFD_MTU bitrate_switch = bool(flags & CANFD_BRS) error_state_indicator = bool(flags & CANFD_ESI) @@ -403,6 +421,7 @@ def capture_message(sock): arbitration_id = can_id & 0x000007FF msg = Message(timestamp=timestamp, + channel=channel, arbitration_id=arbitration_id, extended_id=is_extended_frame_format, is_remote_frame=is_remote_transmission_request, @@ -418,16 +437,19 @@ def capture_message(sock): return msg -class SocketcanNative_Bus(BusABC): +class SocketcanBus(BusABC): """ Implements :meth:`can.BusABC._detect_available_configs`. """ - def __init__(self, channel, receive_own_messages=False, fd=False, **kwargs): + def __init__(self, channel="", receive_own_messages=False, fd=False, **kwargs): """ :param str channel: The can interface name with which to create this bus. An example channel would be 'vcan0' or 'can0'. + An empty string '' will receive messages from all channels. + In that case any sent messages must be explicitly addressed to a + channel using :attr:`can.Message.channel`. :param bool receive_own_messages: If transmitted messages should also be received by this bus. :param bool fd: @@ -435,75 +457,139 @@ def __init__(self, channel, receive_own_messages=False, fd=False, **kwargs): :param list can_filters: See :meth:`can.BusABC.set_filters`. """ - self.socket = create_socket(CAN_RAW) + self.socket = create_socket() self.channel = channel - self.channel_info = "native socketcan channel '%s'" % channel + self.channel_info = "socketcan channel '%s'" % channel # set the receive_own_messages paramater try: - self.socket.setsockopt(socket.SOL_CAN_RAW, - socket.CAN_RAW_RECV_OWN_MSGS, - struct.pack('i', receive_own_messages)) + self.socket.setsockopt(SOL_CAN_RAW, + CAN_RAW_RECV_OWN_MSGS, + 1 if receive_own_messages else 0) except socket.error as e: log.error("Could not receive own messages (%s)", e) if fd: # TODO handle errors - self.socket.setsockopt(socket.SOL_CAN_RAW, - socket.CAN_RAW_FD_FRAMES, - struct.pack('i', 1)) + self.socket.setsockopt(SOL_CAN_RAW, + CAN_RAW_FD_FRAMES, + 1) bind_socket(self.socket, channel) kwargs.update({'receive_own_messages': receive_own_messages, 'fd': fd}) - super(SocketcanNative_Bus, self).__init__(channel=channel, **kwargs) + super(SocketcanBus, self).__init__(channel=channel, **kwargs) def shutdown(self): + """Closes the socket.""" self.socket.close() def _recv_internal(self, timeout): - if timeout: - try: - # get all sockets that are ready (can be a list with a single value - # being self.socket or an empty list if self.socket is not ready) - ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout) - except OSError: - # something bad happened (e.g. the interface went down) - log.exception("Error while waiting for timeout") - ready_receive_sockets = False - else: - ready_receive_sockets = True + # get all sockets that are ready (can be a list with a single value + # being self.socket or an empty list if self.socket is not ready) + try: + # get all sockets that are ready (can be a list with a single value + # being self.socket or an empty list if self.socket is not ready) + ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout) + except socket.error as exc: + # something bad happened (e.g. the interface went down) + raise can.CanError("Failed to receive: %s" % exc) if ready_receive_sockets: # not empty or True - return capture_message(self.socket), self._is_filtered + get_channel = self.channel == "" + msg = capture_message(self.socket, get_channel) + if not msg.channel and self.channel: + # Default to our own channel + msg.channel = self.channel + return msg, self._is_filtered else: # socket wasn't readable or timeout occurred return None, self._is_filtered def send(self, msg, timeout=None): + """Transmit a message to the CAN bus. + + :param can.Message msg: A message object. + :param float timeout: + Wait up to this many seconds for the transmit queue to be ready. + If not given, the call may fail immediately. + + :raises can.CanError: + if the message could not be written. + """ log.debug("We've been asked to write a message to the bus") logger_tx = log.getChild("tx") logger_tx.debug("sending: %s", msg) - if timeout: - # Wait for write availability - _, ready_send_sockets, _ = select.select([], [self.socket], [], timeout) - if not ready_send_sockets: - raise can.CanError("Timeout while sending") + started = time.time() + # If no timeout is given, poll for availability + if timeout is None: + timeout = 0 + time_left = timeout + data = build_can_frame(msg) + while time_left >= 0: + # Wait for write availability + ready = select.select([], [self.socket], [], time_left)[1] + if not ready: + # Timeout + break + sent = self._send_once(data, msg.channel) + if sent == len(data): + return + # Not all data were sent, try again with remaining data + data = data[sent:] + time_left = timeout - (time.time() - started) + + raise can.CanError("Transmit buffer full") + + def _send_once(self, data, channel=None): try: - self.socket.sendall(build_can_frame(msg)) - except OSError as exc: - error_message = error_code_to_str(exc.errno) - raise can.CanError("can.socketcan_native failed to transmit: {}".format(error_message)) + if self.channel == "" and channel: + # Message must be addressed to a specific channel + if HAS_NATIVE_SUPPORT: + sent = self.socket.sendto(data, (channel, )) + else: + addr = get_addr(self.socket, channel) + sent = libc.sendto(self.socket.fileno(), + data, len(data), 0, + addr, len(addr)) + else: + sent = self.socket.send(data) + except socket.error as exc: + raise can.CanError("Failed to transmit: %s" % exc) + return sent def send_periodic(self, msg, period, duration=None): - return CyclicSendTask(self.channel, msg, period, duration) + """Start sending a message at a given period on this bus. + + The kernel's broadcast manager will be used. + + :param can.Message msg: + Message to transmit + :param float period: + Period in seconds between each message + :param float duration: + The duration to keep sending this message at given rate. If + no duration is provided, the task will continue indefinitely. + + :return: A started task instance + :rtype: can.interfaces.socketcan.CyclicSendTask + + .. note:: + + Note the duration before the message stops being sent may not + be exactly the same as the duration specified by the user. In + general the message will be sent at the given rate until at + least *duration* seconds. + + """ + return CyclicSendTask(msg.channel or self.channel, msg, period, duration) def _apply_filters(self, filters): try: - self.socket.setsockopt(socket.SOL_CAN_RAW, - socket.CAN_RAW_FILTER, + self.socket.setsockopt(SOL_CAN_RAW, + CAN_RAW_FILTER, pack_filters(filters)) except socket.error as err: # fall back to "software filtering" (= not in kernel) @@ -515,7 +601,7 @@ def _apply_filters(self, filters): @staticmethod def _detect_available_configs(): - return [{'interface': 'socketcan_native', 'channel': channel} + return [{'interface': 'socketcan', 'channel': channel} for channel in find_available_interfaces()] diff --git a/can/interfaces/socketcan/socketcan_ctypes.py b/can/interfaces/socketcan/socketcan_ctypes.py deleted file mode 100644 index 044ae8d53..000000000 --- a/can/interfaces/socketcan/socketcan_ctypes.py +++ /dev/null @@ -1,538 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -""" -See :meth:`can.util.choose_socketcan_implementation()`. -""" - -from __future__ import print_function, absolute_import - -import ctypes -import threading -import logging -import select -import sys -from ctypes.util import find_library - -import can -from can import Message, BusABC -from can.broadcastmanager import CyclicSendTaskABC, RestartableCyclicTaskABC, ModifiableCyclicTaskABC -from .socketcan_constants import * # CAN_RAW -from .socketcan_common import pack_filters, find_available_interfaces, error_code_to_str - -# Set up logging -log = logging.getLogger('can.socketcan.ctypes') -log.info("Loading socketcan ctypes backend") - - -if not sys.platform.startswith("win32"): - libc = ctypes.CDLL(find_library("c"), use_errno=True) - log.info("Loading libc with ctypes") -else: - log.warning("libc is unavailable") - libc = None - - -start_sec = 0 -start_usec = 0 -SEC_USEC = 1000000 - - -class SocketcanCtypes_Bus(BusABC): - """ - An implementation of the :class:`can.bus.BusABC` for SocketCAN using :mod:`ctypes`. - - Implements :meth:`can.BusABC._detect_available_configs`. - """ - - def __init__(self, channel, receive_own_messages=False, *args, **kwargs): - """ - :param str channel: - The can interface name with which to create this bus. An example channel - would be 'vcan0' or 'can0'. - """ - - self.socket = createSocket() - self.channel = channel - self.channel_info = "ctypes socketcan channel '%s'" % channel - - log.debug("Result of createSocket was %d", self.socket) - - error = bindSocket(self.socket, channel) - if error < 0: - m = u'bindSocket failed for channel {} with error {}'.format( - channel, error) - raise can.CanError(m) - - if receive_own_messages: - error1 = recv_own_msgs(self.socket) - # TODO handle potential error - - self._is_filtered = False - kwargs.update({'receive_own_messages': receive_own_messages}) - super(SocketcanCtypes_Bus, self).__init__(channel=channel, *args, **kwargs) - - def _apply_filters(self, filters): - filter_struct = pack_filters(filters) - res = libc.setsockopt(self.socket, - SOL_CAN_RAW, - CAN_RAW_FILTER, - filter_struct, - len(filter_struct)) - if res != 0: - # fall back to "software filtering" (= not in kernel) - self._is_filtered = False - # TODO Is this serious enough to raise a CanError exception? - # TODO print error code (the errno, not "res", which is always -1) - log.error('Setting filters failed: falling back to software filtering (not in kernel)') - else: - self._is_filtered = True - - def _recv_internal(self, timeout): - - log.debug("Trying to read a msg") - - ready_write_sockets, _, _ = select.select([self.socket], [], [], timeout) - if not ready_write_sockets: - # socket wasn't readable or timeout occurred - return None, self._is_filtered - - packet = capturePacket(self.socket) - - log.debug("Receiving a message") - - arbitration_id = packet['CAN ID'] & MSK_ARBID - - # Flags: EXT, RTR, ERR - flags = (packet['CAN ID'] & MSK_FLAGS) >> 29 - - rx_msg = Message( - timestamp=packet['Timestamp'], - is_remote_frame=bool(flags & SKT_RTRFLG), - extended_id=bool(flags & EXTFLG), - is_error_frame=bool(flags & SKT_ERRFLG), - arbitration_id=arbitration_id, - dlc=packet['DLC'], - data=packet['Data'] - ) - - return rx_msg, self._is_filtered - - def send(self, msg, timeout=None): - frame = _build_can_frame(msg) - - # Wait for write availability. write will fail below on timeout - _, ready_send_sockets, _ = select.select([], [self.socket], [], timeout) - if not ready_send_sockets: - raise can.CanError("Timeout while sending") - - # all sizes & lengths are in bytes - total_sent = 0 - remaining = ctypes.sizeof(frame) - while remaining > 0: - # this might not send the entire frame - # see: http://man7.org/linux/man-pages/man2/write.2.html - bytes_sent = libc.write(self.socket, ctypes.byref(frame, total_sent), remaining) - - if bytes_sent == 0: - raise can.CanError("Transmit buffer overflow") - elif bytes_sent == -1: - error_message = error_code_to_str(ctypes.get_errno()) - raise can.CanError("can.socketcan_ctypes failed to transmit: {}".format(error_message)) - - total_sent += bytes_sent - remaining -= bytes_sent - - log.debug("Frame transmitted with %s bytes", bytes_sent) - - def send_periodic(self, msg, period, duration=None): - task = CyclicSendTask(self.channel, msg, period) - - if duration is not None: - threading.Timer(duration, task.stop).start() - - return task - - @staticmethod - def _detect_available_configs(): - return [{'interface': 'socketcan_ctypes', 'channel': channel} - for channel in find_available_interfaces()] - - -class SOCKADDR(ctypes.Structure): - # See /usr/include/i386-linux-gnu/bits/socket.h for original struct - _fields_ = [("sa_family", ctypes.c_uint16), - ("sa_data", (ctypes.c_char)*14)] - - -class TP(ctypes.Structure): - # This struct is only used within the SOCKADDR_CAN struct - _fields_ = [("rx_id", ctypes.c_uint32), - ("tx_id", ctypes.c_uint32)] - - -class ADDR_INFO(ctypes.Union): - # This struct is only used within the SOCKADDR_CAN struct - # This union is to future proof for future can address information - _fields_ = [("tp", TP)] - - -class SOCKADDR_CAN(ctypes.Structure): - # See /usr/include/linux/can.h for original struct - _fields_ = [("can_family", ctypes.c_uint16), - ("can_ifindex", ctypes.c_int), - ("can_addr", ADDR_INFO)] - - -class IFREQ(ctypes.Structure): - # The two fields in this struct were originally unions. - # See /usr/include/net/if.h for original struct - _fields_ = [("ifr_name", ctypes.c_char*16), - ("ifr_ifindex", ctypes.c_int)] - - -class CAN_FRAME(ctypes.Structure): - # See /usr/include/linux/can.h for original struct - # The 32bit can id is directly followed by the 8bit data link count - # The data field is aligned on an 8 byte boundary, hence the padding. - # Aligns the data field to an 8 byte boundary - _fields_ = [("can_id", ctypes.c_uint32), - ("can_dlc", ctypes.c_uint8), - ("padding", ctypes.c_ubyte * 3), - ("data", ctypes.c_uint8 * 8) - ] - - -class TIME_VALUE(ctypes.Structure): - # See usr/include/linux/time.h for original struct - _fields_ = [("tv_sec", ctypes.c_ulong), - ("tv_usec", ctypes.c_ulong)] - - -class BCM_HEADER(ctypes.Structure): - # See usr/include/linux/can/bcm.h for original struct - _fields_ = [ - ("opcode", ctypes.c_uint32), - ("flags", ctypes.c_uint32), - ("count", ctypes.c_uint32), - ("ival1", TIME_VALUE), - ("ival2", TIME_VALUE), - ("can_id", ctypes.c_uint32), - ("nframes", ctypes.c_uint32), - ("frames", CAN_FRAME) - ] - - -def createSocket(protocol=CAN_RAW): - """ - This function creates a RAW CAN socket. - - The socket returned needs to be bound to an interface by calling - :func:`bindSocket`. - - :param int protocol: - The type of the socket to be bound. Valid values - include CAN_RAW and CAN_BCM - - :return: - +-----------+----------------------------+ - | 0 |protocol invalid | - +-----------+----------------------------+ - | -1 |socket creation unsuccessful| - +-----------+----------------------------+ - | socketID | successful creation | - +-----------+----------------------------+ - """ - if protocol == CAN_RAW: - socketID = libc.socket(PF_CAN, SOCK_RAW, CAN_RAW) - elif protocol == CAN_BCM: - socketID = libc.socket(PF_CAN, SOCK_DGRAM, CAN_BCM) - else: - socketID = -1 - - return socketID - - -def bindSocket(socketID, channel_name): - """ - Binds the given socket to the given interface. - - :param int socketID: - The ID of the socket to be bound - - :param str channel_name: - The interface name to find and bind. - - :return: - The error code from the bind call. - - +----+----------------------------+ - | 0 |protocol invalid | - +----+----------------------------+ - | -1 |socket creation unsuccessful| - +----+----------------------------+ - """ - log.debug('Binding socket with id %d to channel %s', socketID, channel_name) - socketID = ctypes.c_int(socketID) - - ifr = IFREQ() - ifr.ifr_name = channel_name.encode('ascii') - log.debug('calling ioctl SIOCGIFINDEX') - # ifr.ifr_ifindex gets filled with that device's index - ret = libc.ioctl(socketID, SIOCGIFINDEX, ctypes.byref(ifr)) - if ret < 0: - m = u'Failure while getting "{}" interface index.'.format(channel_name) - raise can.CanError(m) - log.info('ifr.ifr_ifindex: %d', ifr.ifr_ifindex) - - # select the CAN interface and bind the socket to it - addr = SOCKADDR_CAN(AF_CAN, ifr.ifr_ifindex) - - error = libc.bind(socketID, ctypes.byref(addr), ctypes.sizeof(addr)) - - if error < 0: - log.error("Couldn't bind socket") - log.debug('bind returned: %d', error) - - return error - - -def connectSocket(socketID, channel_name): - """Connects the given socket to the given interface. - - :param int socketID: - The ID of the socket to be bound - - :param str channel_name: - The interface name to find and bind. - - :return: - The error code from the bind call. - """ - log.debug('Connecting socket with id %d to channel %s', socketID, channel_name) - socketID = ctypes.c_int(socketID) - - ifr = IFREQ() - ifr.ifr_name = channel_name.encode('ascii') - log.debug('calling ioctl SIOCGIFINDEX') - # ifr.ifr_ifindex gets filled with that device's index - libc.ioctl(socketID, SIOCGIFINDEX, ctypes.byref(ifr)) - log.info('ifr.ifr_ifindex: %d', ifr.ifr_ifindex) - - # select the CAN interface and bind the socket to it - addr = SOCKADDR_CAN(AF_CAN, ifr.ifr_ifindex) - - error = libc.connect(socketID, ctypes.byref(addr), ctypes.sizeof(addr)) - - if error < 0: - log.error("Couldn't connect socket") - log.debug('connect returned: %d', error) - - return error - - -def recv_own_msgs(socket_id): - setting = ctypes.c_int(1) - error = libc.setsockopt(socket_id, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, ctypes.byref(setting), ctypes.sizeof(setting)) - - if error < 0: - log.error("Couldn't set recv own msgs") - - return error - - -def _build_can_frame(message): - log.debug("Packing a can frame") - arbitration_id = message.arbitration_id - if message.id_type: - log.debug("sending an extended id type message") - arbitration_id |= 0x80000000 - if message.is_remote_frame: - log.debug("requesting a remote frame") - arbitration_id |= 0x40000000 - if message.is_error_frame: - log.debug("sending error frame") - arbitration_id |= 0x20000000 - log.debug("Data: %s", message.data) - log.debug("Type: %s", type(message.data)) - - # TODO need to understand the extended frame format - frame = CAN_FRAME() - frame.can_id = arbitration_id - frame.can_dlc = message.dlc - - frame.data[0:len(message.data)] = message.data - - log.debug("sizeof frame: %d", ctypes.sizeof(frame)) - return frame - - -def capturePacket(socketID): - """ - Captures a packet of data from the given socket. - - :param int socketID: - The socket to read from - - :return: - A dictionary with the following keys: - - - `"CAN ID"` (int) - - `"DLC"` (int) - - `"Data"` (list) - - `"Timestamp"` (float) - - """ - packet = {} - - frame = CAN_FRAME() - time = TIME_VALUE() - - # Fetching the Arb ID, DLC and Data - bytes_read = libc.read(socketID, ctypes.byref(frame), ctypes.sizeof(frame)) - - # Fetching the timestamp - error = libc.ioctl(socketID, SIOCGSTAMP, ctypes.byref(time)) - - packet['CAN ID'] = frame.can_id - packet['DLC'] = frame.can_dlc - packet["Data"] = [frame.data[i] for i in range(frame.can_dlc)] - - timestamp = time.tv_sec + (time.tv_usec / 1000000.0) - - packet['Timestamp'] = timestamp - - return packet - - -def _create_bcm_frame(opcode, flags, count, ival1_seconds, ival1_usec, ival2_seconds, ival2_usec, can_id, nframes, msg_frame): - - frame = BCM_HEADER() - frame.opcode = opcode - frame.flags = flags - - frame.count = count - frame.ival1.tv_sec = ival1_seconds - frame.ival1.tv_usec = ival1_usec - - frame.ival2.tv_sec = ival2_seconds - frame.ival2.tv_usec = ival2_usec - frame.can_id = can_id - frame.nframes = nframes - - frame.frames = msg_frame - - return frame - - -class SocketCanCtypesBCMBase(object): - """Mixin to add a BCM socket""" - - def __init__(self, channel, *args, **kwargs): - log.debug("Creating bcm socket on channel '%s'", channel) - # Set up the bcm socket using ctypes - self.bcm_socket = createSocket(protocol=CAN_BCM) - log.debug("Created bcm socket (un-connected fd=%d)", self.bcm_socket) - connectSocket(self.bcm_socket, channel) - log.debug("Connected bcm socket") - super(SocketCanCtypesBCMBase, self).__init__(*args, **kwargs) - - -class CyclicSendTask(SocketCanCtypesBCMBase, RestartableCyclicTaskABC, ModifiableCyclicTaskABC): - - def __init__(self, channel, message, period): - """ - - :param channel: The name of the CAN channel to connect to. - :param message: The message to be sent periodically. - :param period: The rate in seconds at which to send the message. - """ - super(CyclicSendTask, self).__init__(channel, message, period) - self.message = message - # Send the bcm message with opcode TX_SETUP to start the cyclic transmit - self._tx_setup() - - def _tx_setup(self): - message = self.message - # Create a low level packed frame to pass to the kernel - msg_frame = _build_can_frame(message) - frame = _create_bcm_frame(opcode=CAN_BCM_TX_SETUP, - flags=SETTIMER | STARTTIMER, - count=0, - ival1_seconds=0, - ival1_usec=0, - ival2_seconds=int(self.period), - ival2_usec=int(1e6 * (self.period - int(self.period))), - can_id=message.arbitration_id, - nframes=1, - msg_frame=msg_frame) - - log.info("Sending BCM TX_SETUP command") - bytes_sent = libc.send(self.bcm_socket, ctypes.byref(frame), ctypes.sizeof(frame)) - if bytes_sent == -1: - log.debug("Error sending frame :-/") - - def start(self): - self._tx_setup() - - def stop(self): - """Send a TX_DELETE message to cancel this task. - - This will delete the entry for the transmission of the CAN-message - with the specified can_id CAN identifier. The message length for the command - TX_DELETE is {[bcm_msg_head]} (only the header). - """ - - frame = _create_bcm_frame( - opcode=CAN_BCM_TX_DELETE, - flags=0, - count=0, - ival1_seconds=0, - ival1_usec=0, - ival2_seconds=0, - ival2_usec=0, - can_id=self.can_id, - nframes=0, - msg_frame=CAN_FRAME() - ) - - bytes_sent = libc.send(self.bcm_socket, ctypes.byref(frame), ctypes.sizeof(frame)) - if bytes_sent == -1: - log.debug("Error sending frame to stop cyclic message:-/") - - def modify_data(self, message): - """Update the contents of this periodically sent message. - """ - assert message.arbitration_id == self.can_id, "You cannot modify the can identifier" - self.message = message - self._tx_setup() - - -class MultiRateCyclicSendTask(CyclicSendTask): - - """Exposes more of the full power of the TX_SETUP opcode. - - Transmits a message `count` times at `initial_period` then - continues to transmit message at `subsequent_period`. - """ - - def __init__(self, channel, message, count, initial_period, subsequent_period): - super(MultiRateCyclicSendTask, self).__init__(channel, message, subsequent_period) - - msg_frame = _build_can_frame(message) - - frame = _create_bcm_frame(opcode=CAN_BCM_TX_SETUP, - flags=SETTIMER | STARTTIMER, - count=count, - ival1_seconds=int(initial_period), - ival1_usec=int(1e6 * (initial_period - int(initial_period))), - ival2_seconds=int(subsequent_period), - ival2_usec=int(1e6 * (subsequent_period - int(subsequent_period))), - can_id=message.arbitration_id, - nframes=1, - msg_frame=msg_frame) - - log.info("Sending BCM TX_SETUP command") - bytes_sent = libc.send(self.bcm_socket, ctypes.byref(frame), ctypes.sizeof(frame)) - if bytes_sent == -1: - log.debug("Error sending frame :-/") diff --git a/can/interfaces/socketcan/socketcan_common.py b/can/interfaces/socketcan/utils.py similarity index 95% rename from can/interfaces/socketcan/socketcan_common.py rename to can/interfaces/socketcan/utils.py index 156888db6..ef522e408 100644 --- a/can/interfaces/socketcan/socketcan_common.py +++ b/can/interfaces/socketcan/utils.py @@ -13,9 +13,9 @@ import subprocess import re -from can.interfaces.socketcan.socketcan_constants import CAN_EFF_FLAG +from can.interfaces.socketcan.constants import CAN_EFF_FLAG -log = logging.getLogger('can.socketcan_common') +log = logging.getLogger(__name__) def pack_filters(can_filters=None): if can_filters is None: diff --git a/can/util.py b/can/util.py index 2424af6b0..a471fda89 100644 --- a/can/util.py +++ b/can/util.py @@ -116,11 +116,6 @@ def load_config(path=None, config=None): Interface can be any of the strings from ``can.VALID_INTERFACES`` for example: kvaser, socketcan, pcan, usb2can, ixxat, nican, virtual. - .. note:: - - If you pass ``"socketcan"`` this automatically selects between the - native and ctypes version. - .. note:: The key ``bustype`` is copied to ``interface`` if that one is missing @@ -182,9 +177,12 @@ def load_config(path=None, config=None): if key not in config: config[key] = None - # this is done later too but better safe than sorry - if config['interface'] == 'socketcan': - config['interface'] = choose_socketcan_implementation() + # deprecated socketcan types + if config['interface'] in ('socketcan_native', 'socketcan_ctypes'): + # Change this to a DeprecationWarning in future 2.x releases + # Remove completely in 3.0 + log.warning('%s is deprecated, use socketcan instead', config['interface']) + config['interface'] = 'socketcan' if config['interface'] not in VALID_INTERFACES: raise NotImplementedError('Invalid CAN Bus Type - {}'.format(config['interface'])) @@ -196,59 +194,6 @@ def load_config(path=None, config=None): return config -def choose_socketcan_implementation(): - """Set the best version of the SocketCAN module for this system. - - :rtype: str - :return: - either 'socketcan_ctypes' or 'socketcan_native', - depending on the current platform and environment - :raises Exception: If the system doesn't support SocketCAN at all - """ - - # Check OS: SocketCAN is available only under Linux - if not sys.platform.startswith('linux'): - msg = 'SocketCAN not available under {}'.format(sys.platform) - raise Exception(msg) - - # Check release: SocketCAN was added to Linux 2.6.25 - rel_string = platform.release() - m = re.match(r'\d+\.\d+\.\d', rel_string) - if not m: # None or empty - msg = 'Bad linux release {}'.format(rel_string) - raise Exception(msg) - rel_num = [int(i) for i in rel_string[:m.end()].split('.')] - - if (rel_num < [2, 6, 25]): - msg = 'SocketCAN not available under Linux {}'.format(rel_string) - raise Exception(msg) - - # Check Python version: - # - # CPython: - # Support for SocketCAN was added in Python 3.3, but support for - # CAN FD frames (with socket.CAN_RAW_FD_FRAMES) was just added - # to Python in version 3.5. - # So we want to use socketcan_native only on Python >= 3.5 (see #274). - # - # PyPy: - # Furthermore, socket.CAN_* is not supported by PyPy 2 or 3 (as of - # April 2018) at all. Thus we want to use socketcan_ctypes there as well. - # - # General approach: - # To support possible future versions of current platforms as well as - # potential other ones, we take the approach of feature checking instead - # of platform/version checking. - - try: - # try to import typical attributes - from socket import CAN_RAW, CAN_BCM, CAN_RAW_FD_FRAMES - except ImportError: - return 'socketcan_ctypes' - else: - return 'socketcan_native' - - def set_logging_level(level_name=None): """Set the logging level for the "can" logger. Expects one of: 'critical', 'error', 'warning', 'info', 'debug', 'subdebug' diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 376d54b4d..099c3e90e 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -1,25 +1,21 @@ -Socketcan +SocketCAN ========= -There are two implementations of socketcan backends. One written with :mod:`ctypes` to be compatible -with Python 2 and 3, and one written for future versions of Python3 which feature native support. - +The full documentation for socketcan can be found in the kernel docs at +`networking/can.txt `_. -.. toctree:: - :maxdepth: 2 - socketcan_ctypes - socketcan_native +.. note:: + Versions before 2.2 had two different implementations named + ``socketcan_ctypes`` and ``socketcan_native``. These are now + deprecated and the aliases to ``socketcan`` will be removed in + version 3.0. Future 2.x release may raise a DeprecationWarning. -Unless you're running Python3.3 or lower the recommended backend is :doc:`socketcan_native `. -For Python2.7 and Python3 <3.4, the available backend is :doc:`socketcan_ctypes `. Socketcan Quickstart -------------------- -The full documentation for socketcan can be found in the kernel docs at -`networking/can.txt `_. The CAN network driver provides a generic interface to setup, configure and monitor CAN devices. To configure bit-timing parameters use the program ``ip``. @@ -124,7 +120,7 @@ To spam a bus: import time import can - bustype = 'socketcan_native' + bustype = 'socketcan' channel = 'vcan0' def producer(id): @@ -162,7 +158,7 @@ function: import can can_interface = 'vcan0' - bus = can.interface.Bus(can_interface, bustype='socketcan_native') + bus = can.interface.Bus(can_interface, bustype='socketcan') message = bus.recv() By default, this performs a blocking read, which means ``bus.recv()`` won't @@ -179,3 +175,46 @@ blocking read with a timeout like this: If you set the timeout to ``0.0``, the read will be executed as non-blocking, which means ``bus.recv(0.0)`` will return immediately, either with a ``Message`` object or ``None``, depending on whether data was available on the socket. + +Filtering +--------- + +The implementation features efficient filtering of can_id's. That filtering +occurs in the kernel and is much much more efficient than filtering messages +in Python. + +Broadcast Manager +----------------- + +The ``socketcan`` interface implements thin wrappers to the linux `broadcast manager` +socket api. This allows the cyclic transmission of CAN messages at given intervals. +The overhead for periodic message sending is extremely low as all the heavy lifting occurs +within the linux kernel. + +send_periodic() +~~~~~~~~~~~~~~~ + +An example that uses the send_periodic is included in ``python-can/examples/cyclic.py`` + +The object returned can be used to halt, alter or cancel the periodic message task. + +.. autoclass:: can.interfaces.socketcan.CyclicSendTask + + +Bus +--- + +.. autoclass:: can.interfaces.socketcan.SocketcanBus + + .. method:: recv(timeout=None) + + Block waiting for a message from the Bus. + + :param float timeout: + seconds to wait for a message or None to wait indefinitely + + :rtype: can.Message or None + :return: + None on timeout or a :class:`can.Message` object. + :raises can.CanError: + if an error occurred while reading diff --git a/doc/interfaces/socketcan_ctypes.rst b/doc/interfaces/socketcan_ctypes.rst deleted file mode 100644 index 7a83413cb..000000000 --- a/doc/interfaces/socketcan_ctypes.rst +++ /dev/null @@ -1,57 +0,0 @@ -SocketCAN (ctypes) -================== - -`socketcan_ctypes.py` is a ctypes wrapper class around libc. It contains replications -of constants and structures found in various linux header files. With -Python 3.3, much of the functionality of this library is likely to be -available natively in the Python socket module. - - - -Bus ----- - -.. autoclass:: can.interfaces.socketcan.SocketcanCtypes_Bus - - - -Broadcast-Manager ------------------ - -The ``socketcan_ctypes`` interface implements thin wrappers to the linux `broadcast manager` -socket api. This allows the cyclic transmission of CAN messages at given intervals. -The overhead for periodic message sending is extremely low as all the heavy lifting occurs -within the linux kernel. - -send_periodic() -~~~~~~~~~~~~~~~ - -An example that uses the send_periodic is included in ``python-can/examples/cyclic.py`` - -The object returned can be used to halt, alter or cancel the periodic message task. - -.. autoclass:: can.interfaces.socketcan.socketcan_ctypes.CyclicSendTask - - -Internals ---------- - -createSocket -~~~~~~~~~~~~ - -.. autofunction:: can.interfaces.socketcan.socketcan_ctypes.createSocket - - -bindSocket -~~~~~~~~~~ - -.. autofunction:: can.interfaces.socketcan.socketcan_ctypes.bindSocket - -connectSocket - -.. autofunction:: can.interfaces.socketcan.socketcan_ctypes.connectSocket - -capturePacket -~~~~~~~~~~~~~ - -.. autofunction:: can.interfaces.socketcan.socketcan_ctypes.capturePacket diff --git a/doc/interfaces/socketcan_native.rst b/doc/interfaces/socketcan_native.rst deleted file mode 100644 index cb6f9aea4..000000000 --- a/doc/interfaces/socketcan_native.rst +++ /dev/null @@ -1,46 +0,0 @@ -SocketCAN (python) -================== - -Python 3.3 added support for socketcan for linux systems. - -The ``socketcan_native`` interface directly uses Python's socket module to -access SocketCAN on linux. This is the most direct route to the kernel -and should provide the most responsive one. - -The implementation features efficient filtering of can_id's. That filtering -occurs in the kernel and is much much more efficient than filtering messages -in Python. - -Python 3.4 added support for the Broadcast Connection Manager (BCM) -protocol, which - if enabled - should be used for queueing periodic tasks. - -Documentation for the socketcan back end file can be found: - -https://www.kernel.org/doc/Documentation/networking/can.txt - - -Bus ---- - -.. autoclass:: can.interfaces.socketcan.SocketcanNative_Bus - - -Internals ---------- - -create_socket -~~~~~~~~~~~~~ - -.. autofunction:: can.interfaces.socketcan.socketcan_native.create_socket - - -bind_socket -~~~~~~~~~~~ - -.. autofunction:: can.interfaces.socketcan.socketcan_native.bind_socket - - -capture_message -~~~~~~~~~~~~~~~ - -.. autofunction:: can.interfaces.socketcan.socketcan_native.capture_message diff --git a/test/back2back_test.py b/test/back2back_test.py index 62f362feb..a93855dd2 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -20,30 +20,29 @@ from .data.example_data import generate_message -BITRATE = 500000 -TIMEOUT = 0.1 - -INTERFACE_1 = 'virtual' -CHANNEL_1 = 'virtual_channel_0' -INTERFACE_2 = 'virtual' -CHANNEL_2 = 'virtual_channel_0' - - class Back2BackTestCase(unittest.TestCase): """ Use two interfaces connected to the same CAN bus and test them against each other. """ + BITRATE = 500000 + TIMEOUT = 0.1 + + INTERFACE_1 = 'virtual' + CHANNEL_1 = 'virtual_channel_0' + INTERFACE_2 = 'virtual' + CHANNEL_2 = 'virtual_channel_0' + def setUp(self): - self.bus1 = can.Bus(channel=CHANNEL_1, - bustype=INTERFACE_1, - bitrate=BITRATE, + self.bus1 = can.Bus(channel=self.CHANNEL_1, + bustype=self.INTERFACE_1, + bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True) - self.bus2 = can.Bus(channel=CHANNEL_2, - bustype=INTERFACE_2, - bitrate=BITRATE, + self.bus2 = can.Bus(channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True) @@ -53,7 +52,7 @@ def tearDown(self): def _check_received_message(self, recv_msg, sent_msg): self.assertIsNotNone(recv_msg, - "No message was received on %s" % INTERFACE_2) + "No message was received on %s" % self.INTERFACE_2) self.assertEqual(recv_msg.arbitration_id, sent_msg.arbitration_id) self.assertEqual(recv_msg.id_type, sent_msg.id_type) self.assertEqual(recv_msg.is_remote_frame, sent_msg.is_remote_frame) @@ -67,7 +66,7 @@ def _check_received_message(self, recv_msg, sent_msg): def _send_and_receive(self, msg): # Send with bus 1, receive with bus 2 self.bus1.send(msg) - recv_msg = self.bus2.recv(TIMEOUT) + recv_msg = self.bus2.recv(self.TIMEOUT) self._check_received_message(recv_msg, msg) # Some buses may receive their own messages. Remove it from the queue self.bus1.recv(0) @@ -76,7 +75,7 @@ def _send_and_receive(self, msg): # Add 1 to arbitration ID to make it a different message msg.arbitration_id += 1 self.bus2.send(msg) - recv_msg = self.bus1.recv(TIMEOUT) + recv_msg = self.bus1.recv(self.TIMEOUT) self._check_received_message(recv_msg, msg) def test_no_message(self): @@ -85,10 +84,10 @@ def test_no_message(self): @unittest.skipIf(IS_CI, "the timing sensitive behaviour cannot be reproduced reliably on a CI server") def test_timestamp(self): self.bus2.send(can.Message()) - recv_msg1 = self.bus1.recv(TIMEOUT) + recv_msg1 = self.bus1.recv(self.TIMEOUT) sleep(2.0) self.bus2.send(can.Message()) - recv_msg2 = self.bus1.recv(TIMEOUT) + recv_msg2 = self.bus1.recv(self.TIMEOUT) delta_time = recv_msg2.timestamp - recv_msg1.timestamp self.assertTrue(1.75 <= delta_time <= 2.25, 'Time difference should have been 2s +/- 250ms.' @@ -136,39 +135,37 @@ def test_fd_message_with_brs(self): data=[0xff] * 48) self._send_and_receive(msg) + @unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") -class BasicTestSocketCan(unittest.TestCase): - """ - TODO Test more thoroughly. See #273. - """ +class BasicTestSocketCan(Back2BackTestCase): + + INTERFACE_1 = 'socketcan' + CHANNEL_1 = 'vcan0' + INTERFACE_2 = 'socketcan' + CHANNEL_2 = 'vcan0' + + +@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +class SocketCanBroadcastChannel(unittest.TestCase): def setUp(self): - socketcan_version = can.util.choose_socketcan_implementation() - print("testing python-can's socketcan version:", - socketcan_version) - - self.bus1 = can.Bus(channel="vcan0", - bustype=socketcan_version, - bitrate=250000, - fd=TEST_CAN_FD) - self.bus2 = can.Bus(channel="vcan0", - bustype=socketcan_version, - bitrate=250000, - fd=TEST_CAN_FD) + self.broadcast_bus = can.Bus(channel='', bustype='socketcan') + self.regular_bus = can.Bus(channel='vcan0', bustype='socketcan') def tearDown(self): - self.bus1.shutdown() - self.bus2.shutdown() - - def test_basics(self): - reader = can.BufferedReader() - notifier = can.Notifier(self.bus2, [reader]) + self.broadcast_bus.shutdown() + self.regular_bus.shutdown() - message = can.Message(arbitration_id=0x4321, data=[1, 2, 3], extended_id=True) - self.bus1.send(message) + def test_broadcast_channel(self): + self.broadcast_bus.send(can.Message(channel='vcan0')) + recv_msg = self.regular_bus.recv(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 'vcan0') - self.assertEqual(message, reader.get_message(timeout=2.0)) - notifier.stop() + self.regular_bus.send(can.Message()) + recv_msg = self.broadcast_bus.recv(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 'vcan0') class TestThreadSafeBus(Back2BackTestCase): @@ -176,14 +173,14 @@ class TestThreadSafeBus(Back2BackTestCase): """ def setUp(self): - self.bus1 = can.ThreadSafeBus(channel=CHANNEL_1, - bustype=INTERFACE_1, - bitrate=BITRATE, + self.bus1 = can.ThreadSafeBus(channel=self.CHANNEL_1, + bustype=self.INTERFACE_1, + bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True) - self.bus2 = can.ThreadSafeBus(channel=CHANNEL_2, - bustype=INTERFACE_2, - bitrate=BITRATE, + self.bus2 = can.ThreadSafeBus(channel=self.CHANNEL_2, + bustype=self.INTERFACE_2, + bitrate=self.BITRATE, fd=TEST_CAN_FD, single_handle=True) diff --git a/test/open_vcan.sh b/test/open_vcan.sh index 0e9ba95c1..bd02ad752 100755 --- a/test/open_vcan.sh +++ b/test/open_vcan.sh @@ -4,4 +4,4 @@ modprobe vcan ip link add dev vcan0 type vcan -ip link set up vcan0 +ip link set up vcan0 mtu 72 diff --git a/test/test_detect_available_configs.py b/test/test_detect_available_configs.py index f9fa1079c..c3e595891 100644 --- a/test/test_detect_available_configs.py +++ b/test/test_detect_available_configs.py @@ -43,7 +43,7 @@ def test_content_virtual(self): def test_content_socketcan(self): configs = detect_available_configs(interfaces='socketcan') for config in configs: - self.assertIn(config['interface'], ('socketcan_native', 'socketcan_ctypes')) + self.assertEqual(config['interface'], 'socketcan') @unittest.skipUnless(IS_LINUX, "socketcan is only available on Linux") def test_socketcan_on_ci_server(self): diff --git a/test/test_socketcan_helpers.py b/test/test_socketcan_helpers.py index f33a4e28a..beb2b27df 100644 --- a/test/test_socketcan_helpers.py +++ b/test/test_socketcan_helpers.py @@ -9,7 +9,7 @@ import unittest -from can.interfaces.socketcan.socketcan_common import \ +from can.interfaces.socketcan.utils import \ find_available_interfaces, error_code_to_str from .config import * From 85e12744b00186b85305618c038801b11d236a80 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 29 Jun 2018 22:27:45 +1000 Subject: [PATCH 166/168] Feature travis to build sphinx docs (#342) * Get travis CI to build the sphinx docs * Various documentation updates (primarily to minimize sphinx warnings) * Pypy is having issues with building sphinx docs so just build for python 3.6 * Enabled nitpick mode, but not turn warnings into errors Closes #340 --- .travis.yml | 7 ++++++- can/__init__.py | 5 +++-- can/broadcastmanager.py | 14 ++++++++------ can/bus.py | 5 ++--- can/interface.py | 8 +++++--- can/notifier.py | 4 ++-- can/thread_safe_bus.py | 6 ------ doc/api.rst | 9 +++++++-- doc/bcm.rst | 39 ++++++++++++++++++++++++++++++++------- doc/bus.rst | 38 +++++++++++++++++++++----------------- doc/conf.py | 2 +- doc/development.rst | 2 +- 12 files changed, 88 insertions(+), 51 deletions(-) diff --git a/.travis.yml b/.travis.yml index 917e135c3..358d32666 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ python: - "3.4" - "3.5" - "3.6" - - "3.7-dev" # TODO: change to "3.7" once it gets released + - "3.7-dev" # TODO: change to "3.7" once it is supported by travis-ci - "nightly" # PyPy: - "pypy" @@ -46,6 +46,11 @@ matrix: install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo bash test/open_vcan.sh ; fi - travis_retry pip install .[test] + - travis_retry pip install sphinx script: - pytest -v --timeout=300 + # Build Docs with Sphinx + # + # -a Write all files + - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then python -m sphinx -an doc build; fi \ No newline at end of file diff --git a/can/__init__.py b/can/__init__.py index 4e2099e7b..c2ba8fc18 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -15,9 +15,10 @@ rc = dict() + class CanError(IOError): - """ - Indicates an error with the CAN network. + """Indicates an error with the CAN network. + """ pass diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index e25a9312f..2c7072739 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -10,11 +10,10 @@ import abc import logging -import sched + import threading import time -import can log = logging.getLogger('can.bcm') @@ -77,7 +76,7 @@ def modify_data(self, message): """Update the contents of this periodically sent message without altering the timing. - :param message: The :class:`~can.Message` with new :attr:`Message.data`. + :param message: The :class:`~can.Message` with new :attr:`can.Message.data`. """ self.message = message @@ -136,9 +135,12 @@ def _run(self): def send_periodic(bus, message, period, *args, **kwargs): + """Send a message every `period` seconds on the given channel. + + :param bus: The :class:`can.BusABC` to transmit to. + :param message: The :class:`can.Message` instance to periodically send + :return: A started task instance """ - Send a message every `period` seconds on the given channel. - """ - log.warn("The method `can.send_periodic` is deprecated and will " + log.warning("The function `can.send_periodic` is deprecated and will " + "be removed in version 2.3. Please use `can.Bus.send_periodic` instead.") return bus.send_periodic(message, period, *args, **kwargs) diff --git a/can/bus.py b/can/bus.py index 87c362b58..c5ae7fdf0 100644 --- a/can/bus.py +++ b/can/bus.py @@ -159,7 +159,7 @@ def send_periodic(self, msg, period, duration=None): no duration is provided, the task will continue indefinitely. :return: A started task instance - :rtype: can.CyclicSendTaskABC + :rtype: can.broadcastmanager.CyclicSendTaskABC .. note:: @@ -211,7 +211,7 @@ def set_filters(self, filters=None): Calling without passing any filters will reset the applied filters to `None`. - :param Iterator[dict] filters: + :param filters: A iterable of dictionaries each containing a "can_id", a "can_mask", and an optional "extended" key. @@ -221,7 +221,6 @@ def set_filters(self, filters=None): If ``extended`` is set as well, it only matches messages where `` == extended``. Else it matches every messages based only on the arbitration ID and mask. - """ self._filters = filters or None self._apply_filters(self._filters) diff --git a/can/interface.py b/can/interface.py index 9a42da73f..8156f7d08 100644 --- a/can/interface.py +++ b/can/interface.py @@ -29,6 +29,7 @@ log = logging.getLogger('can.interface') log_autodetect = log.getChild('detect_available_configs') + def _get_class_for_interface(interface): """ Returns the main bus class for the given interface. @@ -66,7 +67,8 @@ def _get_class_for_interface(interface): class Bus(BusABC): - """ + """Bus wrapper with configuration loading. + Instantiates a CAN Bus of the given ``interface``, falls back to reading a configuration file from default locations. """ @@ -130,9 +132,9 @@ def detect_available_configs(interfaces=None): - the name of an interface to be searched in as a string, - an iterable of interface names to search in, or - `None` to search in all known interfaces. - :rtype: list of `dict`s + :rtype: list[dict] :return: an iterable of dicts, each suitable for usage in - :class:`can.interface.Bus`'s constructor. + :class:`can.interface.Bus`\ 's constructor. """ # Figure out where to search diff --git a/can/notifier.py b/can/notifier.py index 5e4c23e7f..650353694 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -18,8 +18,8 @@ def __init__(self, bus, listeners, timeout=1): """Manages the distribution of **Messages** from a given bus/buses to a list of listeners. - :param can.Bus bus: The :ref:`bus` or a list of buses to listen to. - :param list listeners: An iterable of :class:`~can.Listener`s + :param can.BusABC bus: The :ref:`bus` or a list of buses to listen to. + :param list listeners: An iterable of :class:`~can.Listener` :param float timeout: An optional maximum number of seconds to wait for any message. """ self.listeners = listeners diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index f9779a32b..3a126d90e 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -1,12 +1,7 @@ #!/usr/bin/env python # coding: utf-8 -""" -""" - from __future__ import print_function, absolute_import - -from abc import ABCMeta from threading import RLock try: @@ -19,7 +14,6 @@ import_exc = exc from .interface import Bus -from .bus import BusABC class NullContextManager(object): diff --git a/doc/api.rst b/doc/api.rst index 13adb2903..1e40df2c9 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -18,7 +18,6 @@ A form of CAN interface is also required. bcm - Utilities --------- @@ -27,10 +26,11 @@ Utilities .. automethod:: can.detect_available_configs -.. _notifier: +.. _notifier: + Notifier -------- @@ -38,3 +38,8 @@ The Notifier object is used as a message distributor for a bus. .. autoclass:: can.Notifier :members: + +Errors +------ + +.. autoclass:: can.CanError diff --git a/doc/bcm.rst b/doc/bcm.rst index d5615c58d..f53bc1444 100644 --- a/doc/bcm.rst +++ b/doc/bcm.rst @@ -16,17 +16,42 @@ This example shows the socketcan_ctypes backend using the broadcast manager: :linenos: -.. note:: - The functional APi in :meth:`can.send_periodic` is now deprected. - Use the object oriented APi in :meth:`can.BusABC.send_periodic` instead. - - Class based API --------------- -.. autoclass:: can.CyclicSendTaskABC +.. autoclass:: can.broadcastmanager.CyclicTask + :members: + + +.. autoclass:: can.broadcastmanager.CyclicSendTaskABC + :members: + +.. autoclass:: can.broadcastmanager.LimitedDurationCyclicSendTaskABC + :members: + + +.. autoclass:: can.broadcastmanager.RestartableCyclicTaskABC :members: -.. autoclass:: can.MultiRateCyclicSendTaskABC +.. autoclass:: can.broadcastmanager.ModifiableCyclicTaskABC :members: + +.. autoclass:: can.broadcastmanager.MultiRateCyclicSendTaskABC + :members: + +.. autoclass:: can.broadcastmanager.ThreadBasedCyclicSendTask + :members: + + + +Functional API +-------------- + +.. note:: + The functional API in :func:`can.broadcastmanager.send_periodic` is now deprecated. + Use the object oriented API via :meth:`can.BusABC.send_periodic` instead. + + +.. autofunction:: can.broadcastmanager.send_periodic + diff --git a/doc/bus.rst b/doc/bus.rst index 4aca097bb..0a2291591 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -3,19 +3,12 @@ Bus --- -The :class:`~can.Bus` class, as the name suggests, provides an abstraction of a CAN bus. +The :class:`can.BusABC` class, as the name suggests, provides an abstraction of a CAN bus. The bus provides an abstract wrapper around a physical or virtual CAN Bus. A thread safe bus wrapper is also available, see `Thread safe bus`_. -Filtering -''''''''' - -Message filtering can be set up for each bus. Where the interface supports it, this is carried -out in the hardware or kernel layer - not in Python. - - API '''' @@ -23,16 +16,12 @@ API :members: :special-members: __iter__ -.. autoclass:: can.interface.Bus - :members: - :special-members: __iter__ - Transmitting '''''''''''' -Writing to the bus is done by calling the :meth:`~can.BusABC.send()` method and -passing a :class:`~can.Message` object. +Writing to the bus is done by calling the :meth:`~can.BusABC.send` method and +passing a :class:`~can.Message` instance. Receiving @@ -48,14 +37,21 @@ Alternatively the :class:`~can.Listener` api can be used, which is a list of :cl subclasses that receive notifications when new messages arrive. +Filtering +''''''''' + +Message filtering can be set up for each bus. Where the interface supports it, this is carried +out in the hardware or kernel layer - not in Python. + + Thread safe bus --------------- -This thread safe version of the :class:`~can.Bus` class can be used by multiple threads at once. -Sending and receiving is locked seperatly to avoid unnessesary delays. +This thread safe version of the :class:`~can.BusABC` class can be used by multiple threads at once. +Sending and receiving is locked separately to avoid unnecessary delays. Conflicting calls are executed by blocking until the bus is accessible. -It can be used exactly like the normal :class:`~can.Bus`: +It can be used exactly like the normal :class:`~can.BusABC`: # 'socketcan' is only an exemple interface, it works with all the others too my_bus = can.ThreadSafeBus(interface='socketcan', channel='vcan0') @@ -64,3 +60,11 @@ It can be used exactly like the normal :class:`~can.Bus`: .. autoclass:: can.ThreadSafeBus :members: + +Autoconfig Bus +-------------- + +.. autoclass:: can.interface.Bus + :members: + :special-members: __iter__ + diff --git a/doc/conf.py b/doc/conf.py index c86010c6f..9adaf9b7d 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -45,7 +45,7 @@ } intersphinx_mapping = { - 'python': ('https://docs.python.org/2/', None), + 'python': ('https://docs.python.org/3/', None) } # If this is True, todo and todolist produce output, else they produce nothing. diff --git a/doc/development.rst b/doc/development.rst index 85ddfa826..17e7f68ab 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -99,7 +99,7 @@ The modules in ``python-can`` are: |:doc:`broadcastmanager ` | Contains interface independent broadcast manager | | | code. | +---------------------------------+------------------------------------------------------+ -|:doc:`CAN ` | Legacy API. Deprecated. | +|:doc:`CAN ` | Legacy API. Deprecated. | +---------------------------------+------------------------------------------------------+ From c61d5c7cbc3f8f7d506b28b1334ccf5eac828b7a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 29 Jun 2018 22:55:37 +1000 Subject: [PATCH 167/168] Bump version to 2.2.0-rc.1 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index c2ba8fc18..370260429 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -9,7 +9,7 @@ import logging -__version__ = "2.2.0-dev" +__version__ = "2.2.0-rc.1" log = logging.getLogger('can') From 49eb7301366c6ce79ca3a6c16b3d077110c4f0fe Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sat, 30 Jun 2018 15:03:29 +1000 Subject: [PATCH 168/168] Add changelog highlights and set version to 2.2.0 --- CHANGELOG.txt | 13 +++++++++++++ can/__init__.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 06b6ef4ee..781feef24 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,16 @@ + +Version 2.2.0 (2018-06-30) +===== + +* Fallback message filtering implemented in Python for interfaces that don't offer better accelerated mechanism. +* SocketCAN interfaces have been merged (Now use `socketcan` instead of either `socketcan_native` and `socketcan_ctypes`), + this is now completely transparent for the library user. +* automatic detection of available configs/channels in supported interfaces. +* Added synchronized (thread-safe) Bus variant. +* context manager support for the Bus class. +* Dropped support for Python 3.3 (officially reached end-of-life in Sept. 2017) +* Deprecated the old `CAN` module, please use the newer `can` entry point (will be removed in version 2.4) + Version 2.1.0 (2018-02-17) ===== diff --git a/can/__init__.py b/can/__init__.py index 370260429..e10968cd7 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -9,7 +9,7 @@ import logging -__version__ = "2.2.0-rc.1" +__version__ = "2.2.0" log = logging.getLogger('can')