From 5bf1a40ecacc53bf54de562f8e56893d6b582c41 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Fri, 5 Jun 2020 16:57:40 -0400 Subject: [PATCH 01/12] cantact support --- can/interfaces/__init__.py | 1 + can/interfaces/cantact.py | 72 ++++++++++++++++++++++++++++++++++++++ doc/installation.rst | 8 +++++ 3 files changed, 81 insertions(+) create mode 100644 can/interfaces/cantact.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 2f00d0309..33acf735f 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -24,6 +24,7 @@ "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), "systec": ("can.interfaces.systec", "UcanBus"), "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), + "cantact": ("can.interfaces.cantact", "CANtact"), } BACKENDS.update( diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py new file mode 100644 index 000000000..1716eafee --- /dev/null +++ b/can/interfaces/cantact.py @@ -0,0 +1,72 @@ +""" +Interface for CANtact devices from Linklayer Labs +""" + +import time +import logging +import cantact + +from can import BusABC, Message + +logger = logging.getLogger(__name__) + +class CANtact(BusABC): + """CANtact interface""" + + @staticmethod + def _detect_available_configs(): + interface = cantact.Interface() + channels = [] + + for i in range(0, interface.channel_count()): + channels.append({"interface": "cantact", "channel": "ch:%d" % i}) + + return channels + + def __init__(self, channel, bitrate=500000, poll_interval=0.01, monitor=False, **kwargs): + """ + :param int channel: + Device number + :param int bitrate: + Bitrate in bits/s + :param bool monitor: + If true, operate in listen-only monitoring mode + """ + + self.channel = int(channel) + self.channel_info = "CANtact: ch:%s" % channel + + # configure the interface + self.interface = cantact.Interface() + self.interface.set_bitrate(int(channel), int(bitrate)) + self.interface.set_enabled(int(channel), True) + self.interface.set_monitor(int(channel), monitor) + self.interface.start() + + super().__init__( + channel=channel, bitrate=bitrate, poll_interval=poll_interval, **kwargs + ) + + def _recv_internal(self, timeout): + frame = self.interface.recv(int(timeout * 1000)) + if frame is None: + # timeout occured + return None, False + + msg = Message( + arbitration_id=frame['id'], + is_extended_id=frame['extended'], + timestamp=time.time(), # Better than nothing... + is_remote_frame=frame['rtr'], + dlc=frame['dlc'], + data=frame['data'][: frame['dlc']], + channel=frame['channel'] + ) + return msg, False + + def send(self, msg, timeout=None): + self.interface.send(self.channel, msg.arbitration_id, bool( + msg.is_extended_id), bool(msg.is_remote_frame), msg.dlc, msg.data) + + def shutdown(self): + self.interface.stop() diff --git a/doc/installation.rst b/doc/installation.rst index a70f7d5ea..5778682f0 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -90,6 +90,14 @@ To install ``python-can`` using the XL Driver Library as the backend: 3. Use Vector Hardware Configuration to assign a channel to your application. +CANtact +~~~~~~~ + +To install ``python-can`` using the CANtact driver backend: + +1. Install the Python ``cantact`` package: ``python3 -m pip install cantact`` + + Installing python-can in development mode ----------------------------------------- From ef027811c749ce17837d32eed75b7f96c7a2cd02 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Sat, 6 Jun 2020 16:14:33 -0400 Subject: [PATCH 02/12] add cantact to extras_require --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 6499aad8b..85a4352e4 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ "seeedstudio": ["pyserial>=3.0"], "serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12"], + "cantact": ["cantact>=0.0.5"], } setup( From ef86ab03cc43b97930cce8c3f864f9afc4c82def Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Sat, 6 Jun 2020 16:17:11 -0400 Subject: [PATCH 03/12] formatting fixes --- can/interfaces/cantact.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 1716eafee..01b2f01f4 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -10,6 +10,7 @@ logger = logging.getLogger(__name__) + class CANtact(BusABC): """CANtact interface""" @@ -23,7 +24,9 @@ def _detect_available_configs(): return channels - def __init__(self, channel, bitrate=500000, poll_interval=0.01, monitor=False, **kwargs): + def __init__( + self, channel, bitrate=500000, poll_interval=0.01, monitor=False, **kwargs + ): """ :param int channel: Device number @@ -54,19 +57,25 @@ def _recv_internal(self, timeout): return None, False msg = Message( - arbitration_id=frame['id'], - is_extended_id=frame['extended'], + arbitration_id=frame["id"], + is_extended_id=frame["extended"], timestamp=time.time(), # Better than nothing... - is_remote_frame=frame['rtr'], - dlc=frame['dlc'], - data=frame['data'][: frame['dlc']], - channel=frame['channel'] + is_remote_frame=frame["rtr"], + dlc=frame["dlc"], + data=frame["data"][: frame["dlc"]], + channel=frame["channel"], ) return msg, False def send(self, msg, timeout=None): - self.interface.send(self.channel, msg.arbitration_id, bool( - msg.is_extended_id), bool(msg.is_remote_frame), msg.dlc, msg.data) + self.interface.send( + self.channel, + msg.arbitration_id, + bool(msg.is_extended_id), + bool(msg.is_remote_frame), + msg.dlc, + msg.data, + ) def shutdown(self): self.interface.stop() From 08ba68a1b96951a70d6136b2b590371788deda89 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 8 Jun 2020 13:18:12 -0400 Subject: [PATCH 04/12] rename class to follow convention, add tests --- can/interfaces/__init__.py | 2 +- can/interfaces/cantact.py | 10 +++- test/test_cantact.py | 93 ++++++++++++++++++++++++++++++++++++++ tox.ini | 1 + 4 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 test/test_cantact.py diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 33acf735f..1fa3f5b4b 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -24,7 +24,7 @@ "canalystii": ("can.interfaces.canalystii", "CANalystIIBus"), "systec": ("can.interfaces.systec", "UcanBus"), "seeedstudio": ("can.interfaces.seeedstudio", "SeeedBus"), - "cantact": ("can.interfaces.cantact", "CANtact"), + "cantact": ("can.interfaces.cantact", "CantactBus"), } BACKENDS.update( diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 01b2f01f4..1e18644a5 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -4,14 +4,20 @@ import time import logging -import cantact from can import BusABC, Message logger = logging.getLogger(__name__) +try: + import cantact +except ImportError: + logger.warning( + "The CANtact module is not installed. Install it using `python3 -m pip install cantact`" + ) -class CANtact(BusABC): + +class CantactBus(BusABC): """CANtact interface""" @staticmethod diff --git a/test/test_cantact.py b/test/test_cantact.py new file mode 100644 index 000000000..41211c257 --- /dev/null +++ b/test/test_cantact.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +Tests for CANtact interfaces +""" + +import time +import logging +import unittest +from unittest.mock import Mock, patch + +import pytest + +import can +import cantact as cantactdrv +from can.interfaces import cantact + + +def recv(timeout): + if timeout > 0: + frame = {} + frame["id"] = 0x123 + frame["extended"] = False + frame["timestamp"] = time.time() + frame["rtr"] = False + frame["dlc"] = 8 + frame["data"] = [1, 2, 3, 4, 5, 6, 7, 8] + frame["channel"] = 0 + return frame + else: + # simulate timeout when timeout = 0 + return None + + +""" +Mock methods for CANtact library to allow test without hardware +""" + + +class MockInterface: + start = Mock() + set_bitrate = Mock() + set_enabled = Mock() + set_monitor = Mock() + start = Mock() + stop = Mock() + send = Mock() + recv = Mock(side_effect=recv) + channel_count = Mock(return_value=1) + + +class CantactTest(unittest.TestCase): + def setUp(self): + cantactdrv.Interface = MockInterface + + def test_bus_creation(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + self.assertIsInstance(bus, cantact.CantactBus) + cantactdrv.Interface.set_bitrate.assert_called() + cantactdrv.Interface.set_enabled.assert_called() + cantactdrv.Interface.set_monitor.assert_called() + cantactdrv.Interface.start.assert_called() + + def test_transmit(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + bus.send(msg) + cantactdrv.Interface.send.assert_called() + + def test_recv(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + frame = bus.recv(timeout=0.5) + cantactdrv.Interface.recv.assert_called() + self.assertIsInstance(f, can.Message) + + def test_recv_timeout(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + frame = bus.recv(timeout=0.0) + cantactdrv.Interface.recv.assert_called() + self.assertIsNone(f) + + def test_shutdown(self): + bus = can.Bus(channel=0, bustype="cantact", _testing=True) + bus.shutdown() + cantactdrv.Interface.stop.assert_called() + + def test_available_configs(self): + configs = cantact.CantactBus._detect_available_configs() + expected = [{"interface": "cantact", "channel": "ch:0"}] + self.assertListEqual(configs, expected) diff --git a/tox.ini b/tox.ini index e5df520fe..2fbd1bad4 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ deps = codecov~=2.0 hypothesis~=4.56 pyserial~=3.0 + cantact~=0.0.5 commands = pytest From 8622223207c329598ac903a47e7da4b474e9d11e Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 8 Jun 2020 14:14:16 -0400 Subject: [PATCH 05/12] update pip before running tests in travis-ci --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3b674f371..25323ca42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ install: script: - | + # update pip + python3 -m pip install -U pip # install tox pip install tox # Run the tests From 71bb78af4854c3efddb37d418392feffee37f762 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 8 Jun 2020 14:19:49 -0400 Subject: [PATCH 06/12] fix error in tests, modify pip upgrade --- .travis.yml | 2 +- test/test_cantact.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25323ca42..c676a2c0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ install: script: - | # update pip - python3 -m pip install -U pip + pip install -U pip # install tox pip install tox # Run the tests diff --git a/test/test_cantact.py b/test/test_cantact.py index 41211c257..bd37e5c98 100644 --- a/test/test_cantact.py +++ b/test/test_cantact.py @@ -74,13 +74,13 @@ def test_recv(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) frame = bus.recv(timeout=0.5) cantactdrv.Interface.recv.assert_called() - self.assertIsInstance(f, can.Message) + self.assertIsInstance(frame, can.Message) def test_recv_timeout(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) frame = bus.recv(timeout=0.0) cantactdrv.Interface.recv.assert_called() - self.assertIsNone(f) + self.assertIsNone(frame) def test_shutdown(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) From 6bcb8ee59ffa20136143c03045b380eb26fa0756 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 8 Jun 2020 15:05:31 -0400 Subject: [PATCH 07/12] remove dependency for tests --- .travis.yml | 2 -- can/interfaces/cantact.py | 58 +++++++++++++++++++++++++++++++++++---- test/test_cantact.py | 58 ++++++--------------------------------- tox.ini | 1 - 4 files changed, 61 insertions(+), 58 deletions(-) diff --git a/.travis.yml b/.travis.yml index c676a2c0a..3b674f371 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,6 @@ install: script: - | - # update pip - pip install -U pip # install tox pip install tox # Run the tests diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 1e18644a5..0c60f88b2 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -4,6 +4,7 @@ import time import logging +from unittest.mock import Mock from can import BusABC, Message @@ -22,16 +23,25 @@ class CantactBus(BusABC): @staticmethod def _detect_available_configs(): - interface = cantact.Interface() - channels = [] + try: + interface = cantact.Interface() + except NameError: + # couldn't import cantact, so no configurations are available + return [] + channels = [] for i in range(0, interface.channel_count()): channels.append({"interface": "cantact", "channel": "ch:%d" % i}) - return channels def __init__( - self, channel, bitrate=500000, poll_interval=0.01, monitor=False, **kwargs + self, + channel, + bitrate=500000, + poll_interval=0.01, + monitor=False, + _testing=False, + **kwargs ): """ :param int channel: @@ -42,11 +52,15 @@ def __init__( If true, operate in listen-only monitoring mode """ + if _testing: + self.interface = MockInterface() + else: + self.interface = cantact.Interface() + self.channel = int(channel) self.channel_info = "CANtact: ch:%s" % channel # configure the interface - self.interface = cantact.Interface() self.interface.set_bitrate(int(channel), int(bitrate)) self.interface.set_enabled(int(channel), True) self.interface.set_monitor(int(channel), monitor) @@ -85,3 +99,37 @@ def send(self, msg, timeout=None): def shutdown(self): self.interface.stop() + + +def mock_recv(timeout): + if timeout > 0: + frame = {} + frame["id"] = 0x123 + frame["extended"] = False + frame["timestamp"] = time.time() + frame["rtr"] = False + frame["dlc"] = 8 + frame["data"] = [1, 2, 3, 4, 5, 6, 7, 8] + frame["channel"] = 0 + return frame + else: + # simulate timeout when timeout = 0 + return None + + +class MockInterface: + """ + Mock interface to replace real interface when testing. + This allows for tests to run without actual hardware. + """ + + start = Mock() + set_bitrate = Mock() + set_enabled = Mock() + set_monitor = Mock() + start = Mock() + stop = Mock() + send = Mock() + channel_count = Mock(return_value=1) + + recv = Mock(side_effect=mock_recv) diff --git a/test/test_cantact.py b/test/test_cantact.py index bd37e5c98..9f5bf7ea9 100644 --- a/test/test_cantact.py +++ b/test/test_cantact.py @@ -13,54 +13,17 @@ import pytest import can -import cantact as cantactdrv from can.interfaces import cantact -def recv(timeout): - if timeout > 0: - frame = {} - frame["id"] = 0x123 - frame["extended"] = False - frame["timestamp"] = time.time() - frame["rtr"] = False - frame["dlc"] = 8 - frame["data"] = [1, 2, 3, 4, 5, 6, 7, 8] - frame["channel"] = 0 - return frame - else: - # simulate timeout when timeout = 0 - return None - - -""" -Mock methods for CANtact library to allow test without hardware -""" - - -class MockInterface: - start = Mock() - set_bitrate = Mock() - set_enabled = Mock() - set_monitor = Mock() - start = Mock() - stop = Mock() - send = Mock() - recv = Mock(side_effect=recv) - channel_count = Mock(return_value=1) - - class CantactTest(unittest.TestCase): - def setUp(self): - cantactdrv.Interface = MockInterface - def test_bus_creation(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) self.assertIsInstance(bus, cantact.CantactBus) - cantactdrv.Interface.set_bitrate.assert_called() - cantactdrv.Interface.set_enabled.assert_called() - cantactdrv.Interface.set_monitor.assert_called() - cantactdrv.Interface.start.assert_called() + cantact.MockInterface.set_bitrate.assert_called() + cantact.MockInterface.set_enabled.assert_called() + cantact.MockInterface.set_monitor.assert_called() + cantact.MockInterface.start.assert_called() def test_transmit(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) @@ -68,26 +31,21 @@ def test_transmit(self): arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True ) bus.send(msg) - cantactdrv.Interface.send.assert_called() + cantact.MockInterface.send.assert_called() def test_recv(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) frame = bus.recv(timeout=0.5) - cantactdrv.Interface.recv.assert_called() + cantact.MockInterface.recv.assert_called() self.assertIsInstance(frame, can.Message) def test_recv_timeout(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) frame = bus.recv(timeout=0.0) - cantactdrv.Interface.recv.assert_called() + cantact.MockInterface.recv.assert_called() self.assertIsNone(frame) def test_shutdown(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) bus.shutdown() - cantactdrv.Interface.stop.assert_called() - - def test_available_configs(self): - configs = cantact.CantactBus._detect_available_configs() - expected = [{"interface": "cantact", "channel": "ch:0"}] - self.assertListEqual(configs, expected) + cantact.MockInterface.stop.assert_called() diff --git a/tox.ini b/tox.ini index 2fbd1bad4..e5df520fe 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,6 @@ deps = codecov~=2.0 hypothesis~=4.56 pyserial~=3.0 - cantact~=0.0.5 commands = pytest From 896c760d471ed219d1c6aa16e1045d03ecb3026e Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 8 Jun 2020 15:12:32 -0400 Subject: [PATCH 08/12] add CI flag to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3b674f371..d4fe85b52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ python: - pypy3 env: + - CI=1 install: From 328d2bb11bfde25dfbe20f0e9f94cd777c36d02f Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 8 Jun 2020 15:20:18 -0400 Subject: [PATCH 09/12] disable SimpleCyclicSendTaskTest in CI (causes random failures) --- .travis.yml | 1 - test/simplecyclic_test.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d4fe85b52..3b674f371 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ python: - pypy3 env: - - CI=1 install: diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 83a79de8c..0499b667c 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -120,6 +120,10 @@ def test_managed_tasks(self): bus.shutdown() + @unittest.skipIf( + IS_CI, + "the timing sensitive behaviour cannot be reproduced reliably on a CI server", + ) def test_stopping_perodic_tasks(self): bus = can.interface.Bus(bustype="virtual") tasks = [] From ef3eac7ced0f4845039d5af11c9ac296c34c7849 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 8 Jun 2020 15:24:46 -0400 Subject: [PATCH 10/12] disable the correct test (test_thread_based_cyclic_send_task) --- test/simplecyclic_test.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 0499b667c..0ca415840 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -120,10 +120,6 @@ def test_managed_tasks(self): bus.shutdown() - @unittest.skipIf( - IS_CI, - "the timing sensitive behaviour cannot be reproduced reliably on a CI server", - ) def test_stopping_perodic_tasks(self): bus = can.interface.Bus(bustype="virtual") tasks = [] @@ -156,6 +152,9 @@ def test_stopping_perodic_tasks(self): bus.shutdown() + @unittest.skipIf( + IS_CI, "fails randomly when run on CI server", + ) def test_thread_based_cyclic_send_task(self): bus = can.ThreadSafeBus(bustype="virtual") msg = can.Message( From d8f86a7ba05493feca54ce0608d4c9d6713a5ea5 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 8 Jun 2020 15:34:55 -0400 Subject: [PATCH 11/12] fix formatting --- test/simplecyclic_test.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 0ca415840..82f0850a7 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -152,9 +152,7 @@ def test_stopping_perodic_tasks(self): bus.shutdown() - @unittest.skipIf( - IS_CI, "fails randomly when run on CI server", - ) + @unittest.skipIf(IS_CI, "fails randomly when run on CI server") def test_thread_based_cyclic_send_task(self): bus = can.ThreadSafeBus(bustype="virtual") msg = can.Message( From 129aa027312bcb3d6ee530fa4c467b089f224fc4 Mon Sep 17 00:00:00 2001 From: Eric Evenchick Date: Mon, 3 Aug 2020 11:39:36 -0400 Subject: [PATCH 12/12] improve docs, add timestamps, custom bit timing, and is_rx --- can/interfaces/cantact.py | 23 ++++++++++++++++++++--- doc/installation.rst | 8 +++++++- setup.py | 2 +- test/test_cantact.py | 13 +++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 0c60f88b2..5d71cf3ad 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -40,16 +40,19 @@ def __init__( bitrate=500000, poll_interval=0.01, monitor=False, + bit_timing=None, _testing=False, **kwargs ): """ :param int channel: - Device number + Channel number (zero indexed, labeled on multi-channel devices) :param int bitrate: Bitrate in bits/s :param bool monitor: If true, operate in listen-only monitoring mode + :param BitTiming bit_timing + Optional BitTiming to use for custom bit timing setting. Overrides bitrate if not None. """ if _testing: @@ -61,7 +64,18 @@ def __init__( self.channel_info = "CANtact: ch:%s" % channel # configure the interface - self.interface.set_bitrate(int(channel), int(bitrate)) + if bit_timing is None: + # use bitrate + self.interface.set_bitrate(int(channel), int(bitrate)) + else: + # use custom bit timing + self.interface.set_bit_timing( + int(channel), + int(bit_timing.brp), + int(bit_timing.tseg1), + int(bit_timing.tseg2), + int(bit_timing.sjw), + ) self.interface.set_enabled(int(channel), True) self.interface.set_monitor(int(channel), monitor) self.interface.start() @@ -79,11 +93,12 @@ def _recv_internal(self, timeout): msg = Message( arbitration_id=frame["id"], is_extended_id=frame["extended"], - timestamp=time.time(), # Better than nothing... + timestamp=frame["timestamp"], is_remote_frame=frame["rtr"], dlc=frame["dlc"], data=frame["data"][: frame["dlc"]], channel=frame["channel"], + is_rx=(not frame["loopback"]), # received if not loopback frame ) return msg, False @@ -107,6 +122,7 @@ def mock_recv(timeout): frame["id"] = 0x123 frame["extended"] = False frame["timestamp"] = time.time() + frame["loopback"] = False frame["rtr"] = False frame["dlc"] = 8 frame["data"] = [1, 2, 3, 4, 5, 6, 7, 8] @@ -125,6 +141,7 @@ class MockInterface: start = Mock() set_bitrate = Mock() + set_bit_timing = Mock() set_enabled = Mock() set_monitor = Mock() start = Mock() diff --git a/doc/installation.rst b/doc/installation.rst index 5778682f0..8533b6289 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -93,10 +93,16 @@ To install ``python-can`` using the XL Driver Library as the backend: CANtact ~~~~~~~ +CANtact is supported on Linux, Windows, and macOS. To install ``python-can`` using the CANtact driver backend: -1. Install the Python ``cantact`` package: ``python3 -m pip install cantact`` +``python3 -m pip install "python-can[cantact]"`` +If ``python-can`` is already installed, the CANtact backend can be installed seperately: + +``python3 -m pip install cantact`` + +Additional CANtact documentation is available at https://cantact.io. Installing python-can in development mode ----------------------------------------- diff --git a/setup.py b/setup.py index 85a4352e4..87d807ab9 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ "seeedstudio": ["pyserial>=3.0"], "serial": ["pyserial~=3.0"], "neovi": ["python-ics>=2.12"], - "cantact": ["cantact>=0.0.5"], + "cantact": ["cantact>=0.0.7"], } setup( diff --git a/test/test_cantact.py b/test/test_cantact.py index 9f5bf7ea9..dc3b6a385 100644 --- a/test/test_cantact.py +++ b/test/test_cantact.py @@ -21,6 +21,19 @@ def test_bus_creation(self): bus = can.Bus(channel=0, bustype="cantact", _testing=True) self.assertIsInstance(bus, cantact.CantactBus) cantact.MockInterface.set_bitrate.assert_called() + cantact.MockInterface.set_bit_timing.assert_not_called() + cantact.MockInterface.set_enabled.assert_called() + cantact.MockInterface.set_monitor.assert_called() + cantact.MockInterface.start.assert_called() + + def test_bus_creation_bittiming(self): + cantact.MockInterface.set_bitrate.reset_mock() + + bt = can.BitTiming(tseg1=13, tseg2=2, brp=6, sjw=1) + bus = can.Bus(channel=0, bustype="cantact", bit_timing=bt, _testing=True) + self.assertIsInstance(bus, cantact.CantactBus) + cantact.MockInterface.set_bitrate.assert_not_called() + cantact.MockInterface.set_bit_timing.assert_called() cantact.MockInterface.set_enabled.assert_called() cantact.MockInterface.set_monitor.assert_called() cantact.MockInterface.start.assert_called()