From cdf1e5a4c7ec1caa74a0abba575d8ea3ae197aae Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 18 Jun 2020 21:28:30 +1200 Subject: [PATCH 01/18] Start 3.3.4 release notes --- CHANGELOG.txt | 8 ++++++++ can/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c2a6ea38a..1584ef235 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,11 @@ +Version 3.3.4 +==== + +Last call for Python2 support. + +* #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. + + Version 3.3.3 ==== diff --git a/can/__init__.py b/can/__init__.py index 481dc29e3..45886435d 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.3" +__version__ = "3.3.4-dev0" log = logging.getLogger('can') From ea142fdb9a35091f83ae6617fa829422b3cdd2b2 Mon Sep 17 00:00:00 2001 From: karl ding Date: Thu, 18 Jun 2020 02:43:18 -0700 Subject: [PATCH 02/18] Fix SocketCAN periodic Tasks on Python 2 (#850) socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. Also cherry pick over the relevant cyclic SocketCAN tests and enable Travis CI for SocketCAN tests on Python 2. Fixes #845 --- .travis.yml | 8 ++ can/interfaces/socketcan/socketcan.py | 2 +- test/test_cyclic_socketcan.py | 176 ++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 test/test_cyclic_socketcan.py diff --git a/.travis.yml b/.travis.yml index bcd18033a..3683f5fbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,6 +52,14 @@ jobs: # Unit Testing Stage + # testing socketcan on Trusty & Python 2.7, since it is not available on Xenial + - stage: test + name: Socketcan + os: linux + dist: trusty + python: "2.7" + sudo: required + env: TEST_SOCKETCAN=TRUE # testing socketcan on Trusty & Python 3.6, since it is not available on Xenial - stage: test name: Socketcan diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index b671f926a..4bb989dd7 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -363,7 +363,7 @@ def _tx_setup(self, message): ) try: self.bcm_socket.send(check_header) - except OSError as e: + except (socket.error, OSError) as e: if e.errno != errno.EINVAL: raise e else: diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py new file mode 100644 index 000000000..43a763c79 --- /dev/null +++ b/test/test_cyclic_socketcan.py @@ -0,0 +1,176 @@ +""" +This module tests multiple message cyclic send tasks. +""" +import unittest + +import time +import can + +from .config import TEST_INTERFACE_SOCKETCAN + + +@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "skip testing of socketcan") +class CyclicSocketCan(unittest.TestCase): + BITRATE = 500000 + TIMEOUT = 0.1 + + INTERFACE_1 = "socketcan" + CHANNEL_1 = "vcan0" + INTERFACE_2 = "socketcan" + CHANNEL_2 = "vcan0" + + PERIOD = 1.0 + + DELTA = 0.01 + + def _find_start_index(self, tx_messages, message): + """ + :param tx_messages: + The list of messages that were passed to the periodic backend + :param message: + The message whose data we wish to match and align to + + :returns: start index in the tx_messages + """ + start_index = -1 + for index, tx_message in enumerate(tx_messages): + if tx_message.data == message.data: + start_index = index + break + return start_index + + def setUp(self): + self._send_bus = can.Bus( + interface=self.INTERFACE_1, channel=self.CHANNEL_1, bitrate=self.BITRATE + ) + self._recv_bus = can.Bus( + interface=self.INTERFACE_2, channel=self.CHANNEL_2, bitrate=self.BITRATE + ) + + def tearDown(self): + self._send_bus.shutdown() + self._recv_bus.shutdown() + + def test_cyclic_initializer_message(self): + message = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + task = self._send_bus.send_periodic(message, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.CyclicSendTaskABC) + + # Take advantage of kernel's queueing mechanisms + time.sleep(4 * self.PERIOD) + task.stop() + + for _ in range(4): + tx_message = message + rx_message = self._recv_bus.recv(self.TIMEOUT) + + self.assertIsNotNone(rx_message) + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + def test_create_same_id_raises_exception(self): + messages_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + messages_b = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + + task_a = self._send_bus.send_periodic(messages_a, 1) + self.assertIsInstance(task_a, can.broadcastmanager.CyclicSendTaskABC) + + # The second one raises a ValueError when we attempt to create a new + # Task, since it has the same arbitration ID. + with self.assertRaises(ValueError): + task_b = self._send_bus.send_periodic(messages_b, 1) + + def test_modify_data_message(self): + message_odd = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + message_even = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + task = self._send_bus.send_periodic(message_odd, self.PERIOD) + self.assertIsInstance(task, can.broadcastmanager.ModifiableCyclicTaskABC) + + results_odd = [] + results_even = [] + for _ in range(1 * 4): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_odd.append(result) + + task.modify_data(message_even) + for _ in range(1 * 4): + result = self._recv_bus.recv(self.PERIOD * 2) + if result: + results_even.append(result) + + task.stop() + + # Now go through the partitioned results and assert that they're equal + for rx_index, rx_message in enumerate(results_even): + tx_message = message_even + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + if rx_index != 0: + prev_rx_message = results_even[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + for rx_index, rx_message in enumerate(results_odd): + tx_message = message_odd + + self.assertEqual(tx_message.arbitration_id, rx_message.arbitration_id) + self.assertEqual(tx_message.dlc, rx_message.dlc) + self.assertEqual(tx_message.data, rx_message.data) + self.assertEqual(tx_message.is_extended_id, rx_message.is_extended_id) + self.assertEqual(tx_message.is_remote_frame, rx_message.is_remote_frame) + self.assertEqual(tx_message.is_error_frame, rx_message.is_error_frame) + self.assertEqual(tx_message.is_fd, rx_message.is_fd) + + if rx_index != 0: + prev_rx_message = results_odd[rx_index - 1] + # Assert timestamps are within the expected period + self.assertTrue( + abs( + (rx_message.timestamp - prev_rx_message.timestamp) - self.PERIOD + ) + <= self.DELTA + ) + + +if __name__ == "__main__": + unittest.main() From ed58372c66a5c53297781e0b10e75093e7aa012e Mon Sep 17 00:00:00 2001 From: Johan Brus <69041456+johanbrus@users.noreply.github.com> Date: Tue, 4 Aug 2020 10:19:45 +0200 Subject: [PATCH 03/18] Update configuration.rst (#879) the line can.interfaces.interface gave the error "ModuleNotFoundError: No module named 'can.interfaces.interface' Hence propose to remove 'interfaces' --- doc/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index 142e816da..230eec1bd 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -19,7 +19,7 @@ the **interface** and **channel** before importing from ``can.interfaces``. can.rc['interface'] = 'socketcan' can.rc['channel'] = 'vcan0' can.rc['bitrate'] = 500000 - from can.interfaces.interface import Bus + from can.interface import Bus bus = Bus() From 0c34e5070ccaa3c89d6c1893370083943f0d5390 Mon Sep 17 00:00:00 2001 From: Karl Date: Mon, 3 Aug 2020 22:14:45 -0700 Subject: [PATCH 04/18] Fix RecursionError in Message.__getattr__ __getattr__ incorrectly assumes that self._dict always exists. However, if self._dict doesn't exist, the function attempts to call __getattr__ again, which results in infinite recursion when serializing the object via pickle. This fixes the implementation of __getattr__ and adds a test to exercise pickling/unpickling the Message. Fixes #804 --- can/message.py | 4 +++- test/test_message_class.py | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/can/message.py b/can/message.py index f85218fc0..9002cc625 100644 --- a/can/message.py +++ b/can/message.py @@ -53,7 +53,9 @@ def __getattr__(self, key): # TODO keep this for a version, in order to not break old code # this entire method (as well as the _dict attribute in __slots__ and the __setattr__ method) # can be removed in 4.0 - # this method is only called if the attribute was not found elsewhere, like in __slots__ + # this method is only called if the attribute was not found elsewhere, like in __slots_ + if key not in self.__slots__: + raise AttributeError try: warnings.warn("Custom attributes of messages are deprecated and will be removed in 4.0", DeprecationWarning) return self._dict[key] diff --git a/test/test_message_class.py b/test/test_message_class.py index 85dbe8560..05ed14b2a 100644 --- a/test/test_message_class.py +++ b/test/test_message_class.py @@ -5,12 +5,15 @@ import sys from math import isinf, isnan from copy import copy, deepcopy +import pickle from hypothesis import given, settings, reproduce_failure import hypothesis.strategies as st from can import Message +from .message_helper import ComparingMessagesTestCase + class TestMessageClass(unittest.TestCase): """ @@ -70,7 +73,7 @@ def test_methods(self, **kwargs): # check copies and equalities if is_valid: - self.assertEqual(message, message) + self.assertEqual(message, message) normal_copy = copy(message) deep_copy = deepcopy(message) for other in (normal_copy, deep_copy, message): @@ -79,5 +82,31 @@ def test_methods(self, **kwargs): self.assertTrue(message.equals(other, timestamp_delta=0)) -if __name__ == '__main__': +class MessageSerialization(unittest.TestCase, ComparingMessagesTestCase): + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + ComparingMessagesTestCase.__init__( + self, allowed_timestamp_delta=0.016, preserves_channel=True + ) + + def test_serialization(self): + message = Message( + timestamp=1.0, + arbitration_id=0x401, + is_extended_id=False, + is_remote_frame=False, + is_error_frame=False, + channel=1, + dlc=6, + data=bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]), + is_fd=False, + ) + + serialized = pickle.dumps(message, -1) + deserialized = pickle.loads(serialized) + + self.assertMessageEqual(message, deserialized) + + +if __name__ == "__main__": unittest.main() From 23cf849f1bbdd1aa40576220d2583f625e8aeb66 Mon Sep 17 00:00:00 2001 From: zhupumpkin <11852827@qq.com> Date: Fri, 6 Mar 2020 10:31:43 +0800 Subject: [PATCH 05/18] fix issue 787 --- can/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/message.py b/can/message.py index 9002cc625..2246f1d05 100644 --- a/can/message.py +++ b/can/message.py @@ -112,7 +112,7 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, if is_extended_id is not None: self.is_extended_id = is_extended_id else: - self.is_extended_id = True if extended_id is None else extended_id + self.is_extended_id = False if extended_id is None else extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame From 9682d6a916be2d9dbe61857636de20c99e723204 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Aug 2020 12:01:22 +1200 Subject: [PATCH 06/18] Update changelog and bump to alpha version --- CHANGELOG.txt | 5 +++++ can/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1584ef235..70b865475 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,11 @@ Version 3.3.4 Last call for Python2 support. * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. +* #846 Use inter-process mutex to prevent concurrent neoVI device open. +* #879 Updating incorrect api documentation. +* #885 Fix recursion message in Message.__getattr__ +* #845 Fix socketcan issue +* #788 Fix issue with extended_id Version 3.3.3 diff --git a/can/__init__.py b/can/__init__.py index 45886435d..d3c824cad 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.4-dev0" +__version__ = "3.3.4-alpha0" log = logging.getLogger('can') From 0988fba52446e18c35fe1ebc3e5db11749fd0db9 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Aug 2020 16:05:58 +1200 Subject: [PATCH 07/18] Revert change of Message extended id default behaviour. --- CHANGELOG.txt | 1 - can/__init__.py | 2 +- can/message.py | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 70b865475..9aa2d95db 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -8,7 +8,6 @@ Last call for Python2 support. * #879 Updating incorrect api documentation. * #885 Fix recursion message in Message.__getattr__ * #845 Fix socketcan issue -* #788 Fix issue with extended_id Version 3.3.3 diff --git a/can/__init__.py b/can/__init__.py index d3c824cad..e68fcd743 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.4-alpha0" +__version__ = "3.3.4-alpha1" log = logging.getLogger('can') diff --git a/can/message.py b/can/message.py index 2246f1d05..84f675183 100644 --- a/can/message.py +++ b/can/message.py @@ -112,7 +112,8 @@ def __init__(self, timestamp=0.0, arbitration_id=0, is_extended_id=None, if is_extended_id is not None: self.is_extended_id = is_extended_id else: - self.is_extended_id = False if extended_id is None else extended_id + # Default behaviour is to create extended id messages + self.is_extended_id = True if extended_id is None else extended_id self.is_remote_frame = is_remote_frame self.is_error_frame = is_error_frame From 254514178a7e602687fe22beeef8672628794165 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Aug 2020 16:22:16 +1200 Subject: [PATCH 08/18] Upgrade version of pytest --- .travis.yml | 1 + setup.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3683f5fbc..cca71b50e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ env: install: - if [[ "$TEST_SOCKETCAN" ]]; then sudo bash test/open_vcan.sh ; fi - travis_retry pip install .[test] + - pip freeze script: - | diff --git a/setup.py b/setup.py index e5b05e8dc..797409c81 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ from __future__ import absolute_import +import platform from os import listdir from os.path import isfile, join import re @@ -31,7 +32,7 @@ tests_require = [ 'mock~=2.0', - 'pytest~=4.3', + 'pytest~=4.6', 'pytest-timeout~=1.3', 'pytest-cov~=2.8', # coveragepy==5.0 fails with `Safety level may not be changed inside a transaction` @@ -53,7 +54,6 @@ needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) pytest_runner = ["pytest-runner"] if needs_pytest else [] - setup( # Description name="python-can", From b47ca2fae4fb15771b519b572f91bfdf171874f3 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 6 Aug 2020 19:48:34 +1200 Subject: [PATCH 09/18] Tag version 3.3.4-beta.0 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index e68fcd743..cfce6f0a0 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.4-alpha1" +__version__ = "3.3.4-beta.0" log = logging.getLogger('can') From 5c7810f65e2935ffea30054a3b4ac350da1e844e Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 16 Aug 2020 15:54:35 +1200 Subject: [PATCH 10/18] Change ascii conversion to be compatible with Python2.7 in ixxat backend. Contributed by @wkemps --- can/interfaces/ixxat/canlib.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 84c8751c1..eac867344 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -328,10 +328,12 @@ def __init__(self, channel, can_filters=None, **kwargs): else: raise VCIDeviceNotFoundError("Unique HW ID {} not connected or not available.".format(UniqueHardwareId)) else: - if (UniqueHardwareId is None) or (self._device_info.UniqueHardwareId.AsChar == bytes(UniqueHardwareId, 'ascii')): + if (UniqueHardwareId is None) or ( + self._device_info.UniqueHardwareId.AsChar == UniqueHardwareId.encode("ascii")): break else: - log.debug("Ignoring IXXAT with hardware id '%s'.", self._device_info.UniqueHardwareId.AsChar.decode("ascii")) + log.debug("Ignoring IXXAT with hardware id '%s'.", + self._device_info.UniqueHardwareId.AsChar.decode("ascii")) _canlib.vciEnumDeviceClose(self._device_handle) _canlib.vciDeviceOpen(ctypes.byref(self._device_info.VciObjectId), ctypes.byref(self._device_handle)) log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) From bdf46cd16ba8edf70199baa564d1f2139118a68f Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 16 Aug 2020 15:55:07 +1200 Subject: [PATCH 11/18] Version 3.3.4 --- can/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/__init__.py b/can/__init__.py index cfce6f0a0..2704efc3d 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -8,7 +8,7 @@ import logging -__version__ = "3.3.4-beta.0" +__version__ = "3.3.4" log = logging.getLogger('can') From d271323b9bf565fc500acaae774892c3d79ad075 Mon Sep 17 00:00:00 2001 From: karl ding Date: Wed, 26 Aug 2020 03:38:27 -0700 Subject: [PATCH 12/18] Fix iteration in Bus.stop_all_periodic_tasks (#901) Fix iteration in Bus.stop_all_periodic_tasks so tasks are properly stopped. In addition, add a test in order to verify that Cyclic Tasks are correctly stopped when multiple tasks are active on a particular bus. The test is carried out via the SocketCAN interface. Note: This is a squashed cherry pick of the following commits from develop: - 8112d13ceb4493067174be899d4f014131cd4114 - 1aa0bc64033b5fff6bc88474e7d1afb52e81ee7a Addresses #634 --- can/bus.py | 12 +++++++-- test/test_cyclic_socketcan.py | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/can/bus.py b/can/bus.py index 2b36b3c57..2b3044cf1 100644 --- a/can/bus.py +++ b/can/bus.py @@ -247,13 +247,21 @@ def _send_periodic_internal(self, msg, period, duration=None): return task def stop_all_periodic_tasks(self, remove_tasks=True): - """Stop sending any messages that were started using bus.send_periodic + """Stop sending any messages that were started using **bus.send_periodic**. + + .. note:: + The result is undefined if a single task throws an exception while being stopped. :param bool remove_tasks: Stop tracking the stopped tasks. """ for task in self._periodic_tasks: - task.stop(remove_task=remove_tasks) + # we cannot let `task.stop()` modify `self._periodic_tasks` while we are + # iterating over it (#634) + task.stop(remove_task=False) + + if remove_tasks: + self._periodic_tasks = [] def __iter__(self): """Allow iteration on messages as they are received. diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index 43a763c79..831264bf4 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -171,6 +171,56 @@ def test_modify_data_message(self): <= self.DELTA ) + def test_stop_all_periodic_tasks_and_remove_task(self): + message_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + message_b = can.Message( + arbitration_id=0x402, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + message_c = can.Message( + arbitration_id=0x403, + data=[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], + is_extended_id=False, + ) + + # Start Tasks + task_a = self._send_bus.send_periodic(message_a, self.PERIOD) + task_b = self._send_bus.send_periodic(message_b, self.PERIOD) + task_c = self._send_bus.send_periodic(message_c, self.PERIOD) + + self.assertIsInstance(task_a, can.broadcastmanager.ModifiableCyclicTaskABC) + self.assertIsInstance(task_b, can.broadcastmanager.ModifiableCyclicTaskABC) + self.assertIsInstance(task_c, can.broadcastmanager.ModifiableCyclicTaskABC) + + for _ in range(6): + _ = self._recv_bus.recv(self.PERIOD) + + # Stop all tasks and delete + self._send_bus.stop_all_periodic_tasks(remove_tasks=True) + + # Now wait for a few periods, after which we should definitely not + # receive any CAN messages + time.sleep(4 * self.PERIOD) + + # If we successfully deleted everything, then we will eventually read + # 0 messages. + successfully_stopped = False + for _ in range(6): + rx_message = self._recv_bus.recv(self.PERIOD) + + if rx_message is None: + successfully_stopped = True + break + self.assertTrue(successfully_stopped, "Still received messages after stopping") + + # None of the tasks should still be associated with the bus + self.assertEqual(0, len(self._send_bus._periodic_tasks)) + if __name__ == "__main__": unittest.main() From 57b1a341103a52fa6db848fe15b40b3fb6e7a5c3 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Wed, 26 Aug 2020 22:42:16 +1200 Subject: [PATCH 13/18] Update CHANGELOG.txt --- CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9aa2d95db..1e63e0d4b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,8 +3,8 @@ Version 3.3.4 Last call for Python2 support. +* #901 Fix iteration in Bus.stop_all_periodic_tasks * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. -* #846 Use inter-process mutex to prevent concurrent neoVI device open. * #879 Updating incorrect api documentation. * #885 Fix recursion message in Message.__getattr__ * #845 Fix socketcan issue From 9452b54ef3200e8fe296b046b4fb78ecd3c9abff Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 28 Aug 2020 09:15:20 -0400 Subject: [PATCH 14/18] Backport Neovi features for release 3.3.4 (#903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use inter-process mutex to prevent concurrent neoVI device open When neoVI server is enabled, there is an issue with concurrent device open. * Move neoVI open lock to module level * Adding dummy file lock when importing FileLock fails * Grammar fix * Raising more precise API error when set bitrate fails Also calling shutdown before raising exception from the init if the device is opened * Simplified if fd condition in init * Moving filelock to neovi extras_require * Adding comma back * Update CHANGELOG.txt Co-authored-by: Pierre-Luc Tessier Gagné --- CHANGELOG.txt | 1 + can/interfaces/ics_neovi/neovi_bus.py | 59 +++++++++++++++++++++++---- setup.py | 2 +- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1e63e0d4b..1f8cf82bf 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -15,6 +15,7 @@ Version 3.3.3 Backported fixes from 4.x development branch which targets Python 3. +* #846 Use inter-process mutex to prevent concurrent neoVI device open. * #798 Backport caching msg.data value in neovi interface. * #796 Fix Vector CANlib treatment of empty app name. * #771 Handle empty CSV file. diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 530d7c524..314967708 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -11,6 +11,8 @@ """ import logging +import os +import tempfile from collections import deque from can import Message, CanError, BusABC @@ -27,6 +29,35 @@ ics = None +try: + from filelock import FileLock +except ImportError as ie: + + logger.warning( + "Using ICS NeoVi can backend without the " + "filelock module installed may cause some issues!: %s", + ie, + ) + + class FileLock: + """Dummy file lock that does not actually do anything""" + + def __init__(self, lock_file, timeout=-1): + self._lock_file = lock_file + self.timeout = timeout + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + return None + + +# Use inter-process mutex to prevent concurrent device open. +# When neoVI server is enabled, there is an issue with concurrent device open. +open_lock = FileLock(os.path.join(tempfile.gettempdir(), "neovi.lock")) + + class ICSApiError(CanError): """ Indicates an error with the ICS API. @@ -118,18 +149,28 @@ def __init__(self, channel, can_filters=None, **kwargs): type_filter = kwargs.get('type_filter') serial = kwargs.get('serial') self.dev = self._find_device(type_filter, serial) - ics.open_device(self.dev) - if 'bitrate' in kwargs: - for channel in self.channels: - ics.set_bit_rate(self.dev, kwargs.get('bitrate'), channel) + with open_lock: + ics.open_device(self.dev) - fd = kwargs.get('fd', False) - if fd: - if 'data_bitrate' in kwargs: + try: + if "bitrate" in kwargs: for channel in self.channels: - ics.set_fd_bit_rate( - self.dev, kwargs.get('data_bitrate'), channel) + ics.set_bit_rate(self.dev, kwargs.get("bitrate"), channel) + + if kwargs.get("fd", False): + if "data_bitrate" in kwargs: + for channel in self.channels: + ics.set_fd_bit_rate( + self.dev, kwargs.get("data_bitrate"), channel + ) + except ics.RuntimeError as re: + logger.error(re) + err = ICSApiError(*ics.get_last_api_error(self.dev)) + try: + self.shutdown() + finally: + raise err self._use_system_timestamp = bool( kwargs.get('use_system_timestamp', False) diff --git a/setup.py b/setup.py index 797409c81..8f270378b 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ # Dependencies extras_require = { 'serial': ['pyserial~=3.0'], - 'neovi': ['python-ics>=2.12'] + 'neovi': ['python-ics>=2.12', 'filelock'] } tests_require = [ From 551cc946fe70cd3aa95d2d8b758604aeeb28a3e7 Mon Sep 17 00:00:00 2001 From: pierreluctg Date: Fri, 28 Aug 2020 09:16:52 -0400 Subject: [PATCH 15/18] Update CHANGELOG.txt --- CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1f8cf82bf..d11885fc0 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ Version 3.3.4 Last call for Python2 support. +* #846 Use inter-process mutex to prevent concurrent neoVI device open. * #901 Fix iteration in Bus.stop_all_periodic_tasks * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. * #879 Updating incorrect api documentation. @@ -15,7 +16,6 @@ Version 3.3.3 Backported fixes from 4.x development branch which targets Python 3. -* #846 Use inter-process mutex to prevent concurrent neoVI device open. * #798 Backport caching msg.data value in neovi interface. * #796 Fix Vector CANlib treatment of empty app name. * #771 Handle empty CSV file. From 453a4b7112e1de2ff07999a86c013c02c7f4684f Mon Sep 17 00:00:00 2001 From: Karl Date: Wed, 30 Sep 2020 21:33:37 -0700 Subject: [PATCH 16/18] vector: Skip channels without CAN support Skip any channels that don't have support for CAN (ex. LIN, Digital/Analog IO) instead of accepting everything when using auto-detection. Note: This is a backport of #641 to 3.3.4 Addresses #908 --- can/interfaces/vector/canlib.py | 2 ++ can/interfaces/vector/vxlapi.py | 1 + 2 files changed, 3 insertions(+) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 65af0147b..0864ecefe 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -387,6 +387,8 @@ def _detect_available_configs(): channel_configs = get_channel_configs() LOG.info('Found %d channels', len(channel_configs)) for channel_config in channel_configs: + if not channel_config.channelBusCapabilities & vxlapi.XL_BUS_ACTIVE_CAP_CAN: + continue LOG.info('Channel index %d: %s', channel_config.channelIndex, channel_config.name.decode('ascii')) diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py index ae87706c4..461a62197 100644 --- a/can/interfaces/vector/vxlapi.py +++ b/can/interfaces/vector/vxlapi.py @@ -63,6 +63,7 @@ XL_INTERFACE_VERSION = 3 XL_INTERFACE_VERSION_V4 = 4 +XL_BUS_ACTIVE_CAP_CAN = XL_BUS_TYPE_CAN << 16 XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 0x80000000 # structure for XL_RECEIVE_MSG, XL_TRANSMIT_MSG From 14c016272136c66c3737c0fe33aeca9e3ec72915 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Sat, 3 Oct 2020 13:37:37 +0200 Subject: [PATCH 17/18] Update CHANGELOG.txt --- CHANGELOG.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d11885fc0..7899d50b6 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ Version 3.3.4 Last call for Python2 support. +* #916 Vector: Skip channels without CAN support * #846 Use inter-process mutex to prevent concurrent neoVI device open. * #901 Fix iteration in Bus.stop_all_periodic_tasks * #850 Fix socket.error is a deprecated alias of OSError used on Python versions lower than 3.3. From d3ee3285b55571cc076b32d3d8c0bf7a7f44c792 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Sun, 4 Oct 2020 16:44:20 +1300 Subject: [PATCH 18/18] Remove usused import from setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8f270378b..898f54c16 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ from __future__ import absolute_import -import platform from os import listdir from os.path import isfile, join import re @@ -67,6 +66,7 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",